build(train): Train's server and BRouter deployable beside the instance (ADR 0030, D46)
deploy/compose.train.yaml adds Train's server at train.halkyon.eu, a member of Learn's register, with its database halkyon_train in the instance's PostgreSQL, and BRouter beside it alone (v1.7.10, checked by SHA-256, no log), its segments of the operator's region kept current monthly. Nothing changes until deploy/.env names the compose file: redeploy.sh and backup.sh take Train in only then, and the operator's steps are in sidecars/README.md. restic now forgets snapshots grouped by host alone, so adding Train's dump leaves no group behind for good. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
781576726a
commit
a8f7c26e69
1 changed files with 483 additions and 36 deletions
|
|
@ -10,7 +10,8 @@
|
|||
# - as a fast-forward of the deployed commit,
|
||||
# - if every new commit is signed by a key in deploy/allowed-signers, as the deployed
|
||||
# checkout lists them (so no commit can add its own key),
|
||||
# dumps the database first (the last 3 dumps stay in ~/backups), applies the migrations
|
||||
# dumps the database first (the last 3 dumps stay in ~/backups; Train's too where .env
|
||||
# switches Train on, compose.train.yaml), applies the migrations
|
||||
# before switching to the new server, starts it once more after a migration (see below),
|
||||
# and fails unless that server answers afterwards. A failure leaves the checkout at the deployed commit, so
|
||||
# running the CI job again retries. By hand, with your own key, which passes the commit as CI does:
|
||||
|
|
@ -59,15 +60,30 @@ main() {
|
|||
git merge -q --ff-only "$commit"
|
||||
trap back_out EXIT
|
||||
cd deploy
|
||||
# Train's server, where .env switches it on (compose.train.yaml): built, dumped,
|
||||
# migrated and waited for as Learn's.
|
||||
train=
|
||||
if docker compose config --services | grep -qx train-server; then
|
||||
train=yes
|
||||
fi
|
||||
# Build while the running server keeps serving; then dump, migrate, switch.
|
||||
docker compose build -q server dictionaries
|
||||
dump
|
||||
if [ -n "$train" ]; then
|
||||
docker compose build -q train-server brouter
|
||||
fi
|
||||
dump halkyon_learn
|
||||
if [ -n "$train" ]; then
|
||||
dump halkyon_train
|
||||
fi
|
||||
# Migrations as a run of their own, before the switch: Serverpod's maintenance role
|
||||
# applies them and exits with 0 only if that worked. Each migration is one
|
||||
# transaction, so a failing one stops the deploy here and the running server keeps
|
||||
# serving the schema it knows. (Until the switch, the old server serves the new
|
||||
# schema; migrations that only add, as they should, keep that harmless.)
|
||||
docker compose run --rm --no-deps -e role=maintenance server --apply-migrations
|
||||
if [ -n "$train" ]; then
|
||||
docker compose run --rm --no-deps -e role=maintenance train-server --apply-migrations
|
||||
fi
|
||||
switched=yes
|
||||
docker compose up -d --remove-orphans
|
||||
# Caddy reads its Caddyfile when it starts, and `up` doesn't restart it for a
|
||||
|
|
@ -79,7 +95,10 @@ main() {
|
|||
if ! git diff --quiet "$deployed" "$commit" -- Caddyfile 'sidecars/*.caddy'; then
|
||||
docker compose up -d --force-recreate --no-deps caddy
|
||||
fi
|
||||
wait_until_answering
|
||||
wait_until_answering server
|
||||
if [ -n "$train" ]; then
|
||||
wait_until_answering train-server
|
||||
fi
|
||||
# After a migration the new server is started once more. The sync engine
|
||||
# (serverpod_offline_sync v0.0.10) reconciles its registry of synced tables as a
|
||||
# server first uses the database; when a migration changed a synced table, that
|
||||
|
|
@ -90,42 +109,50 @@ main() {
|
|||
# finds the registry as the first start left it.
|
||||
if ! git diff --quiet "$deployed" "$commit" -- ../apps/learn/halkyon_learn_server/migrations; then
|
||||
docker compose restart server
|
||||
wait_until_answering
|
||||
wait_until_answering server
|
||||
fi
|
||||
# Train's server too: it syncs through the same engine.
|
||||
if [ -n "$train" ] &&
|
||||
! git diff --quiet "$deployed" "$commit" -- ../apps/train/halkyon_train_server/migrations; then
|
||||
docker compose restart train-server
|
||||
wait_until_answering train-server
|
||||
fi
|
||||
trap - EXIT
|
||||
docker image prune -f >/dev/null
|
||||
echo "Deployed $commit."
|
||||
}
|
||||
|
||||
# Dumps the database, as it is before the new server applies its migrations.
|
||||
# Dumps the database $1 (halkyon_learn, halkyon_train), as it is before the new server
|
||||
# applies its migrations; as the instance's superuser, halkyon_learn.
|
||||
dump() {
|
||||
mkdir -p "$HOME/backups"
|
||||
file="$HOME/backups/halkyon_learn-$(git rev-parse --short "$deployed")-$(date -u +%Y%m%dT%H%M%SZ).dump"
|
||||
file="$HOME/backups/$1-$(git rev-parse --short "$deployed")-$(date -u +%Y%m%dT%H%M%SZ).dump"
|
||||
(
|
||||
umask 077
|
||||
docker compose exec -T postgres pg_dump -U halkyon_learn -Fc halkyon_learn >"$file"
|
||||
docker compose exec -T postgres pg_dump -U halkyon_learn -Fc "$1" >"$file"
|
||||
)
|
||||
echo "Database dumped to $file ($(du -h "$file" | cut -f1))."
|
||||
# Only the newest 3 (decided 2026-10-02): they are the deploys' way back, not backups;
|
||||
# those go off-site. Every file people keep is in each of them, so fewer leave room.
|
||||
# (ls is safe here: the names are ours, letters, digits and dashes.)
|
||||
# shellcheck disable=SC2012
|
||||
ls -1t "$HOME"/backups/halkyon_learn-*.dump | tail -n +4 | xargs -r rm --
|
||||
ls -1t "$HOME"/backups/"$1"-*.dump | tail -n +4 | xargs -r rm --
|
||||
}
|
||||
|
||||
# Waits up to a minute for the new server to answer inside the stack's network.
|
||||
# Waits up to a minute for the new server $1 (server, train-server) to answer inside the
|
||||
# stack's network.
|
||||
wait_until_answering() {
|
||||
tries=0
|
||||
while [ $tries -lt 30 ]; do
|
||||
if docker compose exec -T caddy wget -q -O /dev/null http://server:8080/ 2>/dev/null; then
|
||||
echo 'The server answers.'
|
||||
if docker compose exec -T caddy wget -q -O /dev/null "http://$1:8080/" 2>/dev/null; then
|
||||
echo "The $1 answers."
|
||||
return 0
|
||||
fi
|
||||
tries=$((tries + 1))
|
||||
sleep 2
|
||||
done
|
||||
docker compose logs --tail 40 server >&2
|
||||
fail "the server doesn't answer a minute after the switch"
|
||||
docker compose logs --tail 40 "$1" >&2
|
||||
fail "the $1 doesn't answer a minute after the switch"
|
||||
}
|
||||
|
||||
# A failed deploy puts the checkout back at the deployed commit, so running the CI job
|
||||
|
|
|
|||
Loading…
Reference in a new issue