Feature/gitea deployment workflow #1

Merged
Sluijsens merged 35 commits from feature/gitea-deployment-workflow into master 2026-07-29 16:50:44 +02:00
3 changed files with 28 additions and 17 deletions
Showing only changes of commit 843253888e - Show all commits
@@ -1312,3 +1312,14 @@ by Infrastructure Design. U5 carries REF-U3-01's Umami origin gate plus FR-21 an
**Context**: OPERATIONS — Deployment Setup, consistency fix before final approval **Context**: OPERATIONS — Deployment Setup, consistency fix before final approval
--- ---
## Deployment Setup — Nested Directory Structure
**Timestamp**: 2026-07-28T04:15:00Z
**User Input**: "is het ook mogelijk een mappenstructuur te doen met slpsoftware/<env> in paats van slpsoftware-<env>? Dus dat er binnen een slpsoftware-map mappen komen voor elke environment? Zowel voor de ~/apps/slpmodularcms als voor de frontendsen de www/hhtml mappen?"
**AI Response**: "Changed the directory convention throughout deployment-instructions.md and rollback-plan.md from sibling slpmodularcms-<env> directories to a nested slpmodularcms/<env> structure, for both ~/apps/slpmodularcms/<env>/ and /mnt/storage1/www/html/slpmodularcms/<env>/, including the backup directory for consistency. Left systemd unit filenames (slpmodularcms-test.service / slpmodularcms-production.service) and the SERVICE_NAME_TEST/_PRODUCTION Gitea variables hyphenated, since those are service names, not directories, and the request was specifically about folder structure."
**Context**: OPERATIONS — Deployment Setup, directory naming convention change before final approval
---
@@ -35,7 +35,7 @@ sudo useradd -m -s /bin/bash gitea-workflow
sudo passwd gitea-workflow sudo passwd gitea-workflow
``` ```
Each project deploying through this account gets its own subdirectory under its home (§ 1.4 already Each project deploying through this account gets its own subdirectory under its home (§ 1.4 already
namespaces by project: `~/apps/slpmodularcms-<env>/`), so one generic account can serve multiple namespaces by project: `~/apps/slpmodularcms/<env>/`), so one generic account can serve multiple
projects without their release trees colliding. projects without their release trees colliding.
**Fallback, only if no SSH-capable account exists at all and creating one genuinely isn't feasible** **Fallback, only if no SSH-capable account exists at all and creating one genuinely isn't feasible**
@@ -66,25 +66,25 @@ for the CMS's own release/current/shared structure to live anywhere near the oth
which directly avoids interfering with them (as you asked in Q2): which directly avoids interfering with them (as you asked in Q2):
```bash ```bash
mkdir -p ~/apps/slpmodularcms-<env>/releases mkdir -p ~/apps/slpmodularcms/<env>/releases
``` ```
`current` is created by the first deploy itself (`ln -sfn`) — don't pre-create it. `current` is created by the first deploy itself (`ln -sfn`) — don't pre-create it.
**`shared/wwwroot-web` is the one exception** — it must resolve to wherever `webadmin` actually **`shared/wwwroot-web` is the one exception** — it must resolve to wherever `webadmin` actually
uploads *this* customer's website via FileZilla, e.g. `/mnt/storage1/www/html/slpmodularcms-<env>/` uploads *this* customer's website via FileZilla, e.g. `/mnt/storage1/www/html/slpmodularcms/<env>/`
(adjust the exact folder name to whatever convention the other sites under `html/` already use, if (adjust the exact folder name to whatever convention the other sites under `html/` already use, if
one exists). Rather than a plain directory, make it a symlink across accounts: one exists). Rather than a plain directory, make it a symlink across accounts:
```bash ```bash
mkdir -p ~/apps/slpmodularcms-<env> mkdir -p ~/apps/slpmodularcms/<env>
ln -s /mnt/storage1/www/html/slpmodularcms-<env> ~/apps/slpmodularcms-<env>/shared/wwwroot-web ln -s /mnt/storage1/www/html/slpmodularcms/<env> ~/apps/slpmodularcms/<env>/shared/wwwroot-web
``` ```
`deploy-scp.yaml`'s existing logic (`releases/{ts}/wwwroot/web -> ../../../shared/wwwroot-web`) `deploy-scp.yaml`'s existing logic (`releases/{ts}/wwwroot/web -> ../../../shared/wwwroot-web`)
needs no changes for this — it only ever resolves the symlink chain, it doesn't care how many hops needs no changes for this — it only ever resolves the symlink chain, it doesn't care how many hops
that chain has. What **does** need attention: `gitea-workflow` needs read + traverse permission on that chain has. What **does** need attention: `gitea-workflow` needs read + traverse permission on
`/mnt/storage1/www/html/slpmodularcms-<env>/` and its parent directories, which `webadmin` owns. `/mnt/storage1/www/html/slpmodularcms/<env>/` and its parent directories, which `webadmin` owns.
Simplest fix: put both accounts in a shared group (e.g. `webshared`), `chgrp -R webshared` that Simplest fix: put both accounts in a shared group (e.g. `webshared`), `chgrp -R webshared` that
folder, and make sure webadmin's FTP server creates new uploads group-readable (`g+rx`, not just folder, and make sure webadmin's FTP server creates new uploads group-readable (`g+rx`, not just
owner-readable) — a one-time permission setup, not something either pipeline touches per deploy. owner-readable) — a one-time permission setup, not something either pipeline touches per deploy.
@@ -92,8 +92,8 @@ owner-readable) — a one-time permission setup, not something either pipeline t
### 1.5 Runtime Configuration File ### 1.5 Runtime Configuration File
One file per environment, **outside** the release directory so it survives every switch: One file per environment, **outside** the release directory so it survives every switch:
```bash ```bash
touch ~/apps/slpmodularcms-<env>/shared/env touch ~/apps/slpmodularcms/<env>/shared/env
chmod 600 ~/apps/slpmodularcms-<env>/shared/env chmod 600 ~/apps/slpmodularcms/<env>/shared/env
``` ```
Contents (fill in real values — this file is never read by the workflow, only by the systemd unit Contents (fill in real values — this file is never read by the workflow, only by the systemd unit
below): below):
@@ -127,9 +127,9 @@ Description=SlpModularCms API (test)
After=network.target After=network.target
[Service] [Service]
WorkingDirectory=%h/apps/slpmodularcms-test/current WorkingDirectory=%h/apps/slpmodularcms/test/current
ExecStart=/usr/bin/dotnet %h/apps/slpmodularcms-test/current/SlpModularCms.Api.dll ExecStart=/usr/bin/dotnet %h/apps/slpmodularcms/test/current/SlpModularCms.Api.dll
EnvironmentFile=%h/apps/slpmodularcms-test/shared/env EnvironmentFile=%h/apps/slpmodularcms/test/shared/env
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec=5
KillSignal=SIGINT KillSignal=SIGINT
@@ -202,8 +202,8 @@ change:
| `PI_MAIN_PORT` | secret | SSH port | | `PI_MAIN_PORT` | secret | SSH port |
| `PI_MAIN_USERNAME` | secret | `gitea-workflow` — the generic, host-wide deploy account (§ 1.2), **not** `webadmin`; reuse the reference project's if it already has one | | `PI_MAIN_USERNAME` | secret | `gitea-workflow` — the generic, host-wide deploy account (§ 1.2), **not** `webadmin`; reuse the reference project's if it already has one |
| `PI_MAIN_PASSWORD` | secret | `gitea-workflow`'s password | | `PI_MAIN_PASSWORD` | secret | `gitea-workflow`'s password |
| `DEPLOY_PATH_TEST` | variable | `/home/gitea-workflow/apps/slpmodularcms-test` | | `DEPLOY_PATH_TEST` | variable | `/home/gitea-workflow/apps/slpmodularcms/test` |
| `DEPLOY_PATH_PRODUCTION` | variable | `/home/gitea-workflow/apps/slpmodularcms-production` | | `DEPLOY_PATH_PRODUCTION` | variable | `/home/gitea-workflow/apps/slpmodularcms/production` |
| `SERVICE_NAME_TEST` | variable | `slpmodularcms-test.service` | | `SERVICE_NAME_TEST` | variable | `slpmodularcms-test.service` |
| `SERVICE_NAME_PRODUCTION` | variable | `slpmodularcms-production.service` | | `SERVICE_NAME_PRODUCTION` | variable | `slpmodularcms-production.service` |
| `HEALTH_CHECK_URL_TEST` | variable | `https://test.slpsoftware.nl/health` | | `HEALTH_CHECK_URL_TEST` | variable | `https://test.slpsoftware.nl/health` |
@@ -256,7 +256,7 @@ fi
source "$CREDENTIALS_FILE" source "$CREDENTIALS_FILE"
: "${DB_SERVER:?}" "${DB_NAME:?}" "${DB_USER:?}" "${DB_PASSWORD:?}" : "${DB_SERVER:?}" "${DB_NAME:?}" "${DB_USER:?}" "${DB_PASSWORD:?}"
BACKUP_DIR="$HOME/backups/slpmodularcms-${ENVIRONMENT}" BACKUP_DIR="$HOME/backups/slpmodularcms/${ENVIRONMENT}"
mkdir -p "$BACKUP_DIR" mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date -u +%Y%m%d%H%M%S) TIMESTAMP=$(date -u +%Y%m%d%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}-${TIMESTAMP}.bak" BACKUP_FILE="$BACKUP_DIR/${DB_NAME}-${TIMESTAMP}.bak"
@@ -278,7 +278,7 @@ chmod +x ~/scripts/backup-slpmodularcms-db.sh
```bash ```bash
~/scripts/backup-slpmodularcms-db.sh production ~/scripts/backup-slpmodularcms-db.sh production
``` ```
Confirm a `.bak` file appears under `~/backups/slpmodularcms-production/` and that `sqlcmd` didn't Confirm a `.bak` file appears under `~/backups/slpmodularcms/production/` and that `sqlcmd` didn't
silently fail (the script uses `set -euo pipefail`, so a real SQL error does propagate as a non-zero silently fail (the script uses `set -euo pipefail`, so a real SQL error does propagate as a non-zero
exit — which fails the calling `deploy-scp.yaml` step, correctly blocking the deploy). exit — which fails the calling `deploy-scp.yaml` step, correctly blocking the deploy).
@@ -16,9 +16,9 @@ failed deploy never leaves fewer than two releases on disk.
No rebuild needed. Over SSH, on the Pi: No rebuild needed. Over SSH, on the Pi:
```bash ```bash
cd ~/apps/slpmodularcms-<env>/releases cd ~/apps/slpmodularcms/<env>/releases
ls -1t # confirm which directory is the previous, working release ls -1t # confirm which directory is the previous, working release
ln -sfn ~/apps/slpmodularcms-<env>/releases/<previous-timestamp> ~/apps/slpmodularcms-<env>/current ln -sfn ~/apps/slpmodularcms/<env>/releases/<previous-timestamp> ~/apps/slpmodularcms/<env>/current
systemctl --user restart slpmodularcms-<env>.service systemctl --user restart slpmodularcms-<env>.service
curl -f https://<env-domain>/health # confirm the rollback itself is healthy curl -f https://<env-domain>/health # confirm the rollback itself is healthy
``` ```