stacks/ is canonical; stacks-mirror/ is drift snapshot — stop confusing the two

Decision recorded in CLAUDE.md ("Stack tree convention") and memory
(convention_stacks_vs_mirror.md):

  stacks/<stack>/                    canonical / intent. git-tracked.
                                     deploy-stack.sh reads from here.
  stacks-mirror/<host>/<stack>/      snapshot / reality. gitignored.
                                     sync-stacks.sh writes here. Used
                                     for drift inspection only — never
                                     a deploy source.

Bug this fixes: deploy-stack.sh was reading from the mirror, so edits
to stacks/llama-swap/config.yaml never reached ana-ml2. Today's
two new model entries (qwen3.6-35-a3b-heretic + qwen3.6-27b) lived
in the canonical for hours but the deploy reported "in sync" because
the script only diffed mirror vs server.

Changes:
* deploy-stack.sh: source switched from MIRROR_DIR/$HOST/$STACK to
  STACKS_DIR/$STACK. Header comment + error message updated.
* sync-stacks.sh: header explicitly identifies its role as drift
  detection; documents the diff command for comparing canonical vs
  mirror.
* stacks/llama-swap/{config.yaml → conf/config.yaml}: matches the
  deploy mapping (conf/ in canonical → /opt/docker/conf/ on host).
* CLAUDE.md: "Stack mirror (pull / push)" section rewritten as
  "Stack tree convention (canonical vs mirror)" with the role table
  + workflow rules + diff recipe. Layout diagram updated.
This commit is contained in:
vh
2026-04-26 22:07:30 -07:00
parent b48667f33a
commit 6f7bb4885e
4 changed files with 48 additions and 20 deletions
+12 -7
View File
@@ -1,10 +1,15 @@
#!/usr/bin/env bash
# deploy-stack.sh — push a local stacks-mirror dir to a server, with
# per-file diff and confirmation prompt.
# deploy-stack.sh — push a canonical stack from `stacks/` to a server,
# with per-file diff and confirmation prompt.
#
# Source of truth is `stacks/<stack>/` (the canonical, git-tracked tree).
# `stacks-mirror/` is a separate, gitignored snapshot of what's currently
# on each host (pulled by sync-stacks.sh) — it's used for drift detection,
# NOT as the deploy source. See CLAUDE.md "Stack tree convention".
#
# Layout assumed:
# stacks-mirror/<host>/<stack>/<file> → <host>:/opt/docker/compose/<stack>/<file>
# stacks-mirror/<host>/<stack>/conf/<file> → <host>:/opt/docker/conf/<stack>/<file>
# stacks/<stack>/<file> → <host>:/opt/docker/compose/<stack>/<file>
# stacks/<stack>/conf/<file> → <host>:/opt/docker/conf/<stack>/<file>
#
# Secrets / runtime state are never pushed (same exclude list as
# sync-stacks.sh): .env*, acme.json, *.key/crt/pem/pfx, *.sqlite*, *.db,
@@ -32,7 +37,7 @@ fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SERVERS_DIR="$REPO_ROOT/servers"
MIRROR_DIR="$REPO_ROOT/stacks-mirror"
STACKS_DIR="$REPO_ROOT/stacks"
EXCLUDES=(
# Include .env.example / *.env.example templates before the broader
@@ -100,9 +105,9 @@ resolve_target() {
}
TARGET=$(resolve_target "$HOST")
STACK_DIR="$MIRROR_DIR/$HOST/$STACK"
STACK_DIR="$STACKS_DIR/$STACK"
[ -d "$STACK_DIR" ] || { echo "error: $STACK_DIR not found (pull with sync-stacks.sh first)" >&2; exit 2; }
[ -d "$STACK_DIR" ] || { echo "error: $STACK_DIR not found — author the canonical stack first (see stacks/<other>/ for examples)" >&2; exit 2; }
# Collect the two src/dest pairs we need to consider.
PAIRS=() # each entry: "<kind>|<src>|<dest>"
+8 -1
View File
@@ -1,6 +1,13 @@
#!/usr/bin/env bash
# sync-stacks.sh — pull /opt/docker/{compose,conf}/<stack>/ from every
# server into version-controlled `stacks-mirror/<host>/<stack>/`.
# server into the gitignored `stacks-mirror/<host>/<stack>/` snapshot tree.
#
# Role: drift detection. The mirror reflects what's actually running on
# each host RIGHT NOW. The canonical source of truth lives at
# `stacks/<stack>/` (git-tracked), and `deploy-stack.sh` pushes from
# there. Use the mirror to compare what we have to what's deployed:
# diff -ru stacks/<stack>/ stacks-mirror/<host>/<stack>/
# See CLAUDE.md "Stack tree convention" for the full split.
#
# Layout (flat per stack):
# stacks-mirror/<host>/<stack>/ <- mirrors /opt/docker/compose/<stack>/