Skip to content

feat(deploy): one-command deploy from a reset lab, automatic state recovery, honest make down - #16

Merged
Vnntnn merged 3 commits into
mainfrom
feat/one-command-deploy
Oct 26, 2026
Merged

Vnntnn merged 3 commits into
mainfrom
feat/one-command-deploy

Conversation

@Vnntnn

@Vnntnn Vnntnn commented Oct 26, 2026

Copy link
Copy Markdown
Owner

After a lab reset the only steps left are pasting the new credentials into infra/aws.env and make deploy. Also fixes the failure mode seen when the lab cut the credentials half way through make down.

What changes

  • make deploy = scripts/bootstrap.sh: preflight → Terraform state → permanent layer → DNS delegation + wait for the ACM certificate → ECR image mirrors → platform → application (scripts/deploy.sh, so the payment release gate still applies) → smoke test. On a running platform it is a redeploy. make redeploy keeps the old app-only behaviour (what CI runs).
  • scripts/delegate-dns.sh: a reset replaces the Route 53 zone, so the Cloudflare NS records go stale. With the optional CLOUDFLARE_API_TOKEN in infra/aws.env it replaces them through the API (token sent on stdin, never printed); without it, it prints the 4 names and waits on a terminal, or exits 3.
  • scripts/recover-state.sh (run by tf-init.sh, so make up / make deploy, and by make down): removes a stale state lock (no terraform process here, older than 5 min) and pushes errored.tfstate when it is this account's and newer than the remote state of the same lineage; archives it otherwise, never deletes; a lineage mismatch or failed push stops with exit 1.
  • make down no longer lies: it exited 0 after the lab cut the credentials mid-destroy (it never checked the apply, and printed the AccessDenied errors of its own verification as if they were lists). It now exits non-zero unless the apply succeeded and every list is empty; a list it cannot read is UNVERIFIED, never empty.
  • README (rewritten on main) updated for the production layout: overview diagram + the embedded as-built image, components, Terraform layout, the one-command lifecycle, lab limits, rules. The duplicate Deploy section is removed. CLAUDE.md gets "After a lab reset".

Tests (all run in CI, no AWS / network)

recover-state.test.sh (11 cases), teardown.test.sh (7 checks; the old script exits 0 under the same failure, verified), delegate-dns.test.sh (10 checks against a local Cloudflare mock). Also green on the merged tree: lib.test.sh, release.test.sh (25), scan-secrets and its test, terraform validate, helm template for AWS and k3d.

Not verified

The from-zero path as a whole needs a real lab reset. Not exercised against real S3: the lock removal and the state push (the credentials were dead when this was written); the logic is covered by the stand-in tests. Not tried against real Cloudflare: no token yet.

Notes

  • Conflicts with main resolved by keeping both sides (scripts/lib.sh, ci.yml, CLAUDE.md).
  • Open at the time of writing: the platform is half destroyed and still billing (the lab cut the credentials during make down). The next session runs make down from a checkout of this branch: its tf-init recovers errored.tfstate automatically.

🤖 Generated with Claude Code

Vnntnn and others added 3 commits October 26, 2026 03:59
… reset lab

`make deploy` now runs scripts/bootstrap.sh: preflight, Terraform state, the permanent layer,
DNS delegation + certificate, the ECR image mirrors, the platform, the application and a
smoke test. On a running platform it is a redeploy. `make redeploy` keeps the old app-only
behaviour (scripts/deploy.sh, what CI runs).

- scripts/delegate-dns.sh: makes the Cloudflare NS records for the delegated subdomain match the
  new Route 53 zone (a lab reset changes its name servers). With CLOUDFLARE_API_TOKEN it uses the
  API (token sent on stdin, never printed); otherwise it prints the 4 names and waits on a
  terminal, or exits 3. Tested against a local mock (scripts/delegate-dns.test.sh).
- bootstrap trusts the account, not Terraform's state, for "is the platform up"; recreates the
  uploads bucket (a local-exec that never re-runs) when the state remembers a bucket that is
  gone; runs sync-secrets only on a fresh clone (it deletes the tfvars of groups .env leaves
  empty, which would delete those Secrets Manager secrets).
- docs: README "after a lab reset", CLAUDE.md "After a lab reset", aws.env.example gets the
  optional CLOUDFLARE_API_TOKEN.

Verified: the delegation tests, bash -n, and a full run of `make deploy` against the live
platform (the already-up path). The from-zero path as a whole needs a real lab reset.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…down no longer lies

The lab cut the credentials half way through `make down` (explicit deny voc-cancel-cred). Terraform
saved errored.tfstate and left a stale state lock, and teardown.sh still exited 0 because it never
checked the apply and printed the AccessDenied errors of its own verification as if they were
lists.

- scripts/recover-state.sh, run by tf-init.sh (make up, make deploy) and by make down: removes a
  stale lock (no terraform process here, older than 5 min) and pushes errored.tfstate when it is
  this account's and newer than the remote state of the same lineage; archives it (*.foreign-,
  *.stale-) otherwise, never deletes; a lineage mismatch or a failed push stops with exit 1.
- teardown.sh: runs tf-init (so the recovery) first; exits non-zero unless the apply succeeded and
  every list is empty. lib.sh verify_empty reports a list it cannot read as UNVERIFIED, never as
  empty.
- tests with stand-ins for aws, terraform and Cloudflare, run in CI: recover-state (11 cases),
  teardown (7 checks; the old script exits 0 under the same failure, checked), delegate-dns.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Conflicts resolved by keeping both sides:
- scripts/lib.sh: main's payment release gate and the teardown verify_empty are independent blocks.
- .github/workflows/ci.yml: main's release and secret-scan steps plus the session-script tests.
- CLAUDE.md: the "After a lab reset" section replaces the old manual blocker 4.

README.md (rewritten on main) is updated for the production layout: overview diagram and
embedded as-built image, component table (edge/egress node groups, RDS Proxy, ElastiCache,
ALB + WAF, NAT, endpoints, CloudWatch), Terraform layout, the one-command session lifecycle and
the recovery after an interrupted make down, the lab limits, the rules; the duplicate Deploy
section is removed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Vnntnn
Vnntnn merged commit c32e51a into main Oct 26, 2026
9 checks passed
suwizx pushed a commit to suwizx/Cloud_Computing2026-Project that referenced this pull request Oct 27, 2026
…on/off

The same tests as the 2026-10-25 run, on the layout merged in PR Vnntnn#15/Vnntnn#16 (ElastiCache, RDS Proxy,
ALB + WAF, private tiers), measured 2026-10-26:

- lifecycle timings: make deploy from main took 32 min 45 s (per step and per resource), the old
  layout's make down 12 min 21 s, Redis off/on 1 min 39 s / 22 s;
- Redis on vs off, in the cluster at 200 req/s: event detail p50 2.4 ms vs 7.3 ms and a ~33 s stall
  with 6.4% failures when the pods start without Redis; the session path fails 100% (registration
  goes down); the Postgres-only control did not move;
- all services through the public host: 87,725 requests, 0 failed, p95 376 ms overall;
- what is not comparable with the earlier results, one unexplained tail, and what was not measured.

RESULTS.md now points to the new file instead of saying the layout was untested.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

1 participant