Feature/gitea deployment workflow #1
@@ -0,0 +1,150 @@
|
|||||||
|
name: Deploy (SCP)
|
||||||
|
|
||||||
|
# Reusable deploy workflow, invoked by continuous_integration.yaml (U5) for both the automatic
|
||||||
|
# test deploy and the opt-in production deploy. One workflow, one input contract, so a second
|
||||||
|
# transport (e.g. FTPS for a future shared-hosting target) can be added later as an alternative
|
||||||
|
# without restructuring this workflow or its callers (FR-02, D-02, NFR-09).
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
artifact_name:
|
||||||
|
description: 'Build artifact to download and deploy'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
environment:
|
||||||
|
description: 'Environment label, used in log output and step naming'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
deploy_path:
|
||||||
|
description: 'Release root on the target host for this environment'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
service_name:
|
||||||
|
description: 'systemd --user unit to restart after switching releases'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
health_check_url:
|
||||||
|
description: 'Public /health URL to verify after restart'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
run_db_backup:
|
||||||
|
description: 'Take a database backup before deploying (production only)'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
transport:
|
||||||
|
description: 'Transport mechanism. Only "scp" is implemented today; reserved for FTPS later'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: scp
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Compute release timestamp
|
||||||
|
id: release
|
||||||
|
run: echo "timestamp=$(date -u +%Y%m%d%H%M%S)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Download build artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.artifact_name }}
|
||||||
|
path: ${{ inputs.artifact_name }}
|
||||||
|
|
||||||
|
# Production-only database backup, taken before anything on the host changes (FR-20, D-26).
|
||||||
|
# The backup script itself lives on the host (created once during Operations host setup) and
|
||||||
|
# is only invoked here — no database connection string or credential is ever known to this
|
||||||
|
# workflow, keeping D-16 ("runtime secrets live in host environment variables") intact.
|
||||||
|
- name: Back up database (${{ inputs.environment }})
|
||||||
|
if: ${{ inputs.run_db_backup }}
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && sudo apt-get install -y sshpass
|
||||||
|
sshpass -p "${{ secrets.PI_MAIN_PASSWORD }}" ssh \
|
||||||
|
-p ${{ secrets.PI_MAIN_PORT }} \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
${{ secrets.PI_MAIN_USERNAME }}@${{ secrets.PI_MAIN_ADDRESS }} \
|
||||||
|
"bash ~/scripts/backup-slpmodularcms-db.sh ${{ inputs.environment }}"
|
||||||
|
|
||||||
|
# Uploads the published output to a fresh, timestamped release directory rather than
|
||||||
|
# overwriting the live one (FR-06, D-27) — the atomic switch happens in a later step, once
|
||||||
|
# this upload and the website-symlink step below have both succeeded.
|
||||||
|
#
|
||||||
|
# Plain shell step rather than a container SCP action: container-based actions fail on this
|
||||||
|
# runner with "failed to attach to container: unable to upgrade to tcp, received 409", a known
|
||||||
|
# limitation of Podman's Docker-compatible API for the attach/log-streaming that container
|
||||||
|
# actions rely on (D-05). A plain scp command needs no nested container.
|
||||||
|
- name: Upload release to ${{ inputs.environment }} (${{ inputs.transport }})
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && sudo apt-get install -y sshpass
|
||||||
|
RELEASE_DIR="${{ inputs.deploy_path }}/releases/${{ steps.release.outputs.timestamp }}"
|
||||||
|
sshpass -p "${{ secrets.PI_MAIN_PASSWORD }}" ssh \
|
||||||
|
-p ${{ secrets.PI_MAIN_PORT }} \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
${{ secrets.PI_MAIN_USERNAME }}@${{ secrets.PI_MAIN_ADDRESS }} \
|
||||||
|
"mkdir -p $RELEASE_DIR"
|
||||||
|
sshpass -p "${{ secrets.PI_MAIN_PASSWORD }}" scp \
|
||||||
|
-P ${{ secrets.PI_MAIN_PORT }} \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-r ${{ inputs.artifact_name }}/* \
|
||||||
|
${{ secrets.PI_MAIN_USERNAME }}@${{ secrets.PI_MAIN_ADDRESS }}:"$RELEASE_DIR"/
|
||||||
|
|
||||||
|
# The customer's public website (wwwroot/web) must survive every CMS deploy (FR-08, ASM-01).
|
||||||
|
# It lives outside the swapped release directory in a persistent shared/ folder, and is
|
||||||
|
# symlinked into each new release. mkdir -p is idempotent, so this is also correct on the very
|
||||||
|
# first-ever deploy, before any website workspace has published anything there (U1 already
|
||||||
|
# tolerates a missing wwwroot/web at startup). The publish output's own wwwroot/web (empty, or
|
||||||
|
# containing only the placeholder page) is removed before the symlink is created, so it never
|
||||||
|
# shadows the persistent content.
|
||||||
|
- name: Link persistent website content
|
||||||
|
run: |
|
||||||
|
RELEASE_DIR="${{ inputs.deploy_path }}/releases/${{ steps.release.outputs.timestamp }}"
|
||||||
|
sshpass -p "${{ secrets.PI_MAIN_PASSWORD }}" ssh \
|
||||||
|
-p ${{ secrets.PI_MAIN_PORT }} \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
${{ secrets.PI_MAIN_USERNAME }}@${{ secrets.PI_MAIN_ADDRESS }} \
|
||||||
|
"mkdir -p ${{ inputs.deploy_path }}/shared/wwwroot-web && \
|
||||||
|
rm -rf $RELEASE_DIR/wwwroot/web && \
|
||||||
|
ln -s ../../../shared/wwwroot-web $RELEASE_DIR/wwwroot/web"
|
||||||
|
|
||||||
|
# Atomic release switch (FR-06, D-27): `ln -sfn` replaces the `current` symlink target in a
|
||||||
|
# single filesystem operation, so there is no moment where `current` points at a half-written
|
||||||
|
# directory. The process is then restarted so it picks up the new assemblies — a running .NET
|
||||||
|
# process holds on to the ones it already loaded.
|
||||||
|
- name: Switch current release and restart service
|
||||||
|
run: |
|
||||||
|
RELEASE_DIR="${{ inputs.deploy_path }}/releases/${{ steps.release.outputs.timestamp }}"
|
||||||
|
sshpass -p "${{ secrets.PI_MAIN_PASSWORD }}" ssh \
|
||||||
|
-p ${{ secrets.PI_MAIN_PORT }} \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
${{ secrets.PI_MAIN_USERNAME }}@${{ secrets.PI_MAIN_ADDRESS }} \
|
||||||
|
"ln -sfn $RELEASE_DIR ${{ inputs.deploy_path }}/current && \
|
||||||
|
systemctl --user restart ${{ inputs.service_name }}"
|
||||||
|
|
||||||
|
# Verifies the restart actually produced a healthy process before this run is allowed to
|
||||||
|
# report success. Retries absorb ordinary process-startup time; a run that never turns healthy
|
||||||
|
# fails the job without touching `current` or pruning (see below) — no automatic rollback.
|
||||||
|
# A previous release is always still on disk to restore from manually (D-26).
|
||||||
|
- name: Verify /health
|
||||||
|
run: |
|
||||||
|
for attempt in $(seq 1 10); do
|
||||||
|
if curl -f -s -o /dev/null "${{ inputs.health_check_url }}"; then
|
||||||
|
echo "Health check passed on attempt $attempt"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Health check attempt $attempt failed, retrying..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
echo "Health check did not pass after 10 attempts"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
# Retention: keep `current` plus exactly one previous release, so a manual rollback is always
|
||||||
|
# a re-point-and-restart away without rebuilding. Only runs after a passing health check —
|
||||||
|
# pruning after a failed check could leave the only other release as the sole survivor.
|
||||||
|
- name: Prune old releases
|
||||||
|
run: |
|
||||||
|
sshpass -p "${{ secrets.PI_MAIN_PASSWORD }}" ssh \
|
||||||
|
-p ${{ secrets.PI_MAIN_PORT }} \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
${{ secrets.PI_MAIN_USERNAME }}@${{ secrets.PI_MAIN_ADDRESS }} \
|
||||||
|
"cd ${{ inputs.deploy_path }}/releases && ls -1t | tail -n +3 | xargs -r rm -rf"
|
||||||
@@ -1027,3 +1027,80 @@ Nothing pushed. Next: Round 3 — U5 CI Workflow & Gates and U6 Deploy Workflow,
|
|||||||
by Infrastructure Design. U5 carries REF-U3-01's Umami origin gate plus FR-21 and FR-22.
|
by Infrastructure Design. U5 carries REF-U3-01's Umami origin gate plus FR-21 and FR-22.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
## Session Resume
|
||||||
|
|
||||||
|
**Timestamp**: 2026-07-28T00:00:00Z
|
||||||
|
|
||||||
|
**User Input**: "Ik was bezig met de gitea-deployment-workflow feature met aidlc. Die wil ik graag oppakken. Als het goed is waren net u3 en u4 afgerond"
|
||||||
|
|
||||||
|
**AI Response**: "Confirmed Round 2 (U3 Security Headers & CSP, U4 Observability) is complete, committed, and verified (372 backend / 237 frontend tests passing). Nothing pushed. Proceeding to Round 3: U5 CI Workflow & Gates + U6 Deploy Workflow (Infrastructure Design first for U6)."
|
||||||
|
|
||||||
|
**Context**: Session resumed; loaded aidlc-state.md, unit-of-work.md, and Round 2 verification record
|
||||||
|
|
||||||
|
---
|
||||||
|
## Infrastructure Design — U6 Deploy Workflow (Planning)
|
||||||
|
|
||||||
|
**Timestamp**: 2026-07-28T00:05:00Z
|
||||||
|
|
||||||
|
**User Input**: "(implicit — continuing Round 3 per prior approval)"
|
||||||
|
|
||||||
|
**AI Response**: "Created infrastructure-design-plan question file with 12 questions across 6 categories (Deployment Environment, Compute, Storage, Networking, Monitoring, Shared Infrastructure), covering Pi topology, publish RID/self-contained mode, systemd restart mechanism and sudo, release/persistence directory layout, retention count, database backup automatability, health-check verification method, rollback-on-failure behaviour, and Gitea secret naming. Functional Design and NFR Design confirmed skipped for U6 per execution plan (Pipeline-type unit)."
|
||||||
|
|
||||||
|
**Context**: CONSTRUCTION — Round 3 — U6 Infrastructure Design, Step 3-5 (question generation, awaiting user answers)
|
||||||
|
|
||||||
|
---
|
||||||
|
## Infrastructure Design — U6 Deploy Workflow (Complete)
|
||||||
|
|
||||||
|
**Timestamp**: 2026-07-28T00:20:00Z
|
||||||
|
|
||||||
|
**User Input**: "Ik heb de vragen ingevuld, ga verder"
|
||||||
|
|
||||||
|
**AI Response**: "Read 12/12 answers from u6-deploy-workflow-infrastructure-design-plan.md. Resolved an apparent Q1/Q10 tension (same Pi, directory-only split, yet an existing nginx reverse proxy routes environments) by concluding port assignment is a pre-existing host/nginx fact the workflow never manages. Generated infrastructure-design.md and deployment-architecture.md covering host topology, systemd --user process management (identifying a new finding, INFRA-U6-01: loginctl enable-linger is required or the service dies when the deploy SSH session closes), release/shared/current directory layout with 2-release retention, the full deployment sequence including production-only DB backup via a host-side script (no DB credentials touch the workflow), health verification via public URL through nginx with no automatic rollback on failure, the reusable workflow_call interface shared with U5, and Gitea variable/secret naming (PI_MAIN_ADDRESS renamed per user's answer)."
|
||||||
|
|
||||||
|
**Context**: CONSTRUCTION — Round 3 — U6 Infrastructure Design, Steps 6-8 complete; awaiting approval to proceed to Code Generation
|
||||||
|
|
||||||
|
---
|
||||||
|
## Infrastructure Design — U6 Deploy Workflow (Approved)
|
||||||
|
|
||||||
|
**Timestamp**: 2026-07-28T00:25:00Z
|
||||||
|
|
||||||
|
**User Input**: "Continue to Next Stage"
|
||||||
|
|
||||||
|
**AI Response**: "Infrastructure Design for U6 approved. Proceeding to Code Generation, starting with U6 (deploy-scp.yaml, implementing the just-approved interface) then U5 (continuous_integration.yaml, which calls it), since U5 depends on U6's concrete interface."
|
||||||
|
|
||||||
|
**Context**: CONSTRUCTION — Round 3 — Infrastructure Design U6 marked complete; entering Code Generation for U6
|
||||||
|
|
||||||
|
---
|
||||||
|
## Code Generation Plan — U6 Deploy Workflow (Planning)
|
||||||
|
|
||||||
|
**Timestamp**: 2026-07-28T00:30:00Z
|
||||||
|
|
||||||
|
**User Input**: "(implicit — continuing after Infrastructure Design approval)"
|
||||||
|
|
||||||
|
**AI Response**: "Created code generation plan for U6 (3 steps: generate deploy-scp.yaml, validate YAML, generate documentation), presenting for approval before generation starts."
|
||||||
|
|
||||||
|
**Context**: CONSTRUCTION — Round 3 — U6 Code Generation Part 1 (Planning), awaiting plan approval
|
||||||
|
|
||||||
|
---
|
||||||
|
## Code Generation Plan — U6 Deploy Workflow (Approved)
|
||||||
|
|
||||||
|
**Timestamp**: 2026-07-28T00:32:00Z
|
||||||
|
|
||||||
|
**User Input**: "Approve Plan"
|
||||||
|
|
||||||
|
**AI Response**: "Plan approved. Proceeding to Code Generation Part 2 for U6."
|
||||||
|
|
||||||
|
**Context**: CONSTRUCTION — Round 3 — U6 Code Generation Part 1 complete, entering Part 2 (Generation)
|
||||||
|
|
||||||
|
---
|
||||||
|
## Code Generation — U6 Deploy Workflow (Complete)
|
||||||
|
|
||||||
|
**Timestamp**: 2026-07-28T00:40:00Z
|
||||||
|
|
||||||
|
**User Input**: "(implicit — plan approved, generation executed)"
|
||||||
|
|
||||||
|
**AI Response**: "Generated .gitea/workflows/deploy-scp.yaml (7 workflow_call inputs, 1 job, 8 steps) implementing the approved Infrastructure Design. Validated YAML syntax with PyYAML (confirmed the 'on:' key resolves to boolean True under YAML 1.1 rules, matching the reference project's own working deploy.yaml — a parser quirk, not a defect). Documented in construction/u6-deploy-workflow/code/generation-summary.md. No C# test project applies to this Pipeline-type unit."
|
||||||
|
|
||||||
|
**Context**: CONSTRUCTION — Round 3 — U6 Code Generation Part 2 complete, presenting completion message
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
+62
@@ -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 1–2); 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) |
|
||||||
+66
@@ -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)
|
||||||
Reference in New Issue
Block a user