Skip to content

Add guided AWS self-hosting installer - #432

Closed
dhempler wants to merge 9 commits into
ChurchApps:mainfrom
dhempler:codex/aws-self-hosting-installer-pr
Closed

Add guided AWS self-hosting installer#432
dhempler wants to merge 9 commits into
ChurchApps:mainfrom
dhempler:codex/aws-self-hosting-installer-pr

Conversation

@dhempler

@dhempler dhempler commented Jul 24, 2026

Copy link
Copy Markdown

Summary

  • add a guided AWS self-hosting installer: a single yarn installer:run entrypoint that walks a non-technical operator from empty AWS account to a verified, logged-in B1Admin install (evidence-file state machine, interactive customer interview, approval gates before anything state-changing)
  • add CloudFormation templates (bootstrap, backend, frontend split stacks), scoped IAM policy samples, and a GitHub Actions OIDC deployment workflow for private AWS installs
  • add deploy/packaging/migration scripts wired into package.json (split-stack path, RDS Data API migrations), plus day-2 documentation: infrastructure/environments/start-here.md (setup) and operations.md (updates, backups, secrets, cost control, including a ~$80-90/mo default-stack estimate)
  • installer can create the IAM roles, the private deployment repo, and drive the second "adopt frontend origin" deploy itself, each behind a y/N approval gate
  • security hardening included: private asset bucket behind CloudFront OAC, CORS honors the configured origin, secrets kept off argv/logs, project-scoped IAM samples

Scope notes for reviewers

  • The PR deliberately ships one deployment path (guided installer → GitHub Actions → split-stack, Data API migrations). Earlier drafts also carried a parallel full-stack mode, a direct migration runner, and a second setup wizard; those were removed to keep the surface reviewable.
  • Companion branch: the smoke-test harness (scripts/smoke-aws-tooling.mjs, ~8k lines, plus the 40 example output fixtures it checks) lives in follow-up PR Add AWS tooling smoke harness dhempler/B1Admin-source-fork#1 (branch codex/aws-tooling-smoke-harness, stacked on this branch). It was branched from this PR's exact code state and passes against it.

Testing

  • Full smoke suite (companion branch): node scripts/smoke-aws-tooling.mjs — every script exercised against stubbed aws/gh binaries; passes clean. Run it with invalid AWS credentials in the environment so its few live-CLI scenarios cannot touch a real account.
  • node --check across all scripts; CloudFormation templates parse; installer:commit secret-guard and HTTP smoke fallback verified against local fixtures.

This is opened as a draft for maintainer review before it is considered ready to merge.

@dhempler
dhempler force-pushed the codex/aws-self-hosting-installer-pr branch from 61b5740 to 24699e5 Compare July 24, 2026 15:28
@dhempler
dhempler marked this pull request as ready for review July 24, 2026 15:32
dlt074 and others added 7 commits August 12, 2026 13:40
- Workflow: replace npm ci / npm cache with corepack+yarn (repo is Yarn Berry;
  npm ci failed on every run)
- upload-backend-artifact: return the S3 object VersionId so redeploys pin
  LambdaCodeS3ObjectVersion and Lambda code updates are not silently skipped
- publish-lambda-layer: accept --content-bucket/--content-key/
  --content-object-version (the flags deploy-aws and deploy-backend already
  pass) as an S3 alternative to --source-file
- backend-api.yaml: add DependsOn (DatabaseInstanceWriter, MigrationLogGroup)
  to MigrationRunner so migrations cannot fire before the DB instance or log
  group exist
- validate-aws-deploy: fix undefined frontendStackName reference
  (ReferenceError on the split-stack publish-only path)
- deploy-aws: treat --run-api-migrations=false / --run-bootstrap-admin=false
  as disabled (string "false" was truthy)
- IAM samples: scope role management, PassRole, and Secrets Manager access to
  project-prefixed ARNs; split CreateServiceLinkedRole into its own correctly
  conditioned statement; scope the OIDC role's secret read, SSM writes,
  rds-data, and layer publish to project resources

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From a three-part usability audit (novice doc walkthrough, wizard UX
review, prerequisite-burden inventory) aimed at letting a non-technical
church admin complete the install:

- Yarn/Corepack: doctor now checks for yarn and suggests `corepack
  enable`; start-here quick checks and local-runtime cover yarn with an
  explicit warning against `npm install -g yarn` (previously yarn was
  required by every command but never checked or explained)
- Cost and time: start-here's "What Costs Money?" now leads with a
  dollar estimate (~$80-90/mo prod-only, itemized) plus a 2-4 hour time
  expectation and a billing-alarm suggestion
