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,66 @@
# Code Generation Summary — U6 Deploy Workflow
**Date**: 2026-07-28
## Files Created
- `.gitea/workflows/deploy-scp.yaml` — reusable `workflow_call` deploy workflow (C-11)
## What It Implements
A single `deploy` job with 8 shell steps, executed in order: compute a release timestamp,
download the build artifact, back up the database (production only, conditional on
`run_db_backup`), upload to a fresh `releases/{timestamp}/` directory over SCP, link the persistent
`shared/wwwroot-web/` into that release, atomically switch `current` and restart the
`systemctl --user` service, verify `/health` with a bounded retry loop, and — only if that check
passed — prune releases beyond the retention count of 2.
Every design decision here traces back to
`construction/u6-deploy-workflow/infrastructure-design/infrastructure-design.md`; this file does not
repeat the rationale, only the resulting code.
## Interface (shared with U5)
| Input | Required | Default |
|---|---|---|
| `artifact_name` | yes | — |
| `environment` | yes | — |
| `deploy_path` | yes | — |
| `service_name` | yes | — |
| `health_check_url` | yes | — |
| `run_db_backup` | no | `false` |
| `transport` | no | `scp` |
U5 (Code Generation next) must call this workflow with `secrets: inherit` and supply all five
required inputs from Gitea variables, per `infrastructure-design.md` § 6.
## Verification (Step 13.5 — this unit's own check)
No C# or frontend test project applies to `.gitea/workflows/` (pipeline configuration, not an
application project — same category exception as the Slave API having none, per CLAUDE.md).
Verification performed:
- **YAML syntax**: parsed successfully with PyYAML. The parser resolves the unquoted `on:` key to
the boolean `True` under YAML 1.1's implicit-boolean rules — this is a PyYAML quirk, not a defect;
the reference project's own working `deploy.yaml` (`K:\Development\SlpSoftware\Projects\SlpSoftware`)
parses identically, and Gitea/GitHub Actions' own workflow parsers treat `on` as a literal key.
- **Structural check**: confirmed 7 `workflow_call` inputs (5 required, 2 optional with the correct
defaults `run_db_backup: false` and `transport: scp`), 1 job (`deploy`), 8 steps — matching the
code generation plan exactly.
- **Logical review**: step ordering matches `infrastructure-design.md` § 4 (backup → upload → link
→ switch/restart → verify → prune-if-passed); no `sudo` used anywhere (Q4 = C); no container-based
actions (D-05).
Real execution — an actual Pi, live SSH credentials, and a real database — is out of scope until
Operations, per U6's Definition of Done in `unit-of-work.md`. That is not a gap in this unit; it is
the documented boundary between Construction and Operations for this specific unit.
## Deferred to Operations (not built here, by design)
- `~/scripts/backup-slpmodularcms-db.sh` on the host (referenced, not created — a host-side script
is out of this repository's scope)
- `loginctl enable-linger` for the deploy user (**INFRA-U6-01**, carried from Infrastructure Design)
- Actual values for `DEPLOY_PATH_*`, `SERVICE_NAME_*`, `HEALTH_CHECK_URL_*` Gitea variables and the
`PI_MAIN_*` secrets
- nginx routing configuration (already exists per the Infrastructure Design's Q10 answer; not part
of this repository)