build(ci,deploy)!: the runner at home in a nightly window, deploys by hand (ADR 0039, D6, D13, D16)
Some checks failed
CI / check (push) Has been cancelled

The CI runner moves to halkyon.li's host at home and runs from 02:00 to
05:00 alone: a timer starts it, its unit's RuntimeMaxSec ends it, and
night.sh rests the instance's heavy sidecars around it as deploy, while
Caddy, PostgreSQL and the apps' servers keep serving. Jobs pushed during the
day wait in Forgejo's queue.

Its sandbox fits the new host: its folder on the HDD, the rest of /data
hidden, containerd's store out of reach, no private IPv4 network and no
IPv6 at all (the home network's IPv6 addresses are public ones under a
changing prefix, on the internet's own interface), 5 GB of memory with swap
beyond. The Sandbox step checks the tunnel, the home router and IPv6.

BREAKING CHANGE: CI deploys nothing any more: the deploy job goes with
learn.halkyon.eu's server, and halkyon.li deploys by hand with redeploy.sh
once a commit's night's run passed. Jobs run on the runner labelled
balthasar; sahaquiel's runner picks up none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-08 19:20:11 +01:00
commit c8d67b1eb7
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1,13 +1,13 @@
# CI: every push of a branch runs the checks of the pre-push hook (tool/check.dart, every app's and
# shared package's); a push to main is then deployed to the instance on learn.halkyon.eu's server
# (deploy/redeploy.sh): Learn's server and every service .env switches on beside it, Train's
# and Todo's among them.
# Both jobs run on the runner on learn.halkyon.eu's server, directly on the host as an
# unprivileged user (deploy/runner/). A commit that passed the checks before isn't checked again
# (see "Checked before?").
# shared package's). Nothing is deployed from here: halkyon.li deploys by hand, a commit once
# its night's run passed (deploy/redeploy.sh; ADR 0039, D13).
# The job runs on the runner on halkyon.li's host at home, directly on the host as an
# unprivileged user, in the nightly window alone (02:00 to 05:00, deploy/runner/): what is
# pushed during the day waits in the queue. A commit that passed the checks before isn't
# checked again (see "Checked before?").
#
# Pushes only, no pull requests: a pull request from a fork would run its own code on
# the production server's runner.
# the instance's host.
name: CI
on:
@ -16,12 +16,12 @@ on:
# commit, and the tag's run would check that commit a second time while main's still
# runs ("Checked before?" knows only runs that finished). A tag deploys nothing.
branches: ['**']
# Runs the workflow again from the web UI, e.g. to retry a failed deploy.
# Runs the workflow again from the web UI, e.g. to check a commit once more.
workflow_dispatch:
jobs:
check:
runs-on: sahaquiel
runs-on: balthasar
# A warm run took 42 minutes with Train's packages (30 Oct 2026), a new Flutter more:
# the limit only catches a hung job.
timeout-minutes: 60
@ -33,8 +33,9 @@ jobs:
# with them.
SERVERPOD_VERSION: 4.0.4
steps:
# Before anything else runs, the job proves it can neither see nor reach production
# (the runner's sandbox, deploy/runner/). If it can, nothing else should run.
# Before anything else runs, the job proves it can neither see nor reach the instance
# or the home network (the runner's sandbox, deploy/runner/). If it can, nothing else
# should run.
- name: Sandbox
run: |
fail() { echo "The runner's sandbox leaks: $*" >&2; exit 1; }
@ -47,6 +48,11 @@ jobs:
# deploy's home (.env, the checkout, the dumps) and the Docker socket are out of
# reach.
[ ! -e /home/deploy ] || fail "/home/deploy is visible"
# Nor /data, where /home really lies, with Docker's data and the media: the
# runner sees its own folder there alone.
for hidden in /data/home /data/docker /data/containerd /data/media; do
[ ! -e "$hidden" ] || fail "$hidden is visible"
done
{ [ ! -r /run/docker.sock ] && [ ! -w /run/docker.sock ]; } || fail "the Docker socket is usable"
# Docker's networks, where the database listens, are unreachable. Caddy listens on
# every address of the host, the Docker bridge's included.
@ -57,11 +63,20 @@ jobs:
if timeout 5 bash -c 'exec 3<>/dev/tcp/fd4a:6c6b:7e80::2/443' 2>/dev/null; then
fail "Caddy's IPv6 network is reachable"
fi
# Nor is Jellyfin at home through the host's tunnel, where it is up (ADR 0032, D6).
if timeout 5 bash -c 'exec 3<>/dev/tcp/10.61.96.2/8096' 2>/dev/null; then
fail "the tunnel to Jellyfin at home is reachable"
# Nor Caddy at the tunnel's address, where ramiel reaches it (ADR 0039, D16).
if timeout 5 bash -c 'exec 3<>/dev/tcp/10.61.97.2/443' 2>/dev/null; then
fail "the tunnel's address is reachable"
fi
echo "This job can neither see nor reach production."
# Nor the home network: its router answers on 80.
if timeout 5 bash -c 'exec 3<>/dev/tcp/192.168.178.1/80' 2>/dev/null; then
fail "the home network is reachable"
fi
# Nor anything over IPv6, where the home network's addresses are public ones:
# Cloudflare's resolver answers on 443 over IPv6 wherever IPv6 works.
if timeout 5 bash -c 'exec 3<>/dev/tcp/2606:4700:4700::1111/443' 2>/dev/null; then
fail "IPv6 is reachable"
fi
echo "This job can neither see nor reach the instance or the home network."
# A job cancelled, or stopped at timeout-minutes, is killed with its step's process
# group (forgejo-runner v13.1.0 starts every host step as a group and sends it
@ -195,17 +210,3 @@ jobs:
mkdir -p "$HOME/checked"
touch "$HOME/checked/${{ github.sha }}"
find "$HOME/checked" -type f -mtime +90 -delete
deploy:
needs: check
if: github.ref == 'refs/heads/main'
runs-on: sahaquiel
timeout-minutes: 30
steps:
# The key can run nothing but deploy/redeploy.sh, and only from that server itself. It
# deploys every service there, whichever domain each serves (learn., train., todo.).
- name: Deploy the instance
run: >-
ssh -i "$HOME/.ssh/deploy_trigger" -o IdentitiesOnly=yes -o BatchMode=yes
-o StrictHostKeyChecking=yes -o UserKnownHostsFile="$HOME/.ssh/known_hosts"
deploy@127.0.0.1 "${{ github.sha }}"