Deployment setup - the parts the workflow deliberately left out

Host setup checklist, real domains and ports, the database backup
script the deploy workflow only ever invokes, and a rollback plan.
Also drafted the FTPS switch procedure for whenever production moves
off the Pi, with a note that shared hosting is likely IIS-based -
that's a bigger change than swapping the transport.
This commit is contained in:
2026-07-28 19:07:26 +02:00
parent 9ab30fe2a6
commit cf80f827ae
6 changed files with 550 additions and 0 deletions
@@ -0,0 +1,65 @@
# Rollback Plan
**Mechanism** (D-26, `infrastructure-design.md` § 34): two releases are always retained —
`current` plus exactly one previous. Pruning only ever runs after a **passing** health check, so a
failed deploy never leaves fewer than two releases on disk.
## When to Roll Back
- The deploy workflow's health check failed (job already shows red; `current` was left pointing at
the new, unhealthy release — no automatic rollback, per `infrastructure-design.md` § 4)
- The deploy succeeded and passed its health check, but a defect surfaces afterward that only shows
up under real traffic
## Fast Rollback (Previous Release Still on Disk) — the Common Case
No rebuild needed. Over SSH, on the Pi:
```bash
cd ~/apps/slpmodularcms-<env>/releases
ls -1t # confirm which directory is the previous, working release
ln -sfn ~/apps/slpmodularcms-<env>/releases/<previous-timestamp> ~/apps/slpmodularcms-<env>/current
systemctl --user restart slpmodularcms-<env>.service
curl -f https://<env-domain>/health # confirm the rollback itself is healthy
```
This is the same atomic-switch primitive the deploy workflow itself uses — pointing it backward
instead of forward. `wwwroot/web/` is untouched either way, since it was never part of the switched
directory to begin with (FR-08).
## Rebuild-and-Redeploy Rollback (Older Than One Release Back)
If the defect predates the retained previous release, redeploy an **earlier commit** through the
normal pipeline:
1. `git revert` or check out the last-known-good commit on a branch
2. Push to `master` (test) or run `workflow_dispatch` with `deploy_production: true` (production) —
the same gates and deploy sequence run as any other deploy
3. This only works because migrations are required to be forward-compatible and non-destructive
(D-26) — redeploying an older commit's code against a database that has since had newer
migrations applied must not break. If a migration since the target commit **was** destructive,
this path is not safe and the database backup (§ below) is the actual recovery route instead
## Database Rollback
A backup is taken before every **production** deploy (`operations/deployment/deployment-instructions.md`
§ 4, invoked by `deploy-scp.yaml` when `run_db_backup: true`). To restore:
```bash
sqlcmd -S <server> -U <user> -P <password> -C -Q \
"RESTORE DATABASE [SlpModularCmsProduction] FROM DISK = N'<path-to-backup>.bak' WITH REPLACE"
```
Restoring a database backup and rolling back the application release are **independent actions**
decide based on the actual failure whether one, the other, or both are needed. Rolling back the app
without restoring the database is usually sufficient (migrations are non-destructive by design);
restoring the database without rolling back the app should be rare and deliberate.
## What Is Never Part of a Rollback
- `wwwroot/web/` (the customer's website) — structurally outside every release directory; no
rollback action should ever touch it
- The Data Protection key ring — lives in the database, not the release directory; rolling back the
app release does not affect it
- Gitea Actions variables/secrets — these describe the *target*, not a specific release; nothing
about them changes during a rollback