From f95463ef507a1f5efb835c27cadefce13cdc363c Mon Sep 17 00:00:00 2001 From: DevOps Date: Sun, 21 Jun 2026 10:15:36 +0200 Subject: [PATCH] fix: permanent owner password persistence with SeedAudit guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: Dual-source architecture for owner password (Gitea secret ENV_OWNER_PASSWORD vs host .env OWNER_PASSWORD) caused drift when the DB was ever re-seeded or the volume recreated. Changes: - Add SeedAudit entity + migration to track one-time seed operations - EnsureDatabaseAsync checks SeedAudit BEFORE seeding — owner is never re-created even if the Users table is wiped - Deploy and rollback workflows now read OWNER_PASSWORD from the host's persistent .env (single source of truth) instead of Gitea secrets - compose.yaml documented: OWNER_PASSWORD only used during initial seed - Cleanup: .gitignore extended for core dumps, changelog/deployment.md updated with 2026-06-20 session notes After this fix the DB is the single source of truth for the owner password after initial seed. The host .env is the single reference for the initial value. --- .gitea/workflows/deploy.yaml | 29 +- .gitea/workflows/rollback.yaml | 17 +- .gitignore | 4 + backend/Data/Identity.cs | 15 + .../20260621081500_AddSeedAudit.Designer.cs | 336 ++++++++++++++++++ .../Migrations/20260621081500_AddSeedAudit.cs | 33 ++ .../Migrations/NexusDbContextModelSnapshot.cs | 14 + backend/Data/NexusDbContext.cs | 1 + .../ApplicationBuilderExtensions.cs | 12 + backend/core.3048 | Bin 69779456 -> 0 bytes compose.yaml | 3 + phases/changelog.md | 19 + phases/deployment.md | 16 +- 13 files changed, 488 insertions(+), 11 deletions(-) create mode 100644 backend/Data/Migrations/20260621081500_AddSeedAudit.Designer.cs create mode 100644 backend/Data/Migrations/20260621081500_AddSeedAudit.cs delete mode 100644 backend/core.3048 diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index b569b48..ee3a19a 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -67,8 +67,10 @@ jobs: ENV_TMPFILE: /tmp/nexus-deploy-env ENV_POSTGRES_PASSWORD: ${{ secrets.ENV_POSTGRES_PASSWORD }} ENV_JWT_KEY: ${{ secrets.ENV_JWT_KEY }} - ENV_OWNER_PASSWORD: ${{ secrets.ENV_OWNER_PASSWORD }} ENV_OPENCLAW_TOKEN: ${{ secrets.ENV_OPENCLAW_TOKEN }} + # OWNER_PASSWORD is read from the host's persistent .env — NOT from a Gitea secret. + # This ensures the password stays consistent across deploys and the DB is the + # single source of truth after initial seed (enforced by SeedAudit guard). steps: # ═══════════════════════════════════════════════════ @@ -127,20 +129,37 @@ jobs: echo "mutated_main=false" >> "$GITEA_OUTPUT" # ═══════════════════════════════════════════════════ - # Step 4: Build .env from secrets (SAFE) + # Step 4: Build .env from secrets + host .env (SAFE) # # Secrets are written to /tmp/nexus-deploy-env — NEVER # to a file inside the workspace that gets rsync'd to # the host. The temp file is deleted immediately after # compose operations complete. + # + # OWNER_PASSWORD is read from the host's persistent .env + # to ensure it stays the single source of truth. Other + # secrets (POSTGRES_PASSWORD, JWT_KEY, OPENCLAW_TOKEN) + # come from Gitea secrets. # ═══════════════════════════════════════════════════ - - name: Prepare .env (secrets → temp file) + - name: Prepare .env (secrets + host .env → temp file) run: | set -euo pipefail + # Read OWNER_PASSWORD from the host's persistent .env + HOST_OWNER_PASSWORD="" + if [ -f "${DEPLOY_PATH}/.env" ]; then + HOST_OWNER_PASSWORD=$(grep '^OWNER_PASSWORD=' "${DEPLOY_PATH}/.env" | cut -d= -f2- || true) + fi + if [ -z "${HOST_OWNER_PASSWORD}" ]; then + echo "❌ OWNER_PASSWORD not found in ${DEPLOY_PATH}/.env" + echo " The host .env is the single source of truth for the owner password." + echo " Ensure OWNER_PASSWORD is set in the deploy-path .env before deploying." + exit 1 + fi + cat > "${ENV_TMPFILE}" <