From 9b44a5e85e847e52383111de2cfd67ce6ae4040f Mon Sep 17 00:00:00 2001 From: Sluijsens Date: Thu, 30 Jul 2026 10:09:53 +0200 Subject: [PATCH] Adds a shared/modules symlink mechanism for optional plugin modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ModuleOrchestrator already discovers SlpModularCms.Modules.*.dll from disk at startup via reflection, so this needed no code change — only a persistent location (mirroring shared/wwwroot-web) that survives release swaps, and a deploy-scp.yaml step to symlink its contents into each new release before restart. --- .gitea/workflows/deploy-scp.yaml | 28 +++++++++++++++++++ .../deployment/deployment-instructions.md | 28 +++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/deploy-scp.yaml b/.gitea/workflows/deploy-scp.yaml index 667c721..d46c5e0 100644 --- a/.gitea/workflows/deploy-scp.yaml +++ b/.gitea/workflows/deploy-scp.yaml @@ -132,6 +132,34 @@ jobs: rm -rf $RELEASE_DIR/wwwroot/web && \ ln -s ../../../shared/wwwroot-web $RELEASE_DIR/wwwroot/web" + # Optional plugin modules (future modular support): a compiled SlpModularCms.Modules.*.dll + # dropped into shared/modules survives every release, the same persistence pattern as + # shared/wwwroot-web above — except there's no cross-account permission dance here, since + # shared/modules lives entirely under gitea-workflow's own tree (unlike wwwroot-web, which + # reaches into webadmin's). ModuleOrchestrator.DiscoverModules() already scans its own base + # directory on disk for matching assemblies at startup (SlpModularCms.Core/Hosting/ + # ModuleOrchestrator.cs) and loads whatever it finds via reflection — no code change needed, + # only somewhere for the DLL to still be after the next deploy replaces releases/{timestamp}. + # `ls ... 2>/dev/null` piped to a `while read` (rather than a bare glob loop) keeps this + # POSIX-sh safe and correct when shared/modules is empty, which is the common case today. + - name: Link persistent modules + env: + SSH_USER: ${{ secrets.PI_MAIN_USERNAME }} + SSH_PASS: ${{ secrets.PI_MAIN_PASSWORD }} + SSH_PORT: ${{ secrets.PI_MAIN_PORT }} + SSH_HOST: ${{ secrets.PI_MAIN_ADDRESS }} + run: | + RELEASE_DIR="${{ inputs.deploy_path }}/releases/${{ steps.release.outputs.timestamp }}" + sshpass -p "$SSH_PASS" ssh \ + -p "$SSH_PORT" \ + -o StrictHostKeyChecking=no \ + "$SSH_USER@$SSH_HOST" \ + "mkdir -p ${{ inputs.deploy_path }}/shared/modules && \ + ls ${{ inputs.deploy_path }}/shared/modules/*.dll 2>/dev/null | while read -r f; do \ + name=\$(basename \"\$f\"); \ + ln -sf \"../../shared/modules/\$name\" \"$RELEASE_DIR/\$name\"; \ + done" + # 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 diff --git a/aidlc-docs/features/gitea-deployment-workflow/operations/deployment/deployment-instructions.md b/aidlc-docs/features/gitea-deployment-workflow/operations/deployment/deployment-instructions.md index 809927e..16ac21e 100644 --- a/aidlc-docs/features/gitea-deployment-workflow/operations/deployment/deployment-instructions.md +++ b/aidlc-docs/features/gitea-deployment-workflow/operations/deployment/deployment-instructions.md @@ -148,6 +148,28 @@ sudo chgrp -R webshared /mnt/storage1/www/html/slpsoftware/ 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. +**`shared/modules` is the equivalent mechanism for optional plugin modules** (future modular +support) — but simpler, since it lives entirely under `gitea-workflow`'s own tree, with no +cross-account permission dance: + +👤 **pi-main / `gitea-workflow`:** +```bash +mkdir -p ~/apps/slpsoftware//shared/modules +``` + +`deploy-scp.yaml`'s "Link persistent modules" step already runs this `mkdir -p` on every deploy, so +this line is only useful if you want the directory to exist before the very first deploy — it isn't +required. To add a module: build a `SlpModularCms.Modules..dll` against the same version of +`SlpModularCms.Core` the running app was built against (mismatched contracts are caught per-module +by `ModuleOrchestrator`'s existing try/catch — logged and skipped, not a crash), `scp`/`sftp` it as +`gitea-workflow` into `~/apps/slpsoftware//shared/modules/`, then either wait for the next +deploy or restart the service by hand: +```bash +systemctl --user restart slpsoftware-.service +``` +Either way, `ModuleOrchestrator.DiscoverModules()` picks it up from the release directory the next +time the process starts — no CI run required to add a module this way. + ### 1.5 Runtime Configuration File One file per environment, **outside** the release directory so it survives every switch: @@ -498,9 +520,9 @@ Already built (U5/U6) — this is the read-only walkthrough for whoever operates 1. Push to `master`, or a manual `workflow_dispatch` → the six gates run 2. `publish-test` runs (and `publish-production`, only if `workflow_dispatch` with the flag) 3. `deploy-test` (always, if gates pass) calls `deploy-scp.yaml`, which uploads into a new - `releases/{timestamp}/`, links `shared/wwwroot-web` in, switches `current`, restarts - `slpsoftware-test.service`, verifies `https://test.slpsoftware.nl/health`, then prunes old - releases (only test — `run_db_backup: false`) + `releases/{timestamp}/`, links `shared/wwwroot-web` and any `shared/modules/*.dll` in, switches + `current`, restarts `slpsoftware-test.service`, verifies `https://test.slpsoftware.nl/health`, + then prunes old releases (only test — `run_db_backup: false`) 4. `deploy-production` (only with the flag) does the same, plus a database backup first (`run_db_backup: true`) — see § 4