# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Repository Overview

A JSII-enabled CDK construct library that deploys VS Code Server on AWS. Published to npm and PyPI. Designed for workshop/development environments with intentionally permissive security for ease of use.

**Key Characteristic**: This is **NOT production-ready** by design - it's optimized for quick workshop setup and learning environments.

## Projen-Managed Project

**Critical**: ALL project configuration is in `.projenrc.ts`. After modifying it, run `npx projen` to regenerate managed files.

**Never manually edit**:
- `package.json`
- Task definitions
- GitHub workflows
- Any file with "~~ Generated by projen" header

## Development Commands

### Build and Test
```bash
# Full build (compiles TS, bundles Lambdas, generates API docs)
npx projen build

# Run unit tests
npx projen test

# Run single test file
npx jest test/vscode-server.test.ts

# Watch mode for tests
npx projen test:watch

# Integration tests (deploys to eu-west-1, eu-west-2, eu-north-1, eu-west-3)
npm run integ-test
```

### Code Quality
```bash
# ESLint
npx projen eslint

# AWS CDK-specific linting (awslint rules)
npm run awslint
```

### Lambda Development
```bash
# Bundle specific Lambda
npx projen bundle:installer/installer.lambda
npx projen bundle:idle-monitor/idle-monitor.lambda
npx projen bundle:idle-monitor-enabler/idle-monitor-enabler.lambda
npx projen bundle:secret-retriever/secret-retriever.lambda

# Watch mode for Lambda development
npx projen bundle:installer/installer.lambda:watch
```

### Publishing
```bash
# Package for all targets (npm, Python)
npx projen package-all

# Package specific target
npx projen package:js
npx projen package:python
```

## Architecture Overview

### Main Construct: `VSCodeServer` (src/vscode-server.ts)

Orchestrates the entire infrastructure:
- VPC with single public subnet
- EC2 instance (default: m7g.xlarge Graviton3 ARM)
- CloudFront distribution with custom cache policies
- Security groups (CloudFront prefix list restriction only)
- IAM role with broad CDK permissions (workshop-friendly)
- Optional: Route53 + ACM certificate integration
- Optional: Auto-stop feature with idle monitoring

### Custom Resource Pattern

Uses Lambda-backed custom resources via CDK Provider construct:

1. **Installer** (`src/installer/`)
   - Runs SSM documents to install VS Code Server
   - OS-specific installation for Ubuntu 22/24/25 and Amazon Linux 2023
   - Returns SUCCESS when installation completes
   - Custom resource name: `SSMInstallerCustomResource`
   - **Critical**: Must pass `linuxFlavorType` parameter to ensure correct OS-specific SSM document is used

2. **SecretRetriever** (`src/secret-retriever/`)
   - Extracts generated password from Secrets Manager
   - Returns password as CloudFormation output

3. **PrefixListRetriever** (`src/prefixlist-retriever/`)
   - Fetches AWS-managed CloudFront prefix lists
   - Used to restrict security group ingress

4. **IdleMonitorEnabler** (`src/idle-monitor-enabler/`)
   - Enables EventBridge rule ONLY after installation completes
   - Prevents race condition where IdleMonitor stops instance mid-installation
   - Critical dependency chain: `Installer → IdleMonitorEnabler → IdleMonitor active`

### Auto-Stop Feature Architecture

**Problem Solved**: Race condition where IdleMonitor could stop instance during installation.

**Solution Flow**:
1. EventBridge rule created in **DISABLED** state (`src/idle-monitor/idle-monitor.ts:118`)
2. Installer runs and completes VS Code Server setup
3. IdleMonitorEnabler custom resource **enables** the rule after installation succeeds
4. IdleMonitor now safely monitors CloudFront metrics for idle detection

**Key Files**:
- `src/idle-monitor/idle-monitor.ts` - EventBridge rule + Lambda for monitoring
- `src/idle-monitor/idle-monitor.lambda.ts` - Checks CloudWatch metrics, stops instance if idle
- `src/idle-monitor-enabler/idle-monitor-enabler.ts` - Custom resource construct
- `src/idle-monitor-enabler/idle-monitor-enabler.lambda.ts` - Enables EventBridge rule via AWS SDK

**Dependency Wiring** (`src/vscode-server.ts:984`):
```typescript
const installerCustomResource = this.node.findChild('SSMInstallerCustomResource');
enabler.node.addDependency(installerCustomResource);
```

### AMI Selection (`src/mappings.ts`)

Contains SSM parameter paths for:
- Ubuntu 22/24/25 (ARM + x86_64)
- Amazon Linux 2023 (ARM + x86_64)

**Ubuntu Codenames**:
- Ubuntu 22 = "jammy" (uses ebs-gp2)
- Ubuntu 24 = "noble" (uses ebs-gp3)
- Ubuntu 25 = "plucky" (uses ebs-gp3)

Function `getAmiSSMParameterForLinuxArchitectureAndFlavor()` returns region-specific SSM parameter for latest AMI.

**Verify SSM Parameters** (when adding new OS versions):
```bash
# List available Ubuntu versions
aws ssm get-parameters-by-path --path "/aws/service/canonical/ubuntu/server/" --recursive --query "Parameters[*].Name" --region us-east-1

# Verify specific AMI paths exist
aws ssm get-parameters --names \
  "/aws/service/canonical/ubuntu/server/plucky/stable/current/amd64/hvm/ebs-gp3/ami-id" \
  "/aws/service/canonical/ubuntu/server/plucky/stable/current/arm64/hvm/ebs-gp3/ami-id" \
  --region us-east-1
```

### Key Props (`src/vscode-server.ts:27-200`)

**Instance Configuration**:
- `instanceClass`, `instanceSize`, `instanceVolumeSize`
- `instanceOperatingSystem` (LinuxFlavorType enum)
- `instanceCpuArchitecture` (LinuxArchitectureType enum)

**VS Code Configuration**:
- `vscodeUser`, `vscodePassword`, `homeFolder`
- `devServerPort`, `devServerBasePath`

**Domain/Certificate**:
- `domainName`, `hostedZoneId`, `certificateArn`, `autoCreateCertificate`

**Auto-Stop**:
- `enableAutoStop` - Enable automatic instance stop when idle
- `idleTimeoutMinutes` - Minutes of inactivity before stopping (default: 30)
- `idleCheckIntervalMinutes` - How often to check (default: 5)
- `skipStatusChecks` - Skip EC2 status checks before stopping (for testing only)

**Custom Installation**:
- `customInstallSteps` - Array of custom shell commands that run after standard installation
- `repoUrl` - Git repository to clone into home folder during setup

**Extensions**:
- `additionalInstanceRolePolicies`, `additionalTags`

## JSII Constraints

**Critical for JSII compatibility**:
- All public APIs must be JSII-compatible (no TS-specific types)
- Bundled dependencies (like `node-html-parser`) must be in `.projenrc.ts` `bundledDeps`
- Lambda functions use esbuild bundling configured via Projen (auto-discovered in `src/**/*.lambda.ts`)
- **@example JSDoc blocks must NOT use code fences** (```) - JSII will fail with "must be code only, no code block fences allowed"

## CDK-nag Integration

Suppressions defined in `src/suppress-nags.ts` and applied throughout construct code.

**Why suppressions are needed**: Workshop design intentionally violates production best practices:
- Broad IAM permissions for ease of use
- Permissive security groups
- Public subnet deployment
- No VPC endpoints

Apply suppressions via `NagSuppressions.addResourceSuppressions()`.

## Integration Tests

Located in `integ-tests/`, using `@aws-cdk/integ-tests-alpha` framework.

**Test Files**:
- `integ.ubuntu.ts` - Basic Ubuntu 22 deployment + login test
- `integ.ubuntu24.ts` - Ubuntu 24 deployment + login test
- `integ.ubuntu25.ts` - Ubuntu 25 deployment + login test
- `integ.al2023.ts` - Amazon Linux 2023 deployment
- `integ.custom-domain.ts` - Custom domain + ACM certificate
- `integ.stop-on-idle.ts` - **4-phase auto-stop workflow test**

**Stop-on-Idle Test Phases** (`integ-tests/integ.stop-on-idle.ts`):
1. `verify-auto-stop` - Wait for instance to stop after idle timeout
2. `disable-idle-monitor` - Disable EventBridge rule to prevent re-stopping
3. `start-instance` - Start instance and wait for running state
4. `verify-login` - Check VS Code Server accessibility via CloudFront

**Run Integration Tests**:
```bash
npm run integ-test  # Deploys to 4 regions in parallel
```

**Important**: Integration tests deploy actual stacks and incur AWS costs.

## Lambda Bundling

Projen automatically discovers Lambda functions matching `src/**/*.lambda.ts` pattern.

**Auto-generated tasks** for each Lambda:
- `bundle:<path>/name.lambda` - Bundle Lambda code
- `bundle:<path>/name.lambda:watch` - Watch mode for development

**Bundled output**: `assets/<path>/name.lambda/index.js`

## Common Workflows

### Adding a New Lambda Function

1. Create `src/my-feature/my-feature.lambda.ts`
2. Create `src/my-feature/my-feature-function.ts` (CDK construct wrapper)
3. Run `npx projen` - auto-discovers and creates bundle task
4. Import in construct: `import { MyFeatureFunction } from './my-feature/my-feature-function'`

### Adding Custom Installation Steps

Custom steps are appended to SSM document `mainSteps` array in `createSSMDocument()`:

1. **Define interface** (if needed) in `src/vscode-server.ts`:
   ```typescript
   export interface CustomInstallStep {
     readonly name: string;
     readonly commands: string[];
   }
   ```

2. **Add prop** to `VSCodeServerProps` with JSDoc documentation (no code fences!)

3. **Pass to Installer** in both Ubuntu and Amazon Linux paths:
   ```typescript
   installer = Installer.ubuntu({
     // ... other options
     customInstallSteps: customInstallSteps,
   })._bind(this);
   ```

4. **Update `createSSMDocument()`** in `src/installer/installer.ts` to accept and use custom steps:
   ```typescript
   ...(customInstallSteps?.map((step) => ({
     action: 'aws:runShellScript' as const,
     name: step.name,
     inputs: {
       runCommand: step.commands,
     },
   })) ?? []),
   ```

5. **Write unit tests** in `test/vscode-server.test.ts` covering normal usage, without steps, and empty array

6. **Update documentation** in README.md with practical examples and add to `examples/` directory

### Modifying VSCodeServer Props

1. Edit `src/vscode-server.ts` - update `VSCodeServerProps` interface
2. If adding new types/interfaces, export them from `src/vscode-server.ts`
3. Run `npx projen build` - regenerates JSII artifacts + API docs
4. Update README.md with usage examples and feature section
5. Add example usage in `examples/` directory

### Testing Changes

1. Unit tests: `npx jest test/vscode-server.test.ts`
2. Build validation: `npx projen build` (includes awslint checks)
3. Integration test: `npm run integ-test` (full deployment test)

### Adding Support for a New Ubuntu Version

When Ubuntu releases a new version (e.g., Ubuntu 26):

1. **Add AMI mappings** in `src/mappings.ts`:
   ```typescript
   [
     'arm-ubuntu26',
     '/aws/service/canonical/ubuntu/server/CODENAME/stable/current/arm64/hvm/ebs-gp3/ami-id',
   ],
   [
     'amd64-ubuntu26',
     '/aws/service/canonical/ubuntu/server/CODENAME/stable/current/amd64/hvm/ebs-gp3/ami-id',
   ],
   ```
   Replace `CODENAME` with Ubuntu's codename (verify via AWS SSM Parameter Store)

2. **Add enum value** in `src/vscode-server.ts`:
   ```typescript
   export enum LinuxFlavorType {
     // ...
     UBUNTU_26 = 'ubuntu26',
   }
   ```

3. **Update Installer** in `src/installer/installer.ts`:
   - Add `LinuxFlavorType.UBUNTU_26` to the Ubuntu switch case in `createSSMDocument()` (line ~657)
   - Update installer calls in `src/vscode-server.ts` to include new case (line ~930)

4. **Pass linuxFlavorType** in `src/vscode-server.ts`:
   ```typescript
   installer = Installer.ubuntu({
     // ... other options
     linuxFlavorType: instanceOperatingSystem, // Critical!
   })._bind(this);
   ```

5. **Create integration test**: Copy `integ-tests/integ.ubuntu25.ts` to `integ.ubuntu26.ts` and update OS version

6. **Update documentation**:
   - Update README.md examples with inline comments showing all supported versions
   - Examples in `examples/` directory (optional - can keep as Ubuntu 24)

7. **Verify and build**:
   ```bash
   # Verify SSM parameters exist
   aws ssm get-parameters-by-path --path "/aws/service/canonical/ubuntu/server/CODENAME/" --recursive --region us-east-1

   # Build and test
   npx projen build
   npm run integ-test
   ```

## Race Condition Prevention (Auto-Stop)

**Issue**: IdleMonitor EventBridge rule triggers immediately on stack creation, potentially stopping instance during installation (observed: instance stopped 72 seconds after installer started).

**Fix**: Three-stage dependency chain ensures safe operation:

```
EC2 Instance
    ↓
Installer Custom Resource (waits for VS Code Server installation)
    ↓
IdleMonitorEnabler Custom Resource (enables EventBridge rule)
    ↓
IdleMonitor Active (now safe to monitor)
```

**Implementation Details**:
- EventBridge rule created with `enabled: false` (`src/idle-monitor/idle-monitor.ts:118`)
- IdleMonitorEnabler depends on `SSMInstallerCustomResource` via `node.addDependency()`
- CloudFormation enforces ordering automatically

This pattern can be applied to any future features requiring post-installation activation.

## Multi-Language Publishing

Project publishes to:
- **npm**: `@mavogel/cdk-vscode-server`
- **PyPI**: `cdk-vscode-server`

JSII compiles TypeScript to:
- JavaScript (npm)
- Python (PyPI)

**Publishing** (requires credentials):
```bash
npx projen release
```

## Workshop-Specific Considerations

**Remember**: This construct is intentionally designed for workshops, NOT production:

- IAM roles have broad permissions (all CDK operations)
- Security groups allow CloudFront only (but permissive within that constraint)
- No private subnets or VPC endpoints (cost optimization for workshops)
- Single public subnet (simplicity over high availability)
- Password-based authentication (ease of use over MFA/SSO)

When reviewing changes, prioritize **ease of use** and **quick setup** over security hardening.