- Solo-admin framing: "Who Does What" and aws-account-access now say
  plainly that in a small church all three roles are usually the same
  person and the operator can run the IAM commands themselves
- infrastructure/README.md gets a banner routing novices to
  start-here.md (previously never linked)
- Tool install links for Node, Git, GitHub CLI, and AWS CLI
- first-rollout-checklist defines $DEPLOY_REPO/$DEPLOY_ENV_DIR before
  using them; deployment-repository clone example uses HTTPS instead of
  SSH; root README dev steps use yarn (npm install is rejected by
  preinstall)
- installer:customer-values generates a temporary admin password on
  Enter (unambiguous alphabet, xxxx-xxxx-xxxx) instead of requiring the
  user to invent one
- installer:doctor reads region/account-id/repo from customer-values
  (not just env vars) and its next steps point at the interview instead
  of advising env var exports

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second tier of the non-technical-installer work. The guided runner can
now drive the install from scaffold to report without ejecting the user
into copy-paste shell commands:

- installer:run executes yarn commands (previously it only recognized
  npm/cp, so every generated next-command fell back to "run this
  manually"); failures now surface a plain-English hint (AWS sign-in,
  GitHub sign-in, missing tool, network) via a shared explainFailure
  helper instead of raw stderr, and crashes no longer print stack traces
- installer:aws-roles --apply=true creates the GitHub OIDC provider and
  both IAM roles with the operator's own AWS sign-in (idempotent:
  refreshes trust policy if a role exists), writes apply-result.json
  evidence, and the state machine gains an "AWS IAM roles created" step
  gated behind the runner's y/N approval; the admin-handoff document
  remains the alternative for orgs with a separate AWS admin
- new installer:commit syncs the private deployment repo: initializes
  git, creates the private GitHub repository if missing, stages ONLY the
  safe file allowlist, refuses to run if the .gitignore protecting
  secrets is absent, commits, and pushes; the state machine gains a
  "Private repository synced" step so manual git ceremony disappears
- adopt-frontend-origin archives the stale deployment summary and deploy
  dispatch evidence, so the runner automatically walks the second prod
  pass (commit -> redeploy -> observe) instead of leaving login broken
  until the user rereads the docs
- environment-setup-wizard announces itself as an advanced tool and
  points to installer:run; customer-values failures keep answers and say
  so instead of dumping a stack trace
- docs updated to match: no manual git commands on the guided path, IAM
  self-service framing, automatic second-deploy explanation
- smoke walkthrough scenario extended to cover the IAM apply step, both
  private-repo sync points, and the post-adopt redeploy loop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third tier of the non-technical-installer work:

- backend-api.yaml: the CreateNatGateway=false mode gains Polly and
  execute-api interface endpoints (the latter only when the WebSocket
  API is enabled), so text-to-speech and socket pushes work without NAT.
  The NAT default stays "true" deliberately: outbound email via the SES
  API and third-party integrations (payment gateways, Mautic, YouTube)
  have no VPC-endpoint equivalent and would silently break. The
  trade-off is now documented instead of hidden.
- New infrastructure/environments/operations.md day-2 runbook: routine
  updates, version pinning (pin both repos to a release tag instead of
  dual `main`), Aurora backup/restore guidance, secret rotation with the
  applied-at-deploy-time caveat, billing alarms, final-snapshot cleanup
  after resets, NAT cost trade-off, and where logs live. Linked from
  start-here (TOC, update section, cost section) and the environments
  README.
- installer:browser-smoke gains --mode=auto|browser|http. Auto falls
  back to plain HTTP checks (home page + login page reachable and
  app-shaped) when Playwright or its browser is not installed, so the
  smoke step no longer forces a Chromium download onto the operator's
  machine; browser mode reports how to fix the missing browser instead
  of a raw error. Evidence records which mode ran. Verified against a
  local HTTP server (success and closed-port failure paths).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the round-1 security review findings:

- ManagedAssetBucket is no longer world-readable. Public access is fully
  blocked and uploaded assets are served through a new CloudFront
  distribution with Origin Access Control (same pattern as the frontend
  site); CONTENT_ROOT / CONTENT_ROOT_URL env vars and the ContentRootUrl
  stack output now point at the distribution domain, and the bucket's
  CORS rule is scoped to the CorsOrigin parameter. New
  AssetDistributionId output. Externally supplied AssetBucketName
  deployments are unchanged.
- API Gateway CORS now honors the CorsOrigin parameter instead of a
  hardcoded "*" (the parameter default remains "*", tightened
  automatically when the installer adopts the frontend origin).
- sync-legacy-ssm-parameters no longer leaks secrets: parameter values
  go to AWS via a 0600 temp --cli-input-json file instead of argv, the
  echoed command hides the value, JSON/text output lists parameter names
  only, --output=json actually syncs instead of silently returning
  early, and connection-string credentials are URL-encoded. Sample and
  smoke contract updated to assert values never appear.
- sync-app-config-secret passes the secret to the AWS CLI as
  --secret-string file://<path> instead of putting the full secret JSON
  on the process argv.
- deploy-aws/deploy-backend/deploy-full-stack mask password and
  secret-string values in their echoed command lines.
- operations.md gains a security-posture section, including the honest
  limitation that secrets reach Lambda as env vars resolved at deploy
  time (runtime fetching would require upstream Api changes) and what
  that implies for AWS account access.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewer-driven diet. The PR shipped three overlapping ways to deploy
the same stack; this commits to the single golden path the guided
installer uses (split-stack via GitHub Actions) and removes the
parallel alternatives:

- Remove the full-stack deployment mode: deploy-full-stack.mjs,
  full-stack.yaml, its 8 output/parameter samples, and the package.json
  entry. validate:aws-deploy now defaults to --mode=split-stack and
  exits with a clear message if --mode=full-stack is requested.
- Remove the "direct" API migration runner (run-api-migrations.mjs and
  its sample): migrations run through the RDS Data API everywhere.
  deploy-backend/deploy-aws default to data-api and explain the removal
  if direct is requested; run:api-migrations now maps to the data-api
  script.
- Remove tools superseded by the guided installer:
  environment-setup-wizard.mjs (replaced by installer:customer-values)
  and launch-staging.mjs (replaced by installer:run).
- Docs: deployment-workbook.md removed; infrastructure/README.md
  rewritten from a 1,186-line manual-flow reference into a ~55-line
  architecture overview that defers to start-here.md and operations.md.
- Smoke suite updated: ~32 scenarios and 19 helper functions for the
  removed tools deleted (916 lines), replaced by 4 scenarios asserting
  the new removal errors; script/template/sample check lists updated.
  Full suite passes.

Net: -5,200 lines, -15 files versus the previous branch state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
scripts/smoke-aws-tooling.mjs (7,943 lines) and the 40 example output
fixtures it checks against now live on the codex/aws-tooling-smoke-harness
branch, to be opened as a follow-up PR once the installer PR lands. This
keeps the reviewable surface of the main PR to the product itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From the adversarially verified code review of this branch:

- backend-api.yaml declares LambdaCodeS3ObjectVersion and
  MigrationCodeS3ObjectVersion and wires them into every Lambda Code
  block, so the deploy scripts' existing versionId plumbing now both
  passes CloudFormation validation and actually forces function updates
  when a new zip lands under the same S3 key
- OIDC deploy policy grants s3:GetObjectVersion (publish-layer-version
  with S3ObjectVersion reads the versioned object with the caller's
  credentials)
- deploy-aws getBooleanArgString honors space-separated values, so
  `--run-api-migrations false` no longer runs migrations
- sync-legacy-ssm --output=json defaults back to dry-run, restoring the
  side-effect-free preview contract; pass --dry-run=false to sync while
  emitting JSON (deploy-backend's invocation never passes json, so real
  deploys still sync)
- removed the execute-api VPC endpoint: API Gateway VPC endpoints do
  not support the WebSocket @connections API, so it could never work;
  docs now list WebSocket pushes among the NAT-required features
- staging deploy-split-stack.sh defaults API_MIGRATION_RUNNER=data-api;
  validate-aws-deploy defaults to data-api, rejects "direct" with the
  same removal message as the deploy scripts, and drops the deleted
  direct-runner checks
- installer-commit scans `git diff --cached` after staging and aborts
  (unstaging first) if any staged path looks like a secret, instead of
  trusting a five-path list; allowlist moved to installer-common as the
  shared DEPLOY_REPO_SAFE_PATHS
- installer-run spawns yarn/npm through a shell on Windows (cmd shims
  cannot be spawned directly), quoting args with spaces
- installer-start's synced check watches only the safe-path allowlist,
  so stray files like .DS_Store no longer wedge the runner in a commit
  loop; the scaffold .gitignore also ignores .DS_Store/Thumbs.db

Verified: full smoke harness green against this tree (fixture for the
api-migrations validation scenario updated on the harness branch for
the data-api default).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dhempler

Copy link
Copy Markdown
Author

Closing this out — the AWS self-hosting installer is being pulled into its own standalone repo so it can be maintained independently and evolve without needing to land here first. Thanks for the review time so far; will link the new repo if it's useful to point to from here.

@dhempler dhempler closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants