# Infrastructure & CI/CD — Sea Haven SHOC frontend AWS hosting for the Vite SPA, defined as an **AWS CDK** app local to this repo, deployed through the org's **reusable** GitHub Actions workflow. - **Hosting:** private S3 bucket (origin) + CloudFront, served on the custom domain **`dev.seahaven.com`** (ACM `*.seahaven.com`, Route 53 apex alias). - **API:** the SPA calls the backend **directly** over HTTPS at `https://api.dev.seahaven.com/api` (`VITE_API_URL`, cross-origin; the backend allows CORS). CloudFront serves static content only — no `/api` proxy. - Domain/cert/zone values live in `cdk.json` context so the CI `cdk deploy` picks them up with no flags. `VITE_API_URL` is baked into the build, so it's per-environment (see the note under "Adding staging / prod"). - **Auth:** GitHub Actions → AWS via **OIDC** (no long-lived keys) - **CD workflow:** `.github/workflows/deploy.yml` is a thin caller of the org's `Sea-Haven-Industries/.github` → `cd-cdk.yaml`. That workflow runs `cdk deploy` (provisions infra) then `scripts/deploy-web.sh` (builds + uploads the SPA). - **Infra is local to this repo** (CDK in `infra/cdk`); the deploy role is created by this stack, not added to the central `oidc-deploy-roles.yaml`. - **Environments:** `dev` only today, deployed on push to the `dev` branch. ``` infra/cdk/ bin/app.ts entry point (reads -c context) lib/frontend-stack.ts S3 + CloudFront + OAC + OIDC deploy role scripts/deploy-web.sh build SPA -> s3 sync -> CloudFront invalidation .github/workflows/ ci.yml quality gates (lint / build / test / e2e) deploy.yml caller of the org reusable cd-cdk.yaml (push to dev) ``` ## What the stack creates | Resource | Purpose | | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | S3 bucket `seahaven-shoc-frontend-dev` | private origin (BLOCK_ALL, SSE, OAC-only reads) | | CloudFront distribution | HTTPS, gzip/br; serves the static SPA from S3 (the app calls the API directly, cross-origin) | | CloudFront Function (viewer request) | SPA routing: rewrites extensionless paths to `/index.html` (scoped to the S3 behavior, so it never touches `/api`) | | IAM role `githubdeploy-shoc-frontend-new-dev` | assumed by GitHub Actions via OIDC, scoped to `repo:Sea-Haven-Industries/shoc-frontend-new:ref:refs/heads/dev` | The whole `cd-cdk.yaml` job runs as that role, so it holds: `sts:AssumeRole` on `cdk-hnb659fds-*` (for `cdk deploy`), `cloudformation:DescribeStacks` (cd-cdk's pre-flight/health-check + output reads), read/write on the bucket (`s3 sync`), and `cloudfront:CreateInvalidation` (cache bust). The OIDC **provider** is a singleton account resource — the stack only _imports_ it (created in step 2), so `cdk destroy` can't delete a resource shared by other roles. --- ## One-time setup (run by a human with admin AWS creds) ### 1. Authenticate to the AWS account ```bash aws configure # or: aws sso login --profile aws sts get-caller-identity # confirm the right account + region (us-east-1) ``` ### 2. Ensure the GitHub OIDC provider exists (once per account) ```bash aws iam list-open-id-connect-providers # If none ends in token.actions.githubusercontent.com, create it (thumbprint is # no longer required — AWS validates GitHub against its own trust store): aws iam create-open-id-connect-provider \ --url https://token.actions.githubusercontent.com \ --client-id-list sts.amazonaws.com ``` ### 3. CDK bootstrap (once per account/region) ```bash cd infra/cdk npm ci npx cdk bootstrap aws:///us-east-1 ``` ### 4. Domain, cert, and API URL (already wired for dev) Domain/cert/zone are set in `cdk.json` context (account `396287094661`): | Context key | Value | | --------------------------------- | ------------------------------------------------------------ | | `domainNames` | `dev.seahaven.com` | | `certificateArn` | `…:certificate/2b78e74f-…` (ACM `*.seahaven.com`, us-east-1) | | `hostedZoneId` / `hostedZoneName` | `Z07671212N75U4YLPWZR8` / `dev.seahaven.com` | The stack creates the apex A/AAAA alias in the hosted zone (in this account, delegated from the parent `seahaven.com` zone). The **API URL is not infra** — it's `VITE_API_URL` in `.env.production` (`https://api.dev.seahaven.com/api`), baked into the build. Per-environment; override for staging/prod. ### 5. First deploy (locally, with admin creds) The deploy role doesn't exist until the first `cdk deploy`, so bootstrap it locally. This provisions infra + the role: ```bash cd infra/cdk npx cdk deploy ``` Note the `DeployRoleArn` output. Then push the first content (or just push to `dev` and let CI do everything from here on): ```bash # from repo root, optional manual first content publish: STACK_NAME=shoc-frontend-dev AWS_REGION=us-east-1 bash scripts/deploy-web.sh ``` ### 6. Set the one GitHub secret `cd-cdk.yaml` takes the role ARN as a **secret** (not a variable): ```bash REPO=Sea-Haven-Industries/shoc-frontend-new gh secret set AWS_DEPLOY_ROLE_ARN --repo "$REPO" \ --body "arn:aws:iam:::role/githubdeploy-shoc-frontend-new-dev" ``` (Or **Settings → Secrets and variables → Actions → Secrets**.) ### 7. From now on: push to `dev` ```bash git push origin dev ``` `ci.yml` runs the quality gates and `deploy.yml` calls `cd-cdk.yaml`, which runs `cdk deploy` then `scripts/deploy-web.sh`. Watch the **Actions** tab, then open the `SiteUrl` output. > First-run verification: this first push is what actually exercises the role's > permissions and the OIDC trust through the reusable workflow (the local > bootstrap used admin creds and tested none of that). Watch for > credential/OIDC errors and a green post-deploy step. --- ## Adding staging / prod later Separate accounts: deploy this stack there with per-env `domainNames`, `certificateArn`, `hostedZoneId`/`hostedZoneName` context; set that repo's `AWS_DEPLOY_ROLE_ARN` secret; and add a job to `deploy.yml`. Because the SPA calls the API directly at an absolute URL, **`VITE_API_URL` is baked into `vite build`** — so each environment needs its own build with its own API host (e.g. `https://api.staging.seahaven.com/api`). Set it per environment in the deploy job (e.g. export `VITE_API_URL` before the build step) rather than relying on the committed `.env.production` (which carries the dev value). The backend must also allow CORS from each frontend origin. ## Notes - **Teardown:** `npx cdk destroy`. The bucket uses `RemovalPolicy.DESTROY` + `autoDeleteObjects` (dev artifacts are reproducible) — change this for prod. - **CI and CD both fire on push to `dev`** in parallel; a red-CI commit still deploys (matches the org's push-time-CD model). Gating deploy on CI is a follow-up, not part of enabling CICD. - **npm is pinned to v11.16.0**; the committed `package-lock.json` uses lockfileVersion 3, matching the Node 24 / npm 11 CI environment.