#!/usr/bin/env bash # Phase 7a (AUD-115): prove the 06:00 n8n schedule fired and the briefs were sent. # # Read-only. It never runs the workflow and never sends anything, so it is safe to # call from a monitor or by hand at any time. # # Checks: # 1. the workflow is active in n8n, # 2. the newest scheduled run (mode "trigger") of schoolMorningBrf finished # "success" — a hand run (mode "cli") does not count, # 3. prints the notify rows so the morning's 8 deliveries can be read. # # Exit 0 only when 1 and 2 hold. Exit 1 with a clear "no scheduled run yet" when the # 06:00 trigger has not fired, so the caller can tell "not yet" from "failed". # # Usage: # deploy/verify-n8n-scheduled-run.sh # # Environment overrides: # CONTAINER n8n container name (default n8n) # APP_CONTAINER app container name (default compass-teams-nhs) # N8N_DATA_DIR n8n data dir on the host (default /home/sam/Docker/Containers/n8n_data) # WF_ID workflow id (default schoolMorningBrf) set -euo pipefail CONTAINER="${CONTAINER:-n8n}" APP_CONTAINER="${APP_CONTAINER:-compass-teams-nhs}" N8N_DATA_DIR="${N8N_DATA_DIR:-/home/sam/Docker/Containers/n8n_data}" WF_ID="${WF_ID:-schoolMorningBrf}" DB="$N8N_DATA_DIR/database.sqlite" fail=0 echo "== 1. the workflow is active in n8n ==" if docker exec "$CONTAINER" n8n list:workflow --active=true 2>/dev/null | grep -q "$WF_ID"; then echo "active: $WF_ID" else echo "NOT active: $WF_ID — the 06:00 schedule will not fire" >&2 fail=1 fi echo echo "== 2. the newest scheduled (mode=trigger) execution ==" if [[ ! -f "$DB" ]]; then echo "n8n database not found: $DB (set N8N_DATA_DIR)" >&2 fail=1 elif python3 - "$DB" "$WF_ID" <<'PY' import datetime, sqlite3, sys db, wf = sys.argv[1], sys.argv[2] con = sqlite3.connect(f"file:{db}?mode=ro", uri=True, timeout=8) con.row_factory = sqlite3.Row rows = list(con.execute( "select id, mode, status, startedAt, stoppedAt from execution_entity " "where workflowId=? and mode='trigger' order by id desc limit 3", (wf,))) if not rows: print("no scheduled (mode=trigger) execution yet for", wf) sys.exit(1) aedt = datetime.timezone(datetime.timedelta(hours=11)) def local(ts): for fmt in ("%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S"): try: dt = datetime.datetime.strptime(ts, fmt).replace(tzinfo=datetime.timezone.utc) return dt.astimezone(aedt).strftime("%Y-%m-%d %H:%M %Z") except ValueError: continue return ts for r in rows: print(f" id={r['id']} mode={r['mode']} status={r['status']} started={local(r['startedAt'])}") newest = rows[0] sys.exit(0 if newest["status"] == "success" else 1) PY then echo "the newest scheduled run finished success" else echo "the newest scheduled run is not green (or no scheduled run has happened yet)" >&2 fail=1 fi echo echo "== 3. notify rows (the morning's 8 deliveries) ==" if docker exec "$APP_CONTAINER" /app/school notify status 2>/dev/null | grep -v '^gateway=' | tail -20; then : else echo "could not read notify status from $APP_CONTAINER" >&2 fail=1 fi echo if [[ "$fail" -eq 0 ]]; then echo "OK: the 06:00 schedule is active, its newest scheduled run is green, rows above" else echo "NOT YET / FAILED: see the messages above" >&2 fi exit "$fail"