# Infrastructure Design Plan — U6 Deploy Workflow ## Prerequisite note (deliberate deviation) Functional Design and NFR Design are **skipped** for U6 per the execution plan (`unit-of-work.md`, `aidlc-state.md`) — U6 is a Pipeline-type unit (YAML, no C# logic), and its behavioural contract is already fully specified by FR-02, FR-03, FR-04, FR-06, FR-08, FR-20, ASM-01, ASM-03, ASM-04, D-02, D-05, D-09, D-26, D-27 and the U5↔U6 shared-interface constraint in `unit-of-work-dependency.md` ("U5 with U6 — one interface, two files"). Infrastructure Design is the right entry point: it maps that contract onto an actual host layout. ## Steps - [x] Step 1: Analyze design artifacts (requirements.md §§ Decisions/FR/ASM, unit-of-work.md U6, unit-of-work-dependency.md, reference project `.gitea/workflows/` at `K:\Development\SlpSoftware\Projects\SlpSoftware`) - [x] Step 2: Create this plan - [x] Step 3: Generate context-appropriate questions (below) - [x] Step 4: Store plan (this file) - [x] Step 5: Notify user in chat - [x] Step 6: Collect and analyze answers (12/12 answered, no contradictions — Q1/Q10 initially looked like a conflict, same-port two-instance clash, resolved: port assignment is a host/nginx-level fact outside the workflow's concern, so "directory-only" split is consistent) - [x] Step 7: Generate infrastructure design artifacts (`infrastructure-design.md`, `deployment-architecture.md`) - [x] Step 8: Present completion message - [ ] Step 9: Wait for explicit approval - [ ] Step 10: Record approval and update progress ## What the reference project (`SlpSoftware`) already establishes — not re-asked - Reusable `workflow_call` deploy workflow, invoked with `secrets: inherit` - `sshpass` + `scp` in a plain shell step (container SCP actions fail on the Podman runner with a 409 attach error) — D-05 - A `config` job that turns `env:` values into job outputs, because the `env` context is unavailable inside a reusable workflow's `with:` block - Gitea Actions **variables** for paths/environment names, **secrets** for host/credentials (`PI_MAIN_HOST`, `PI_MAIN_PORT`, `PI_MAIN_USERNAME`, `PI_MAIN_PASSWORD` — reusable per ASM-07) None of that needs to be re-decided. What the reference project does **not** cover — because it only ever uploads a static `dist/` folder to a fixed path — is everything below: this unit publishes a running .NET process, must switch releases atomically without a moment of downtime for the customer's website, must back up a database first, and must be able to restart the process and prove it came back healthy. --- ## Category: Deployment Environment ### Question 1 Is production the **same Raspberry Pi** as test (different port/directory), or a **second, separate** Pi? A) Same Pi, different port and directory per environment B) Separate Pi per environment C) Not decided yet — design for either (parameterize host via Gitea variables/secrets, don't assume) [Answer]: A, but only a different directory ### Question 2 What OS architecture is the Pi running, for the `dotnet publish` runtime identifier? A) `linux-arm64` (Raspberry Pi 4/5, 64-bit OS) B) `linux-arm` (32-bit OS) C) `linux-x64` (not a Pi / emulated) D) Other (please describe after [Answer]: tag below) [Answer]: A, but I might need a windows package later as well for other client API's. For now it is on the Pi --- ## Category: Compute Infrastructure ### Question 3 How is the published process managed/restarted on the Pi (ASM-03 assumes systemd)? A) systemd service, one unit per environment (e.g. `slpmodularcms-test.service`, `slpmodularcms-production.service`), restarted via `sudo systemctl restart ` over SSH B) systemd service, but restart is a manual/documented step, not automated in the workflow C) Some other process manager (pm2, supervisor, a custom script) — describe below D) Other (please describe after [Answer]: tag below) [Answer]: A, but if there are better or more convenient ways to manage .net processes let me know ### Question 4 Passwordless `sudo systemctl restart` for the deploy user — is that already configured, or does it need to be part of the one-time host setup documented in Operations? A) Already configured B) Needs to be added — document it as a one-time host-setup step in Operations C) Avoid sudo entirely — run the app as the deploy user's own systemd **user** service (`systemctl --user restart`), no elevation needed D) Other (please describe after [Answer]: tag below) [Answer]: C ### Question 5 Should the workflow **publish** (`dotnet publish -r --self-contained ...`) framework-dependent or self-contained? A) Self-contained (Pi doesn't need a separately-installed .NET runtime; larger artifact) B) Framework-dependent (assumes .NET 10 runtime already installed on the Pi, per ASM-03; smaller artifact) C) Other (please describe after [Answer]: tag below) [Answer]: B --- ## Category: Storage Infrastructure ### Question 6 Release directory layout on the host, given `deploy_path` as the single input (FR-02) — how should `releases/`, the atomic `current` pointer, and the persistent `wwwroot/web/` be arranged underneath it? A) `{deploy_path}/releases/{timestamp-or-sha}/` per release, `{deploy_path}/current` symlink switched atomically to point at one release, `{deploy_path}/shared/wwwroot-web/` persistent and symlinked into each new release as `wwwroot/web` B) Same as A, but the persistent website content lives at a path **outside** `deploy_path` entirely (e.g. a fixed host path unrelated to the deploy target), to make it structurally impossible for any future workflow change to prune it by mistake C) Other (please describe after [Answer]: tag below) [Answer]: A ### Question 7 How many previous releases should be retained after pruning (FR-06: "at least the previous one")? A) 2 (current + 1 previous) B) 3 (current + 2 previous) C) 5 D) Other (please describe after [Answer]: tag below) [Answer]: A ### Question 8 Database backup before production deploy (FR-20) — is it automatable from the Gitea runner over SSH (ASM-04 assumes the database is reachable from the *application*, not necessarily from the runner)? A) Automatable: the deploy workflow runs a backup command over SSH on the Pi itself (e.g. `sqlcmd`/`sqlpackage` or a SQL Server backup script already on the host), before the atomic switch B) Not automatable from the runner: emit a clear, verifiable manual step in the deployment instructions instead (FR-20's documented fallback), and the workflow does not attempt it C) Not sure yet — design the workflow with an explicit backup step that can be a placeholder script the user fills in during Operations D) Other (please describe after [Answer]: tag below) [Answer]: A --- ## Category: Networking Infrastructure ### Question 9 Post-switch health verification (FR-06 implies confirming the restart succeeded) — how should the workflow check `/health`? A) `curl` from the Gitea runner against the environment's public HTTPS URL B) `curl` executed **on the Pi itself** over the same SSH connection, against `localhost:` (works even if the public URL is only reachable through a reverse proxy not yet configured, and doesn't depend on external DNS/TLS) C) Both — localhost check on the Pi first, then a public-URL check as a secondary confirmation D) Other (please describe after [Answer]: tag below) [Answer]: A ### Question 10 Is there an existing reverse proxy (nginx, as in the reference project) in front of this application, or does Kestrel serve requests directly? A) Kestrel serves directly on a fixed port per environment (e.g. `5000` test, `5001` production) — no reverse proxy for this app B) Existing nginx reverse proxy in front, forwarding to Kestrel on a local port C) Not decided yet / out of scope for this workflow — document the assumption and let Operations confirm the real topology D) Other (please describe after [Answer]: tag below) [Answer]: B --- ## Category: Monitoring Infrastructure ### Question 11 If the post-switch health check (Question 9) fails, what should the workflow do? A) Fail the job loudly (red pipeline) but leave the atomic switch as-is — no automatic rollback; manual rollback via Operations' documented procedure B) Automatically re-point `current` back to the previous release and restart, then fail the job C) Other (please describe after [Answer]: tag below) [Answer]: A --- ## Category: Shared Infrastructure ### Question 12 Gitea Actions variable/secret naming for the deploy target — reuse the reference project's `PI_MAIN_*` secrets (ASM-07), or introduce project-specific names since this repo has its own test **and** production paths on the same or a different host? A) Reuse `PI_MAIN_HOST` / `PI_MAIN_PORT` / `PI_MAIN_USERNAME` / `PI_MAIN_PASSWORD` as-is (same values already configured for the reference repo, since it's the same Pi) B) New, project-scoped secrets (e.g. `SLPMODULARCMS_PI_HOST`, etc.) even if the values happen to be identical today, so the two repositories' deploy targets can diverge independently later C) Other (please describe after [Answer]: tag below) [Answer]: A, but rename PI_MAIN_HOST to PI_MAIN_ADDRESS --- ## Self-Validation - [x] Every question uses lettered multiple-choice options - [x] Every question ends in an explicit `Other` option - [x] Every question has an `[Answer]:` tag - [x] No process/approval question is included here (those stay in chat)