Files
questions/.gitea/workflows/deploy.yml
T
Bendik Aagaard LynghaugandClaude Fable 5.1 aedf45cf0a
Deploy instance / deploy (push) Successful in 2s
Lint and reload / lint (push) Failing after 2s
Lint and reload / reload (push) Skipped
Releases come from dl.redoal.com, and the site runs portal 0.6.20
The front door's releases card read the private repo's Gitea releases
with its link nulled, since those 404 for visitors. It now reads the
public manifest redoal's release job writes to dl.redoal.com, which
portal renders as a download card with this device's file first. The
pins move from v0.5.2 to v0.6.20 for both the instance and the lint;
the content lints clean on it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 12:56:32 +02:00

120 lines
5.2 KiB
YAML

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.6.20
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 <<EOF
NATS_URL=${{ secrets.NATS_URL }}
KANIDM_URL=${{ vars.KANIDM_URL }}
OAUTH2_CLIENT_ID=${{ vars.OAUTH2_CLIENT_ID }}
OAUTH2_CLIENT_SECRET=${{ secrets.OAUTH2_CLIENT_SECRET }}
# Onboarding from a desk (portal 0.3.40+, `invite:` alternatives): a
# Kanidm service-account token in idm_people_on_boarding. Empty
# means invites are not configured on this instance.
KANIDM_API_TOKEN=${{ secrets.KANIDM_API_TOKEN }}
PUBLIC_URL=${{ vars.PUBLIC_URL }}
COOKIE_SECURE=true
# This repo is its own instance's content source; branding
# (title, wordmark, gesture hero + relay) rides in site.yaml.
CONTENT_REPO=${{ github.server_url }}/${{ github.repository }}
CONTENT_BRANCH=main
LEPTOS_SITE_ADDR=0.0.0.0:3020
# The release tarball carries the site bundle at site/ (no
# target/ prefix), so override Cargo.toml's build-time path.
LEPTOS_SITE_ROOT=site
# Portal ships content-hashed pkg files (portal.<hash>.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.
# enable: the unit must come back after a host reboot (2026-08-30 a
# reboot left both portal instances down - deploys had only ever
# started them). Needs the manage-unit-files polkit grant; until
# that's on the host the enable is reported and skipped, never a
# failed deploy.
- name: Activate and restart
run: |
ln -sfn "/srv/app/$INSTANCE/releases/$PORTAL_RELEASE" /srv/app/$INSTANCE/current
systemctl enable app@$INSTANCE.service \
|| echo "::warning::could not enable app@$INSTANCE (polkit) - unit will not survive a reboot"
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