U6 — the deploy workflow itself

deploy-scp.yaml: download, back up the production database before
touching anything, upload into a timestamped release, symlink the
persistent website in, switch current and restart, verify /health
with retries, prune old releases only once that check passes. No
sudo, no container actions, one shared interface U5 will call next.
This commit is contained in:
2026-07-28 15:33:03 +02:00
parent 9a77eec0f1
commit bd2a963498
4 changed files with 355 additions and 0 deletions
@@ -0,0 +1,62 @@
# Code Generation Plan — U6 Deploy Workflow
## Unit Context
- **Unit**: U6 Deploy Workflow (Pipeline-type — YAML only, no C#/frontend code)
- **Component**: C-11 Deploy transport workflows (`.gitea/workflows/`)
- **Requirements**: FR-02, FR-03, FR-06, FR-08, FR-20
- **Depends on**: U1, U2, U5 (per `unit-of-work-dependency.md`) — U1/U2 are already committed
(Rounds 12); U5 does not exist yet, but U6 only needs to **expose** the interface U5 will call,
not the other way around, so generating U6 first is safe
- **Depended on by**: U5 (invokes this workflow), U7, Operations
- **Basis**: `construction/u6-deploy-workflow/infrastructure-design/infrastructure-design.md`
(approved) and `deployment-architecture.md`
- **No test project applies**: `.gitea/workflows/` is pipeline configuration, not an application
project — there is no C# test project for it, mirroring how the Slave API has none (CLAUDE.md).
Verification for this unit is YAML validity plus logical review; real execution requires the
actual Pi, SSH credentials and database, which is explicitly out of scope until Operations (per
U6's Definition of Done in `unit-of-work.md`).
## Steps
- [x] Step 1: Generate `.gitea/workflows/deploy-scp.yaml` — reusable `workflow_call` workflow
- `on.workflow_call.inputs`: `artifact_name`, `environment`, `deploy_path`, `service_name`,
`health_check_url` (all required strings), `run_db_backup` (optional boolean, default `false`),
`transport` (optional string, default `scp`)
- Single `deploy` job, plain shell steps only (D-05 — no container actions)
- Step: download artifact (`actions/download-artifact`)
- Step: production-only DB backup — `if: ${{ inputs.run_db_backup }}`, SSH into the host and
invoke `~/scripts/backup-slpmodularcms-db.sh` (script itself is an Operations/host-setup
artifact, not generated here)
- Step: `sshpass` + `scp` the artifact into `${{ inputs.deploy_path }}/releases/<timestamp>/`
(timestamp computed once, reused across steps via a job output — same pattern the reference
project uses for the `config` job)
- Step: ensure `${{ inputs.deploy_path }}/shared/wwwroot-web` exists (`mkdir -p`, idempotent),
remove the publish output's own `wwwroot/web`, symlink it to the shared directory
- Step: atomically switch `${{ inputs.deploy_path }}/current` to the new release (`ln -sfn`)
- Step: restart `systemctl --user restart ${{ inputs.service_name }}` over SSH
- Step: health check — retry loop, `curl -f` against `${{ inputs.health_check_url }}`, bounded
attempts with a short sleep between tries
- Step: prune releases beyond the retention count of 2, **conditional on the health check having
passed** (`if: success()` on the pruning step, following the preceding step's outcome)
- Inline comments explaining each non-obvious step (Podman container-action failure, why pruning
is conditional, why the timestamp is a job output), matching the reference project's comment
density for anything a future reader would otherwise have to re-derive
- [x] Step 2: Validate YAML
- Parse `deploy-scp.yaml` to confirm it is syntactically valid YAML and produces the exact
`workflow_call` input shape listed above (no test framework applies — see Unit Context)
- Result: valid; 7 inputs (5 required, 2 optional with correct defaults), 1 job, 8 steps —
matches plan exactly
- [x] Step 3: Documentation — `construction/u6-deploy-workflow/code/generation-summary.md`
- What was generated, key decisions carried from Infrastructure Design, what is explicitly
deferred to Operations (host script, real Pi verification, variable/secret runtime values)
## Story / Requirement Traceability
| Step | Covers |
|---|---|
| 1 | FR-02 (reusable workflow, minimum inputs + extensions), FR-03/FR-04 (environment-agnostic — U5 controls when each environment is called), FR-06 (atomic switch, restart), FR-08/ASM-01 (persistent website symlink), FR-20 (production DB backup) |
| 2 | Definition of Done: "workflow YAML valid" |
| 3 | Definition of Done: "deployment sequence documented step by step including the rollback path" (rollback path itself is documented in `infrastructure-design.md` § 4 and carried to Operations — this summary cross-references it, not duplicates it) |