import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; import { EnvName, prefix } from "./config"; import { AppService } from "./constructs/app-service"; import { AssetsBucket } from "./constructs/assets-bucket"; import { ConfigStore } from "./constructs/config-store"; import { InstanceRole } from "./constructs/instance-role"; import { BAKED_OPEN_SWE_AMI_ID } from "./constructs/ami-cache"; export interface OpenSweStackProps extends cdk.StackProps { /** open-swe environment — drives the `open-swe--*` resource naming. */ readonly envName: EnvName; } /** * Per-env open-swe stack (`open-swe-dev` / `open-swe-prod`). Resource names are * prefixed `open-swe--*`. * * Composes: the per-env least-privilege instance role (T6), the Secrets/SSM * config store (T11), and the compute + ingress wiring (T12, AppService — EC2 * box, instance SG, target group, imported-listener rules, Route53 aliases, * 30-day log groups). The shared VPC and ALB are imported, never owned. Synth is * offline (AMI is the cdk.context.json-pinned placeholder until T12-deploy). */ export class OpenSweStack extends cdk.Stack { public readonly instanceRole: InstanceRole; public readonly configStore: ConfigStore; public readonly assetsBucket: AssetsBucket; public readonly appService: AppService; constructor(scope: Construct, id: string, props: OpenSweStackProps) { super(scope, id, props); const envName = props.envName; const p = prefix(envName); cdk.Tags.of(this).add("project", "open-swe"); cdk.Tags.of(this).add("env", envName); cdk.Tags.of(this).add("ManagedBy", "cdk"); // Per-env least-privilege EC2 instance role (open-swe--instance-role). this.instanceRole = new InstanceRole(this, "Instance", envName); // Secrets Manager + SSM Parameter Store shells the boot hook reads // (deploy/seahaven/fetch-config.sh). Secret shells are value-less and // populated out-of-band; IaC-managed SSM params carry real derivable values. // The instance role already grants read on open-swe-/* + /open-swe-/*. this.configStore = new ConfigStore(this, "Config", { envName }); // T7: the S3 artifact bucket (open-swe--assets) CI uploads releases to // and the box pulls app.tar.gz / spa.tar.gz from. The instance role already // grants read on it by name; the app deploy role grants write. this.assetsBucket = new AssetsBucket(this, "Assets", envName); // Surface the baked open-swe base AMI id the box runs on (pinned by id in // ami-cache.ts; refreshed by a deliberate packer rebuild → replacement). new cdk.CfnOutput(this, "BakedAmiId", { value: BAKED_OPEN_SWE_AMI_ID, description: "Baked open-swe-base-arm64 AMI id consumed by the EC2 instance.", }); // T12: compute + ingress. Imports the shared seahaven-vpc + ALB and adds the // env's EC2 box, instance SG, target group, listener rules, DNS, log groups. this.appService = new AppService(this, "App", { envName, instanceRole: this.instanceRole.role, }); new cdk.CfnOutput(this, "InstanceRoleArn", { value: this.instanceRole.role.roleArn, description: `${p} EC2 instance role ARN.`, }); new cdk.CfnOutput(this, "InstanceId", { value: this.appService.instance.instanceId, description: `${p} EC2 instance id.`, }); new cdk.CfnOutput(this, "TargetGroupArn", { value: this.appService.targetGroup.targetGroupArn, description: `${p} ALB target group ARN (→ instance:80 nginx).`, }); new cdk.CfnOutput(this, "AssetsBucketName", { value: this.assetsBucket.bucket.bucketName, description: `${p} S3 artifact bucket (CI uploads releases; box pulls).`, }); new cdk.CfnOutput(this, "DeployDocumentName", { value: this.appService.deployDocumentName, description: `${p} SSM document that rolls the box to the latest release.`, }); } }