From 705c17c2b4763bcf5e6a3ebeb52c31db2505b89b Mon Sep 17 00:00:00 2001 From: Matt Spinola Date: Mon, 27 Jul 2026 19:18:49 -0400 Subject: [PATCH] docs: add verify-replicas.sh example for cross-replica sync verification Templated version of the check used to confirm the producer push reaches every replica. Reads a local store and a remote one (over SSH) in one pass, keys the pass/fail on each status.json mtime date (rewritten on every producer push), and exits non-zero if either replica did not receive today's push. Host, store paths, cotdata-update paths and SSH key are env-var config with REPLACE_WITH_ markers, matching the other docs/examples/ scripts; no host is baked in. Point the "Verifying a sync worked" section at it, and note the status.json mtime signal and wiring it to launchd/cron after the producer run. Docs only; no code changes. Co-Authored-By: Claude Opus 4.8 --- docs/SYNCING.md | 6 ++++ docs/examples/mac/verify-replicas.sh | 54 ++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100755 docs/examples/mac/verify-replicas.sh diff --git a/docs/SYNCING.md b/docs/SYNCING.md index e5593a8..f3dbe26 100644 --- a/docs/SYNCING.md +++ b/docs/SYNCING.md @@ -269,3 +269,9 @@ even when its data looks fine. If the replica shows entries the producer does not have, the sync is additive rather than mirroring and stale files are accumulating. If it shows *fewer*, the sync has not completed or an exclusion is too broad. + +To automate this across replicas, [`examples/mac/verify-replicas.sh`](examples/mac/verify-replicas.sh) +checks a local store and a remote one (over SSH) in one pass and exits non-zero if either +did not receive today's push. `status.json` is rewritten on every producer push, so its +mtime date is the "did this replica update today" signal. Wire it to launchd/cron a little +after the producer's run. diff --git a/docs/examples/mac/verify-replicas.sh b/docs/examples/mac/verify-replicas.sh new file mode 100755 index 0000000..d9fdea6 --- /dev/null +++ b/docs/examples/mac/verify-replicas.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# verify-replicas.sh — confirm the latest producer run reached BOTH store replicas +# TODAY: one local store and one remote store read over SSH. +# +# How it works: the producer rewrites `status.json` on every push, and the sync +# carries it, so if a replica's status.json mtime date == the day this runs, that +# replica received today's push. Run it AFTER the producer's scheduled run, e.g. +# from launchd/cron. Exits 0 on PASS (both current), 1 on FAIL (names the laggard). +# +# Configure by editing the block below or exporting the vars before calling. +# Markers are plain text (not ) so an unedited copy still parses. +set -uo pipefail + +# ── config ────────────────────────────────────────────────────────────────── +LOCAL_STORE="${LOCAL_STORE:-REPLACE_WITH_LOCAL_STORE}" # e.g. $HOME/code/cotdata_store +LOCAL_CHECK="${LOCAL_CHECK:-REPLACE_WITH_LOCAL_COTDATA_UPDATE}" # cotdata-update path, e.g. .../.venv/bin/cotdata-update +REMOTE="${REMOTE:-REPLACE_WITH_REMOTE}" # ssh target, e.g. deploy@dash.example.com +REMOTE_STORE="${REMOTE_STORE:-REPLACE_WITH_REMOTE_STORE}" # e.g. /srv/cotdata_store +REMOTE_CHECK="${REMOTE_CHECK:-REPLACE_WITH_REMOTE_COTDATA_UPDATE}" # cotdata-update path ON the remote +SSH_KEY="${SSH_KEY:-}" # optional: private key; empty = default key/agent +# ──────────────────────────────────────────────────────────────────────────── + +TODAY=$(date +%F) +SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=8) +[ -n "$SSH_KEY" ] && SSH_OPTS+=(-i "$SSH_KEY") + +pass=0; fail=0 +check_date() { # $1 = status.json mtime date (YYYY-MM-DD), $2 = label + if [ "$1" = "$TODAY" ]; then + echo " PASS: $2 status.json written today ($1)"; pass=$((pass + 1)) + else + echo " FAIL: $2 status.json last written '${1:-unknown}', not today ($TODAY)"; fail=$((fail + 1)) + fi +} + +echo "=== cotdata replica verification — $TODAY ===" +echo +echo "[local] $LOCAL_STORE" +# date -r prints the file's mtime on both macOS (BSD) and Linux (GNU). +check_date "$(date -r "$LOCAL_STORE/status.json" +%F 2>/dev/null)" "local" +COTDATA_STORE="$LOCAL_STORE" "$LOCAL_CHECK" --check 2>&1 | sed 's/^/ /' +echo +echo "[remote] $REMOTE:$REMOTE_STORE" +check_date "$(ssh "${SSH_OPTS[@]}" "$REMOTE" "date -r '$REMOTE_STORE/status.json' +%F" 2>/dev/null)" "remote" +ssh "${SSH_OPTS[@]}" "$REMOTE" "COTDATA_STORE=$REMOTE_STORE $REMOTE_CHECK --check" 2>&1 | sed 's/^/ /' +echo + +if [ "$fail" -eq 0 ] && [ "$pass" -eq 2 ]; then + echo "RESULT: PASS — both replicas received today's producer push." + exit 0 +else + echo "RESULT: FAIL — $fail replica(s) did not update today. Check the producer task and its chained sync." + exit 1 +fi