thermograph/infra/deploy/deploy-dev.sh
emi dde2cdd6e2
Some checks failed
Sync infra to hosts / sync-beta (push) Failing after 13s
Sync infra to hosts / sync-prod (push) Successful in 25s
secrets-guard / encrypted (push) Successful in 22s
Validate observability stack / validate (push) Successful in 27s
shell-lint / shellcheck (push) Successful in 21s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m24s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 1m21s
Deploy backend to beta VPS / deploy (push) Successful in 2m4s
Deploy frontend to beta VPS / deploy (push) Successful in 2m12s
infra: mirror LAN dev secrets under $HOME for snap-confined Docker (#85)
2026-07-25 07:19:18 +00:00

140 lines
8.7 KiB
Bash
Executable file

#!/usr/bin/env bash
# LAN-dev deploy: roll the per-service registry-pull stack (see deploy.sh) onto
# the ~/thermograph-dev overlay instead of prod/beta's loopback-only stack.
#
# This script assumes $APP_DIR is a checkout of the monorepo (deploy assets under infra/),
# the same way /opt/thermograph is on prod/beta. That is the live state: the LAN
# box's ~/thermograph-dev was reprovisioned as an infra checkout during the
# 2026-07-22 cutover (the app monorepo is archived), provision-dev-lan.sh's
# REPO_URL defaults to this repo, and the app repos' deploy-dev.yml workflows
# invoke this script on the thermograph-lan runner. This IS the live LAN-dev
# deploy path.
#
# Design: a thin wrapper around deploy.sh, not a duplicate. deploy.sh already owns
# the entire per-service registry-pull mechanism -- secrets sourcing, docker login,
# the retry-pull loop, --no-deps single-service rolls vs. --remove-orphans `all`,
# .image-tags.env persistence, and the 8137/8080 health checks. None of that is
# dev-specific; the only things LAN dev actually changes are WHERE it deploys
# (a separate checkout + branch) and WHICH compose files are in play (the base
# file plus docker-compose.dev.yml's uncapped/LAN-published overrides). Both are
# expressible as environment (APP_DIR/BRANCH that deploy.sh already reads, and
# docker compose's own COMPOSE_FILE variable), so re-exec'ing deploy.sh with that
# environment set covers it with no forked copy of the pull/roll/health logic to
# drift out of sync. If LAN dev ever needs deploy logic that genuinely diverges
# from prod/beta (not just "different files/directory"), promote this to a
# standalone script at that point rather than growing special cases into deploy.sh.
#
# Usage, mirrors deploy.sh directly:
# # roll just the backend onto a dev-tagged image:
# ssh dev-box 'SERVICE=backend BACKEND_IMAGE_TAG=sha-<12hex> deploy/deploy-dev.sh'
# # bring the whole dev stack up (both tags required, same as deploy.sh):
# ssh dev-box 'SERVICE=all BACKEND_IMAGE_TAG=sha-<a> FRONTEND_IMAGE_TAG=sha-<b> deploy/deploy-dev.sh'
set -euo pipefail
# Dev context: a separate checkout + branch from prod/beta's /opt/thermograph
# (main), so a dev deploy never touches or is touched by the prod/beta one.
APP_DIR="${APP_DIR:-$HOME/thermograph-dev}"
BRANCH="${BRANCH:-dev}"
export APP_DIR BRANCH
# Point every `docker compose` invocation inside deploy.sh at the LAN-dev overlay
# (uncapped CPU, backend published on 0.0.0.0:8137, frontend unpublished -- see
# docker-compose.dev.yml) without deploy.sh needing to know dev exists at all.
# COMPOSE_FILE is docker compose's own env var for this; ':' is the Linux/macOS
# path-list separator it expects (';' only on Windows).
export COMPOSE_FILE="docker-compose.yml:docker-compose.dev.yml"
# Pin the dev project name: docker-compose.yml pins `name: thermograph` for
# prod/beta, but dev's stack has always been the separate "thermograph-dev"
# project (originally derived from this checkout's directory name). The env
# var wins over the compose-file `name:` key, preserving that split.
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-thermograph-dev}"
# --- Secrets -----------------------------------------------------------------
# dev renders from the SOPS vault the same way prod and beta do -- same files, same
# tool, same edit/commit/deploy loop -- with two dev-only adjustments. Both are
# expressed as environment that render-secrets.sh already parameterises, so nothing
# about the rendering logic is forked. See deploy/secrets/README.md.
# (1) dev renders deploy/secrets/dev.yaml ALONE; it does NOT layer common.yaml.
#
# common.yaml is the internet-facing fleet's SHARED PRODUCTION credential set: the
# registry token, both S3 keypairs (the bucket keypair is read-write, and that
# bucket also holds prod's database backups), the VAPID private key that signs push
# to real subscribers, the IndexNow key, the metrics token. Layering it here would
# write all of them in plaintext to /etc/thermograph.env on the box that is also the
# Forgejo CI runner, and hand them to every container in the dev stack through
# `env_file:` -- that is, to whatever unreviewed branch dev happens to be running.
# It would also silently apply THERMOGRAPH_COOKIE_SECURE=1, which is correct for the
# TLS hosts and breaks login on dev's plain-HTTP LAN URL.
#
# dev.yaml is self-contained instead: it carries dev's own copy of everything dev
# genuinely needs, and nothing else.
export THERMOGRAPH_SECRETS_SKIP_COMMON=1
# (1b) Docker on this box is the snap package, confined to $HOME (plus a short
# allowlist) -- it silently can't see /etc/thermograph.env at all, so
# env_file: /etc/thermograph.env resolves to nothing for every container no
# matter how correctly it's rendered. Mirror the render to a path under
# $APP_DIR too; docker-compose.dev.yml points env_file at this copy instead of
# /etc/thermograph.env. Untracked (see infra/.gitignore); re-rendered on every
# deploy, never committed.
export THERMOGRAPH_SECRETS_ENV_FILE_MIRROR="$APP_DIR/infra/deploy/dev-secrets.env"
# (2) The age key is read from wherever it is READABLE, not necessarily
# /etc/thermograph/age.key. render-secrets.sh falls back to `sudo cat` for a
# root-owned 0400 key, and this box has no passwordless sudo -- under the Forgejo
# runner (a systemd --user service, no tty) that sudo cannot prompt, so the
# fleet-standard placement would make every dev deploy fail at the render. Prefer
# the standard path when it is readable; otherwise the operator's own keyring, which
# is where dev's key already lives (this box is the machine `sops` is run on), so
# provisioning dev needs no second copy of the estate's recovery root.
if [ -z "${THERMOGRAPH_AGE_KEY:-}" ] && [ ! -r /etc/thermograph/age.key ] \
&& [ -r "$HOME/.config/sops/age/keys.txt" ]; then
export THERMOGRAPH_AGE_KEY="$HOME/.config/sops/age/keys.txt"
fi
# Fail closed rather than leak. If this box is provisioned to render (an env marker
# AND a key) but the render-secrets.sh in this checkout predates
# THERMOGRAPH_SECRETS_SKIP_COMMON, the render would quietly concatenate common.yaml
# into dev's /etc/thermograph.env -- exactly the leak (1) exists to prevent, with a
# success exit code. Checking a sibling script for the flag is blunt, but the two
# files are versioned together in this same checkout, and the alternative failure
# mode is unreviewed code running with the estate's object-storage keys. Remove this
# once the flag is unconditionally present in render-secrets.sh.
_render_sh="$(dirname "$0")/render-secrets.sh"
_secrets_marker="${THERMOGRAPH_SECRETS_ENV_FILE:-/etc/thermograph/secrets-env}"
_age_key="${THERMOGRAPH_AGE_KEY:-/etc/thermograph/age.key}"
if [ -s "$_secrets_marker" ] && [ -f "$_age_key" ] && [ -f "$_render_sh" ] \
&& ! grep -q 'THERMOGRAPH_SECRETS_SKIP_COMMON' "$_render_sh"; then
echo "!! This box is provisioned to render SOPS secrets (env marker + age key)," >&2
echo "!! but $_render_sh does not honour" >&2
echo "!! THERMOGRAPH_SECRETS_SKIP_COMMON, so it would render common.yaml -- the" >&2
echo "!! fleet's shared PRODUCTION credentials -- into /etc/thermograph.env on the" >&2
echo "!! CI-runner box. Refusing to deploy." >&2
echo "!! Fix: update deploy/render-secrets.sh, or remove $_secrets_marker to fall" >&2
echo "!! back to the un-vaulted dev path." >&2
exit 1
fi
# deploy.sh needs POSTGRES_PASSWORD to interpolate the compose file (both to init the
# db container and to build backend's THERMOGRAPH_DATABASE_URL). On a provisioned dev
# box it arrives from dev.yaml via the render above -- deploy.sh sources
# /etc/thermograph.env AFTER calling the renderer, so the vault value wins over this
# line. This default is the fallback for the un-vaulted paths that remain: a fresh
# box before provisioning, and a bare `make dev-up`. It is the same value dev.yaml
# holds and the value dev's existing pgdata volume was initialized with -- keep the
# two in step, and note that changing it needs an ALTER ROLE against the running
# database as well (see deploy/secrets/README.md), not just an edit.
export POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-thermograph-dev}"
# REGISTRY_TOKEN (for `docker login` in deploy.sh) is deliberately NOT in dev.yaml
# and has no default here. dev pulls with the persistent `docker login` credential
# already in this host's docker config -- the same arrangement the prod/beta CI
# deploy paths use, and the reason deploy.sh's login step is conditional. Putting a
# registry token in dev's vault would only add a second copy of a credential the box
# already has, in a file more processes can read. Export it by hand for a one-off run
# on a box that has never logged in; a genuine auth problem fails loudly at `pull`.
echo "==> LAN-dev deploy: APP_DIR=$APP_DIR BRANCH=$BRANCH COMPOSE_FILE=$COMPOSE_FILE"
exec "$(dirname "$0")/deploy.sh" "$@"