# Deployment Plan **Date**: 2026-07-28 **Decided at**: Deployment Setup, per `deployment-setup-plan.md` (Q1 = A, Q2 = A) ## Method Gitea Actions CI/CD, already built in Construction: - `.gitea/workflows/continuous_integration.yaml` (U5) — six blocking gates, then a per-environment `dotnet publish`, then invokes the deploy workflow - `.gitea/workflows/deploy-scp.yaml` (U6) — reusable `workflow_call` workflow: backup (production only) → upload → link persistent website → atomic switch → restart → health check → prune This stage does not change that mechanism — it documents the operational side Construction deliberately left out: host setup, real domains, the database backup script, and the rollback procedure. ## Environments | Environment | Host | Domain | Trigger | |---|---|---|---| | Test | Raspberry Pi (`linux-arm64`) | `test.slpsoftware.nl` | Automatic on push to `master`, or any `workflow_dispatch` | | Production | Same Pi, separate directory | `slpsoftware.nl` | Only `workflow_dispatch` with `deploy_production = true` | Same physical host for both (per `infrastructure-design.md` § 1) — separated by directory, `systemd --user` unit, and local port, never by anything the workflow manages directly. **Account model (revised at Deployment Setup)**: the Pi's existing `webadmin` account is FileZilla/ SFTP-only, used by website-workspace authors uploading customer sites under `/mnt/storage1/www/html/` (`WEBSITE_WORKSPACE.md`'s role) — it cannot SSH in and stays that way. The deploy pipeline uses a **separate, generic, host-wide** SSH-capable account instead (`deployment-instructions.md` § 1.2) — not one created specifically for this project, since it may serve other projects' deploy pipelines on the same Pi too (reuse the reference project's existing account if it already has SSH access). Deploy paths live under that account's own home directory, namespaced per project, not under `html/` — this was a deliberate choice to avoid the CMS's release/current/shared structure interfering with the other websites hosted there. ## Rationale for What's Documented Here vs. Already Decided | Already decided (Construction) | Documented here (Operations) | |---|---| | Atomic switch mechanism, retention count, transport | Real domains, real ports | | `deploy-scp.yaml`'s step sequence | The one-time host setup that sequence assumes already exists | | That a DB backup step runs before production deploys | The actual backup script's content | | That `transport` is reserved for FTPS (D-02, NFR-09) | What actually changes operationally when that day comes | ## Automation Level Fully automated for test (push to `master` → live on `test.slpsoftware.nl` with no human step). Production requires an explicit, manual `workflow_dispatch` run with the flag checked — this manual trigger **is** the approval gate (D-09); no separate approval workflow step exists or is needed. ## Rollback Strategy Summarized here; full procedure in `rollback-plan.md`. Two releases are always retained (`infrastructure-design.md` § 3), so rolling back is a re-point-and-restart, never a rebuild, for the most recent deploy. Migrations are required to be forward-compatible and non-destructive (D-26), so redeploying an older commit remains valid further back than just one release. ## Secrets and Configuration Per D-16, runtime configuration lives in **host** environment variables that the workflow never writes — see `deployment-instructions.md` § "Runtime Configuration" for the full list and where it lives on the host (a single `shared/env` file per environment, outside the swapped release directory, `chmod 600`). Gitea Actions secrets are limited to what the workflow itself needs to reach the host: SSH credentials (`PI_MAIN_*`). No application secret is ever a Gitea secret or variable.