name: Deploy instance # This content repo owns its portal instance: which portal version runs, # the service env, the systemd unit, and the Caddy route. The portal repo # only publishes versioned release artifacts (uhhm/portal's Publish # workflow); PORTAL_RELEASE below pins the one this site runs. # # Rolling out a new portal version = bumping PORTAL_RELEASE (a commit, # so every rollout is auditable and revertable). Content-only changes # never come through here - lint-and-reload hot-swaps those into the # running instance over NATS. on: workflow_dispatch: push: branches: [main] paths: - .gitea/workflows/deploy.yml env: PORTAL_RELEASE: v0.3.1 INSTANCE: redoal-portal jobs: deploy: runs-on: bare steps: # Instance config comes from THIS repo's Actions variables/secrets # (Settings -> Actions) - not the portal repo's. Guard before # touching anything on the host: a missing secret must fail the # run, not silently write an empty value into the live env file. - name: Check instance secrets are configured run: | set -eu [ -n "${{ secrets.NATS_URL }}" ] || { echo "missing secret NATS_URL"; exit 1; } [ -n "${{ secrets.OAUTH2_CLIENT_SECRET }}" ] || { echo "missing secret OAUTH2_CLIENT_SECRET"; exit 1; } [ -n "${{ secrets.PORTAL_GITEA_API_TOKEN }}" ] || { echo "missing secret PORTAL_GITEA_API_TOKEN"; exit 1; } # uhhm/portal is public, so the asset download is anonymous. The # app@ template's ExecStart is /srv/app/%i/current/%i - the shipped # binary is named "portal", so link the instance name to it. - name: Ship pinned portal release run: | set -euo pipefail api="${{ github.server_url }}/api/v1/repos/uhhm/portal" url=$(curl -sf "$api/releases/tags/$PORTAL_RELEASE" | jq -r '.assets[0].browser_download_url') rel="/srv/app/$INSTANCE/releases/$PORTAL_RELEASE" rm -rf "$rel" mkdir -p "$rel" curl -sfL "$url" | tar -xz -C "$rel" ln -sfn portal "$rel/$INSTANCE" - name: Write service env run: | cat > /etc/app/$INSTANCE.env <.js) with # a hash.txt in the site root; this makes the server reference # them, so a stale cached bundle can never pair with new wasm. LEPTOS_HASH_FILES=true # Read-only Gitea token - here it also backs the # gitea_releases source for the private redoal/redoal repo, # so the owning user needs read access there. GITEA_API_TOKEN=${{ secrets.PORTAL_GITEA_API_TOKEN }} EOF # Activate only after the release and env are fully written, so a # failed download or missing config never takes the site down. # # No sudo: the runner's unit sets NoNewPrivileges=yes - systemctl # talks to PID1 over D-Bus, authorized by the polkit rule scoped # to deploy-runner + the app@* unit pattern. - name: Activate and restart run: | ln -sfn "/srv/app/$INSTANCE/releases/$PORTAL_RELEASE" /srv/app/$INSTANCE/current systemctl restart app@$INSTANCE.service # www redirects to the apex for the same single-cookie-scope # reason as uhhm's instance. The runner is in the docker group, # so no sudo here either. - name: Update Caddy routing run: | cat > /etc/caddy/services.d/$INSTANCE.caddy <<'EOF' www.redoal.com { redir https://redoal.com{uri} permanent } redoal.com { reverse_proxy host.docker.internal:3020 log { output file /var/log/caddy/redoal.log } } EOF docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile