diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/AMPLIFY_DEPLOY.md b/AMPLIFY_DEPLOY.md new file mode 100644 index 0000000..11f6df5 --- /dev/null +++ b/AMPLIFY_DEPLOY.md @@ -0,0 +1,171 @@ +# Deploying to AWS Amplify Hosting + +First-time walkthrough for shipping this app on AWS Amplify Hosting. Amplify +auto-detects Next.js, runs the build, and routes SSR pages + API routes +through managed Lambda behind a CloudFront CDN. The `amplify.yml` and +`.nvmrc` in this repo are the only pieces Amplify needs from the codebase. + +## Before you start + +- An AWS account with billing enabled. Amplify Hosting has a free tier + (1,000 build minutes + 15 GB served + 100 GB-hours of SSR Lambda per + month for the first year) — typical traffic for this app stays comfortably + inside it. +- GitHub access to `joelbrock/WebForm-mw`. Amplify connects via OAuth or a + GitHub App; either works. +- The three required CiviCRM env vars in hand: `CIVI_BASE_URL`, + `CIVI_API_KEY`, `CIVI_SITE_KEY`. Plus the optional `CIVI_HTTP_AUTH_*` + pair if CiviCRM sits behind webserver-level Basic Auth. + +## Step 1 — Create the Amplify app + +1. Open the Amplify console: + - Pick a region near you / near CiviCRM. `us-east-1` (N. Virginia) is + the default and has the lowest cold-start latency for most setups. +2. Click **Create new app** → **Host web app**. +3. Choose **GitHub** as the source. Authorize Amplify if prompted. + The first time, AWS will install a GitHub App on your account; grant + it access just to `joelbrock/WebForm-mw` (not "all repos"). +4. Pick the repo `joelbrock/WebForm-mw` and the `main` branch. +5. **App name:** something like `coop-checkin` (this becomes the + `*.amplifyapp.com` subdomain). +6. Amplify reads `amplify.yml` automatically; you do not need to edit + the build spec on this screen. Click **Next**. + +## Step 2 — Environment variables + +On the environment-variables screen, add the following. Required ones first: + +| Variable | Value | +|---|---| +| `CIVI_BASE_URL` | e.g. `https://crm.fci.coop` | +| `CIVI_API_KEY` | mark as **Secret** (eye icon on the value field) | +| `CIVI_SITE_KEY` | mark as **Secret** | +| `NODE_ENV` | `production` | + +Optional, only if relevant: + +| Variable | When | +|---|---| +| `CIVI_HTTP_AUTH_USER` | CiviCRM has webserver-level Basic Auth in front of it | +| `CIVI_HTTP_AUTH_PASS` | same — mark **Secret** | +| `HEALTH_TOKEN` | gates `/api/health` in production — mark **Secret** | +| `PREVIEW_ADMIN_TOKEN` | gates `/api/preview-link` — mark **Secret** | + +"Secret" values are encrypted at rest in AWS Systems Manager Parameter +Store and only injected into the build/runtime environment. They are not +visible in logs or to anyone without `amplify:*` IAM permissions. + +Click **Next**, review, **Save and deploy**. + +## Step 3 — First build + +The first build takes about 4–6 minutes (later builds are 2–3 with cache). +You can watch progress in the **Hosting environments** view. When it +completes, three things are confirmed: + +- The build succeeded → `next build` produced `.next/`. +- SSR functions deployed → API routes and dynamic pages have a Lambda + function behind them. +- The app is reachable at `https://main..amplifyapp.com`. + +Smoke-test the deploy: + +``` +https://main..amplifyapp.com/healthz +``` + +Should return `200 OK` (lightweight platform check, doesn't hit CiviCRM). + +If you set `HEALTH_TOKEN`, the richer diagnostic at +`/api/health?token=` will probe the CiviCRM connection itself — +useful to confirm env vars are wired correctly. + +## Step 4 — Custom domain (optional) + +Amplify can attach a custom domain (e.g. `check-in.fci.coop`) with +auto-issued TLS in a few minutes: + +1. In the app, **Hosting** → **Custom domains** → **Add domain**. +2. Enter the apex (`fci.coop`) or a subdomain. +3. Amplify suggests DNS records (CNAME / ALIAS). Add them at your DNS + provider (Cloudflare, Route53, Namecheap, etc.). +4. Wait 5–15 minutes for validation and certificate issuance. + +The `*.amplifyapp.com` URL keeps working alongside the custom domain. + +## Step 5 — Per-PR previews (optional, recommended) + +Amplify can build a preview environment for every pull request: + +- **Hosting** → **Previews** → enable for the `joelbrock/WebForm-mw` repo. +- PRs get their own `https://pr-..amplifyapp.com` URL, + posted as a comment on the PR. +- Previews inherit the main branch's environment variables unless you + override on the preview branch. + +Useful for reviewing form changes against live CiviCRM DEV before merging. + +## What it'll cost + +Realistic estimate for this app's expected traffic (a few hundred +form/report loads per month, very modest API throughput): + +- **Hosting (CDN + static)**: pennies. Free tier covers it. +- **SSR Lambda**: ~$0.05–$0.50/month depending on traffic. Each form load + triggers a couple of API calls into CiviCRM via Lambda; each call is a + short-lived invocation. +- **Build minutes**: free tier covers up to ~300 small builds/month. + +Expect **<$5/month** total at launch traffic. If usage grows +significantly, the costs scale roughly linearly with Lambda invocations. + +## Things to know going in + +- **Cold starts.** First request after a quiet period adds 500–1500ms of + Lambda init. Subsequent requests reuse the warm container. For an + internal-use form this is fine; users see the loading state during + init. +- **Outbound IPs are not static.** Amplify SSR Lambda functions egress + through AWS-managed IPs that change. If CiviCRM has an IP allowlist + on its API, this won't work out of the box — you'd need a VPC + NAT + Gateway + Elastic IP setup (significantly more complex). Most + CiviCRM auth uses API keys, not IP allowlisting, so this usually + isn't an issue. +- **No Render-style background workers.** Amplify is request/response + only. Anything cron-shaped needs EventBridge + a separate Lambda. + The current app doesn't have background jobs, so this doesn't apply. +- **Logs live in CloudWatch.** Each SSR function has its own log group; + click into the function from the Amplify app view to jump to logs. + Retention defaults to "never expire" — switch to 30 days unless you + need more. +- **Secrets rotate manually.** No auto-rotation. To rotate + `CIVI_API_KEY`, update it in Amplify env vars → trigger a redeploy. +- **`render.yaml` is now informational.** It's still in the repo for the + Render path; Amplify ignores it. If you commit fully to Amplify and + abandon Render, the `render.yaml` and the Render section of + `DEPLOYMENT.md` can be removed. + +## When something breaks + +- **Build fails on `npm ci`** → usually a Node version mismatch. The + `.nvmrc` pins Node 20; if Amplify's build image doesn't have it, the + `amplify.yml` `nvm install` falls back. Open the build log, search + for "Node version". +- **Build fails on `next build`** → typecheck or lint error. Reproduce + locally with `npm run build`. +- **Site builds but routes 503** → SSR Lambda misconfig. Check the + function's CloudWatch logs for the actual error. Most common cause: + missing required env var (the app refuses to boot in production stub + mode and the `lib/env.ts` validator throws). +- **CiviCRM calls fail with 401** → API key or Basic Auth credentials + wrong. Re-check `CIVI_API_KEY` and `CIVI_HTTP_AUTH_*` in the Amplify + env-vars panel; redeploy after changes. + +## After the first successful deploy + +- Bookmark the Amplify app URL and the CloudWatch log group. +- Run the email/link smoke test from `EMAIL_DELIVERY.md` against the + Amplify URL to confirm `/api/preview-link` produces working tokenized + URLs end-to-end. +- Update any docs / staff runbooks pointing at the old Render URL. diff --git a/README.md b/README.md index 5ec3c24..a7fab2a 100644 --- a/README.md +++ b/README.md @@ -105,11 +105,11 @@ works while in stub mode. The report is at `/report?cid=1&cs=anything`. ## Deploy -The repo ships with `render.yaml` for [Render](https://render.com) and a -detailed `DEPLOYMENT.md` covering Render specifically. The app is a +The repo ships with `amplify.yml` for [AWS Amplify Hosting](https://aws.amazon.com/amplify/) +(see `AMPLIFY_DEPLOY.md` for a first-time walkthrough) and `render.yaml` +for [Render](https://render.com) (see `DEPLOYMENT.md`). The app is a standard Next.js 16 App Router project and runs anywhere Node 20+ can -run `next start` — Vercel, AWS Amplify Hosting, App Runner, Fly, -self-hosted, etc. +run `next start` — Vercel, App Runner, Fly, self-hosted, etc. Build + start: diff --git a/amplify.yml b/amplify.yml new file mode 100644 index 0000000..1615c3d --- /dev/null +++ b/amplify.yml @@ -0,0 +1,20 @@ +version: 1 +applications: + - frontend: + phases: + preBuild: + commands: + - nvm use $(cat .nvmrc) || nvm install $(cat .nvmrc) + - npm ci --cache .npm --prefer-offline + build: + commands: + - npm run build + artifacts: + baseDirectory: .next + files: + - '**/*' + cache: + paths: + - node_modules/**/* + - .next/cache/**/* + - .npm/**/*