Files
Patrick Plate 83b46c8cda
CI — Build, Lint & Security Scan / backend (push) Failing after 1m3s
CI — Build, Lint & Security Scan / frontend (push) Failing after 1m23s
CI — Build, Lint & Security Scan / image-scan (push) Has been skipped
CI — Build, Lint & Security Scan / secrets-scan (push) Failing after 37s
Deploy to TrueNAS / deploy (push) Successful in 37s
harden(deploy): db internal-only + robust container-loopback frontend verify
- db: drop host :5432 publish (ports !override []) — no LAN exposure, reached
  via compose net (db:5432) + docker exec for the ALTER USER reconcile. Matches
  inspectflow isolation. backend :8081 kept (LAN-only, used by healthcheck).
- deploy verify-frontend: probe container loopback via bundled node instead of
  host :3000 wget. Network-namespace-independent; fixes the transient
  false-failure when polling mid-recreate. <500 = healthy (307->/login).
2026-06-22 11:06:58 +02:00

134 lines
5.6 KiB
YAML

name: Deploy to TrueNAS
# Auto-deploy on push to main.
# Runs on the self-hosted Gitea Actions runner on TrueNAS.local
# (container: cannamanage-act-runner). The runner mounts the host Docker
# socket into the job container, so `docker compose` commands act on the
# TrueNAS Docker daemon and (re)build/restart the live cannamanage stack.
#
# The job checks the repo out into its own workspace and builds from there,
# so it always deploys exactly the pushed commit — it does NOT depend on the
# old /mnt/VM_SSD_Pool/cannamanage host checkout.
#
# Compose project name is pinned to "cannamanage" so it updates the existing
# containers and reuses the persistent "cannamanage_pgdata" volume on the host.
# Live host ports: frontend 3000, backend 8081->8080 (LAN, healthcheck/debug).
# db is internal-only (no host publish) — reachable as db:5432 on the compose net.
on:
push:
branches: [main]
# Avoid overlapping deploys if pushes land in quick succession.
concurrency:
group: truenas-deploy
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
env:
COMPOSE: docker compose -f docker-compose.yml -f docker-compose.truenas.yml -p cannamanage
# Production secrets — set in Gitea repo Settings → Actions → Secrets.
# AUTH_SECRET : NextAuth v5 session secret (rotating invalidates sessions)
# JWT_SECRET : base64 backend HMAC key (rotating invalidates all tokens)
# DB_PASSWORD : Postgres role password (must match the live DB role)
AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
steps:
- name: Check out pushed commit
uses: actions/checkout@v4
- name: Show toolchain
run: |
set -euo pipefail
docker version --format 'docker {{.Server.Version}}'
docker compose version
# NOTE: Backend tests (mvn test) and frontend lint (pnpm lint) are run locally
# before pushing. The self-hosted act runner uses Docker-in-Docker which doesn't
# support volume mounts for nested containers. Tests remain a local-only gate.
- name: Build images
run: |
set -euo pipefail
$COMPOSE build
- name: Ensure DB up & reconcile role password
run: |
set -euo pipefail
# Start just the db first (idempotent — reuses the running container
# and the persistent cannamanage_pgdata volume).
$COMPOSE up -d db
echo "Waiting for db to accept connections ..."
for i in $(seq 1 20); do
if docker exec cannamanage-db pg_isready -U cannamanage -q; then break; fi
echo " attempt $i/20 — waiting 3s"; sleep 3
done
# POSTGRES_PASSWORD only applies on FIRST volume init, so the existing
# volume still holds the old role password. Force the live role to match
# the rotated ${DB_PASSWORD} so the backend can authenticate. Local
# socket connections inside the container use trust auth (no password).
# Skipped when the secret is unset to avoid blanking the dev password.
if [ -n "${DB_PASSWORD:-}" ]; then
docker exec cannamanage-db psql -U cannamanage -d cannamanage \
-c "ALTER USER cannamanage WITH PASSWORD '${DB_PASSWORD}';"
echo "✅ DB role password reconciled"
else
echo "⚠️ DB_PASSWORD secret not set — leaving role password unchanged"
fi
- name: Roll out stack
run: |
set -euo pipefail
$COMPOSE up -d --remove-orphans
- name: Wait for backend health
run: |
set -euo pipefail
echo "Waiting for backend health on :8081 ..."
for i in $(seq 1 20); do
if wget -q -O /dev/null http://192.168.188.119:8081/actuator/health; then
echo "✅ Backend healthy after ${i} attempt(s)"
exit 0
fi
echo " attempt $i/20 — waiting 6s"
sleep 6
done
echo "❌ Backend did not become healthy — recent logs:"
$COMPOSE logs --tail=40 backend
exit 1
- name: Verify frontend
run: |
set -euo pipefail
# Probe the frontend on its own loopback INSIDE the container via the
# bundled node runtime. This is network-namespace-independent (no
# reliance on the host port being wired during a mid-recreate window,
# which caused a transient false-failure previously) and needs no
# wget/curl in the image. Any HTTP status < 500 counts as "up" — the
# root path returns 307 -> /login when unauthenticated, which is healthy.
echo "Waiting for frontend on container loopback :3000 ..."
for i in $(seq 1 20); do
if docker exec cannamanage-frontend node -e "require('http').get('http://127.0.0.1:3000/',r=>process.exit(r.statusCode<500?0:1)).on('error',()=>process.exit(1))"; then
echo "✅ Frontend responding after ${i} attempt(s)"
exit 0
fi
echo " attempt $i/20 — waiting 5s"
sleep 5
done
echo "❌ Frontend did not respond — recent logs:"
$COMPOSE logs --tail=40 frontend
exit 1
- name: Prune dangling images
run: docker image prune -f || true
- name: Deployment summary
run: |
echo "=== CannaManage deployed to TrueNAS ==="
echo "Commit: ${GITHUB_SHA}"
echo "Backend: http://192.168.188.119:8081"
echo "Frontend: http://192.168.188.119:3000"