deploy: adapt scripts + compose to the monorepo host checkout
/opt/thermograph becomes a monorepo checkout: deploy.sh gains INFRA_DIR (git ops at the root, compose work cd'd into infra/), lock/tags/render paths move under infra/deploy/, image-path defaults become emi/thermograph/backend| frontend across compose, stack, and the tag-prune. docker-compose.yml pins name: thermograph -- without it compose run from .../infra derives project "infra" and recreates the whole stack beside the running one; deploy-dev.sh pins COMPOSE_PROJECT_NAME=thermograph-dev (env wins over the file key) to keep LAN dev's separate project.
This commit is contained in:
parent
7b2db07722
commit
2e753f2c6f
5 changed files with 52 additions and 30 deletions
|
|
@ -2,7 +2,7 @@
|
|||
# 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 THIS repo (thermograph-infra),
|
||||
# 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
|
||||
|
|
@ -44,6 +44,12 @@ export APP_DIR BRANCH
|
|||
# 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}"
|
||||
|
||||
# deploy.sh needs POSTGRES_PASSWORD to interpolate the compose file (both to init
|
||||
# the db container and to build backend's THERMOGRAPH_DATABASE_URL) -- normally
|
||||
# supplied by /etc/thermograph.env via render-secrets.sh's SOPS render, but dev has
|
||||
|
|
|
|||
|
|
@ -6,27 +6,30 @@
|
|||
# and you can run it by hand too.
|
||||
#
|
||||
# # roll just the backend onto a specific image:
|
||||
# ssh deploy@vps 'SERVICE=backend BACKEND_IMAGE_TAG=sha-<12hex> /opt/thermograph/deploy/deploy.sh'
|
||||
# ssh deploy@vps 'SERVICE=backend BACKEND_IMAGE_TAG=sha-<12hex> /opt/thermograph/infra/deploy/deploy.sh'
|
||||
# # roll just the frontend:
|
||||
# ssh deploy@vps 'SERVICE=frontend FRONTEND_IMAGE_TAG=sha-<12hex> /opt/thermograph/deploy/deploy.sh'
|
||||
# ssh deploy@vps 'SERVICE=frontend FRONTEND_IMAGE_TAG=sha-<12hex> /opt/thermograph/infra/deploy/deploy.sh'
|
||||
# # bring the whole stack up (both tags required):
|
||||
# ssh deploy@vps 'SERVICE=all BACKEND_IMAGE_TAG=sha-<a> FRONTEND_IMAGE_TAG=sha-<b> /opt/thermograph/deploy/deploy.sh'
|
||||
# ssh deploy@vps 'SERVICE=all BACKEND_IMAGE_TAG=sha-<a> FRONTEND_IMAGE_TAG=sha-<b> /opt/thermograph/infra/deploy/deploy.sh'
|
||||
#
|
||||
# FE/BE CI-CD split: backend and frontend are published from separate repos as
|
||||
# separate images (emi/thermograph-backend/app, emi/thermograph-frontend/app),
|
||||
# separate images (emi/thermograph/backend, emi/thermograph/frontend),
|
||||
# so a deploy targets ONE service and leaves the other's running container +
|
||||
# tag untouched. Each service's live tag is persisted host-side in
|
||||
# deploy/.image-tags.env (untracked -- survives the git reset below) so a
|
||||
# single-service roll re-renders compose with BOTH services' real tags and
|
||||
# never accidentally recreates or downgrades the sibling.
|
||||
#
|
||||
# This checkout is thermograph-infra, not an app repo: BRANCH is this repo's
|
||||
# This checkout is the monorepo (compose + deploy live under infra/), not an app-only checkout: BRANCH is this repo's
|
||||
# branch (compose files, db init, the secrets vault, this script itself);
|
||||
# the *_IMAGE_TAG values are the separately-published app images to run -- the
|
||||
# two axes are independent and rarely change together.
|
||||
set -euo pipefail
|
||||
|
||||
APP_DIR="${APP_DIR:-/opt/thermograph}"
|
||||
# Monorepo layout: git operations act on the checkout root ($APP_DIR); all
|
||||
# compose files and deploy assets live under infra/.
|
||||
INFRA_DIR="$APP_DIR/infra"
|
||||
BRANCH="${BRANCH:-main}"
|
||||
# Which service this deploy rolls: backend | frontend | all. Defaults to `all`
|
||||
# (a full-stack bring-up) so a by-hand run with both tags still works; the
|
||||
|
|
@ -49,7 +52,7 @@ esac
|
|||
# inherited across the self re-exec below, so the lock spans the whole run;
|
||||
# -w 600 bounds the wait (a deploy holding the lock >10 min is already
|
||||
# broken), after which this exits non-zero and the CI job fails loudly.
|
||||
DEPLOY_LOCK="$APP_DIR/deploy/.deploy.lock"
|
||||
DEPLOY_LOCK="$INFRA_DIR/deploy/.deploy.lock"
|
||||
if [ -z "${DEPLOY_SH_FLOCKED:-}" ]; then
|
||||
export DEPLOY_SH_FLOCKED=1
|
||||
exec flock -w 600 "$DEPLOY_LOCK" "$0" "$@"
|
||||
|
|
@ -66,10 +69,10 @@ fi
|
|||
# after which deploy.sh re-execs), so a missing helper simply falls back to the
|
||||
# existing /etc/thermograph.env. Then source it so a by-hand run interpolates the
|
||||
# same as the systemd unit does. See deploy/render-secrets.sh + deploy/secrets/.
|
||||
if [ -f "$APP_DIR/deploy/render-secrets.sh" ]; then
|
||||
if [ -f "$INFRA_DIR/deploy/render-secrets.sh" ]; then
|
||||
# shellcheck source=deploy/render-secrets.sh
|
||||
. "$APP_DIR/deploy/render-secrets.sh"
|
||||
render_thermograph_secrets "$APP_DIR"
|
||||
. "$INFRA_DIR/deploy/render-secrets.sh"
|
||||
render_thermograph_secrets "$INFRA_DIR"
|
||||
fi
|
||||
set -a; . /etc/thermograph.env 2>/dev/null || true; set +a
|
||||
|
||||
|
|
@ -120,9 +123,15 @@ fi
|
|||
# freshly-pulled one, and the SERVICE/tag contract passes through unchanged --
|
||||
# the app repos' workflows never need to know which mode a host runs.
|
||||
if [ "$(cat /etc/thermograph/deploy-mode 2>/dev/null || true)" = "stack" ]; then
|
||||
exec bash "$APP_DIR/deploy/stack/deploy-stack.sh"
|
||||
exec bash "$INFRA_DIR/deploy/stack/deploy-stack.sh"
|
||||
fi
|
||||
|
||||
# Monorepo: run every docker compose command from infra/ (the git operations
|
||||
# above ran at the checkout root). The compose project name is pinned in the
|
||||
# file itself (`name: thermograph`), so moving the working directory does NOT
|
||||
# rename the project and recreate the stack under a second name.
|
||||
cd "$INFRA_DIR"
|
||||
|
||||
# Registry-pull cutover: pull the image each app repo's build-push.yml already
|
||||
# built and pushed, instead of building in place. This checkout is
|
||||
# thermograph-infra, not an app repo, so there's no "current commit" to derive
|
||||
|
|
@ -137,7 +146,7 @@ export REGISTRY_HOST BACKEND_IMAGE_PATH FRONTEND_IMAGE_PATH
|
|||
# bare `local` that would recreate/downgrade it). The incoming env for the
|
||||
# service being deployed then overrides its line below. This file is untracked
|
||||
# (see .gitignore), so `git reset --hard` above leaves it in place.
|
||||
TAGS_FILE="$APP_DIR/deploy/.image-tags.env"
|
||||
TAGS_FILE="$INFRA_DIR/deploy/.image-tags.env"
|
||||
# Capture the tags the caller explicitly passed BEFORE sourcing -- they must
|
||||
# WIN. The file only supplies the *sibling's* last-known tag; sourcing it
|
||||
# unconditionally would clobber an incoming tag (e.g. a frontend deploy whose
|
||||
|
|
@ -277,8 +286,8 @@ fi
|
|||
# roll never garbage-collects the image a rollback would need; docker also
|
||||
# refuses to remove an image any container still uses.
|
||||
echo "==> Pruning old app-image tags"
|
||||
_be_repo="${REGISTRY_HOST}/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}"
|
||||
_fe_repo="${REGISTRY_HOST}/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}"
|
||||
_be_repo="${REGISTRY_HOST}/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}"
|
||||
_fe_repo="${REGISTRY_HOST}/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}"
|
||||
docker images --format '{{.Repository}}:{{.Tag}}' \
|
||||
| grep -E "^(${_be_repo}|${_fe_repo}):" \
|
||||
| grep -v -e "^${_be_repo}:${BACKEND_IMAGE_TAG}$" -e "^${_fe_repo}:${FRONTEND_IMAGE_TAG}$" \
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@ export STACK_NAME
|
|||
# Render (SOPS) + source /etc/thermograph.env exactly like deploy.sh, then
|
||||
# install the uid-10001-readable copy the tasks' env-entrypoint shim sources.
|
||||
# 10001 = the app images' `thermograph` user; the file is 0400 to that uid.
|
||||
if [ -f "$APP_DIR/deploy/render-secrets.sh" ]; then
|
||||
if [ -f "$APP_DIR/infra/deploy/render-secrets.sh" ]; then
|
||||
# shellcheck source=deploy/render-secrets.sh
|
||||
. "$APP_DIR/deploy/render-secrets.sh"
|
||||
render_thermograph_secrets "$APP_DIR"
|
||||
. "$APP_DIR/infra/deploy/render-secrets.sh"
|
||||
render_thermograph_secrets "$APP_DIR/infra"
|
||||
fi
|
||||
set -a; . /etc/thermograph.env 2>/dev/null || true; set +a
|
||||
sudo install -o 10001 -g 0 -m 0400 /etc/thermograph.env /etc/thermograph/stack.env \
|
||||
|
|
@ -62,7 +62,7 @@ sudo install -o 10001 -g 0 -m 0400 /etc/thermograph.env /etc/thermograph/stack.e
|
|||
# supplies the sibling. Stack mode keeps its own file so test/real never mix.
|
||||
REGISTRY_HOST="${REGISTRY_HOST:-git.thermograph.org}"
|
||||
export REGISTRY_HOST
|
||||
TAGS_FILE="$APP_DIR/deploy/.stack-image-tags.env"
|
||||
TAGS_FILE="$APP_DIR/infra/deploy/.stack-image-tags.env"
|
||||
_incoming_backend="${BACKEND_IMAGE_TAG:-}"
|
||||
_incoming_frontend="${FRONTEND_IMAGE_TAG:-}"
|
||||
if [ -f "$TAGS_FILE" ]; then set -a; . "$TAGS_FILE"; set +a; fi
|
||||
|
|
@ -77,8 +77,8 @@ case "$SERVICE" in
|
|||
esac
|
||||
export BACKEND_IMAGE_TAG="${BACKEND_IMAGE_TAG:-local}"
|
||||
export FRONTEND_IMAGE_TAG="${FRONTEND_IMAGE_TAG:-local}"
|
||||
BACKEND_IMAGE="$REGISTRY_HOST/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}:$BACKEND_IMAGE_TAG"
|
||||
FRONTEND_IMAGE="$REGISTRY_HOST/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}:$FRONTEND_IMAGE_TAG"
|
||||
BACKEND_IMAGE="$REGISTRY_HOST/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}:$BACKEND_IMAGE_TAG"
|
||||
FRONTEND_IMAGE="$REGISTRY_HOST/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}:$FRONTEND_IMAGE_TAG"
|
||||
|
||||
# --- timescale image pin ---------------------------------------------------------
|
||||
# Hazard #7: the db image under an existing volume must never drift. Resolve
|
||||
|
|
@ -138,7 +138,7 @@ FIRST_DEPLOY_MIGRATE=0
|
|||
docker service inspect "${STACK_NAME}_db" >/dev/null 2>&1 || FIRST_DEPLOY_MIGRATE=1
|
||||
if [ "$SERVICE" = "all" ] || ! docker service inspect "${STACK_NAME}_web" >/dev/null 2>&1; then
|
||||
echo "==> docker stack deploy ($STACK_NAME)"
|
||||
docker stack deploy --with-registry-auth -c "$APP_DIR/deploy/stack/thermograph-stack.yml" "$STACK_NAME"
|
||||
docker stack deploy --with-registry-auth -c "$APP_DIR/infra/deploy/stack/thermograph-stack.yml" "$STACK_NAME"
|
||||
# First-ever deploy ran no migrate above (db didn't exist): wait for db,
|
||||
# migrate, then force web/worker to restart cleanly against the schema.
|
||||
if [ "${FIRST_DEPLOY_MIGRATE:-0}" = "1" ]; then
|
||||
|
|
@ -179,7 +179,7 @@ if ! docker ps --format '{{.Names}}' | grep -qx "$LB_NAME"; then
|
|||
docker run -d --name "$LB_NAME" --restart unless-stopped \
|
||||
--network "$NET" \
|
||||
-p "127.0.0.1:${LB_HTTP_PORT}:8137" -p "127.0.0.1:${LB_FE_PORT}:8080" \
|
||||
-v "$APP_DIR/deploy/stack/lb/Caddyfile:/etc/caddy/Caddyfile:ro" \
|
||||
-v "$APP_DIR/infra/deploy/stack/lb/Caddyfile:/etc/caddy/Caddyfile:ro" \
|
||||
caddy:2-alpine >/dev/null
|
||||
fi
|
||||
|
||||
|
|
@ -205,8 +205,8 @@ FRONTEND_IMAGE_TAG=$FRONTEND_IMAGE_TAG
|
|||
EOF
|
||||
|
||||
# GC superseded app-image tags (keep the running pair), same as deploy.sh.
|
||||
_be_repo="$REGISTRY_HOST/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}"
|
||||
_fe_repo="$REGISTRY_HOST/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}"
|
||||
_be_repo="$REGISTRY_HOST/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}"
|
||||
_fe_repo="$REGISTRY_HOST/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}"
|
||||
docker images --format '{{.Repository}}:{{.Tag}}' \
|
||||
| grep -E "^(${_be_repo}|${_fe_repo}):" \
|
||||
| grep -v -e "^${_be_repo}:${BACKEND_IMAGE_TAG}$" -e "^${_fe_repo}:${FRONTEND_IMAGE_TAG}$" \
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ services:
|
|||
condition: on-failure
|
||||
|
||||
web:
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}:${BACKEND_IMAGE_TAG:?required}
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}:${BACKEND_IMAGE_TAG:?required}
|
||||
entrypoint: ["/host/env-entrypoint.sh"]
|
||||
environment:
|
||||
THERMOGRAPH_DATABASE_URL: postgresql+asyncpg://thermograph:${POSTGRES_PASSWORD}@db:5432/thermograph
|
||||
|
|
@ -105,7 +105,7 @@ services:
|
|||
failure_action: rollback
|
||||
|
||||
worker:
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}:${BACKEND_IMAGE_TAG:?required}
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}:${BACKEND_IMAGE_TAG:?required}
|
||||
entrypoint: ["/host/env-entrypoint.sh"]
|
||||
environment:
|
||||
THERMOGRAPH_DATABASE_URL: postgresql+asyncpg://thermograph:${POSTGRES_PASSWORD}@db:5432/thermograph
|
||||
|
|
@ -139,7 +139,7 @@ services:
|
|||
condition: on-failure
|
||||
|
||||
frontend:
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}:${FRONTEND_IMAGE_TAG:?required}
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}:${FRONTEND_IMAGE_TAG:?required}
|
||||
entrypoint: ["/host/env-entrypoint.sh"]
|
||||
environment:
|
||||
THERMOGRAPH_BASE: /
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@
|
|||
# so `docker compose up` can interpolate it. It is used BOTH to initialize the db
|
||||
# container and to build backend's THERMOGRAPH_DATABASE_URL below.
|
||||
|
||||
# Pin the compose project name: the monorepo layout runs compose from
|
||||
# /opt/thermograph/infra, and without an explicit name the project would be
|
||||
# derived from that directory ("infra") -- silently a NEW project, recreating
|
||||
# the whole stack beside the running one. LAN dev overrides this via
|
||||
# COMPOSE_PROJECT_NAME=thermograph-dev (the env var always wins over this key).
|
||||
name: thermograph
|
||||
|
||||
services:
|
||||
db:
|
||||
# TimescaleDB on PostgreSQL 18 (the stock image already sets
|
||||
|
|
@ -90,10 +97,10 @@ services:
|
|||
# No `build:` here -- infra holds no Dockerfile; each service's Dockerfile
|
||||
# lives in its own app repo. deploy.sh sets BACKEND_IMAGE_TAG to the SHA
|
||||
# build-push.yml pushed for the backend commit being deployed; local dev
|
||||
# builds `emi/thermograph-backend/app:local` from the backend repo (see
|
||||
# builds `emi/thermograph/backend:local` from the backend repo (see
|
||||
# infra Makefile) and this pulls/uses it. BACKEND_IMAGE_PATH/TAG are
|
||||
# independent of the frontend's, so the two services deploy separately.
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph-backend/app}:${BACKEND_IMAGE_TAG:-local}
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${BACKEND_IMAGE_PATH:-emi/thermograph/backend}:${BACKEND_IMAGE_TAG:-local}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
|
@ -155,7 +162,7 @@ services:
|
|||
# from its own Dockerfile (which starts `uvicorn app:app` directly -- no
|
||||
# THERMOGRAPH_SERVICE_ROLE process-picking anymore). Independent
|
||||
# FRONTEND_IMAGE_TAG so a frontend deploy never disturbs the backend's tag.
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${FRONTEND_IMAGE_PATH:-emi/thermograph-frontend/app}:${FRONTEND_IMAGE_TAG:-local}
|
||||
image: ${REGISTRY_HOST:-git.thermograph.org}/${FRONTEND_IMAGE_PATH:-emi/thermograph/frontend}:${FRONTEND_IMAGE_TAG:-local}
|
||||
# Its own register() fetches the IndexNow key from backend at boot -- must
|
||||
# wait for a real, healthy backend, not just a started container.
|
||||
depends_on:
|
||||
|
|
|
|||
Loading…
Reference in a new issue