n8n 2.x disables n8n-nodes-base.executeCommand by default (nodes.exclude), so the first workflow shape could not activate: 'Unrecognized node type'. Replace the Execute Command node with the SSH node, which is enabled, and keep the restricted trigger key: the server forced command runs exactly scripts/morning-brief.sh. - deploy/n8n-morning-brief.json: schedule + optional manual trigger -> SSH node -> Code check that makes a non-zero exit red (FR-12). - deploy/import-n8n-morning-brief.sh: builds the sshPrivateKey credential from the key (id schoolScanKey, mode-0600 temp, removed after import), imports, publishes, restarts once, and confirms activation in the n8n log. - scripts/morning-brief.sh: default IMAGE 0.6.1-phase8 so the scheduled job uses the deployed image. - docs/phase-7a-n8n-schedule.md, deploy/DEPLOYMENT.md: document the design and the throwaway-n8n rehearsal (activation + success + failure path).
144 lines
5.2 KiB
Bash
Executable File
144 lines
5.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Phase 7a (AUD-115): import + publish the 6:00am workflow in n8n.
|
|
#
|
|
# n8n on .13 is a shared family service. Run this only after the change is
|
|
# approved. It:
|
|
# 1. imports one SSH credential built from the restricted trigger key,
|
|
# 2. imports one workflow (schedule -> SSH -> failure check),
|
|
# 3. publishes the workflow,
|
|
# 4. restarts n8n once so the 06:00 schedule registers,
|
|
# 5. verifies the workflow is active in the n8n log, not just in the database.
|
|
#
|
|
# It does NOT run the workflow. The first real run is the next 06:00 trigger,
|
|
# or `docker exec n8n n8n execute --id=schoolMorningBrf` by hand (real send).
|
|
#
|
|
# The SSH credential holds the private half of the restricted key. It is built
|
|
# into a mode-0600 temp file, copied into the container, imported, and removed.
|
|
# It is never written into this repository.
|
|
#
|
|
# Why not an Execute Command node? n8n 2.x disables `n8n-nodes-base.executeCommand`
|
|
# by default (`nodes.exclude`), so such a workflow cannot activate. The SSH node
|
|
# is enabled, and the restricted key keeps it to one command.
|
|
#
|
|
# Usage:
|
|
# deploy/import-n8n-morning-brief.sh # dry run, print the plan
|
|
# deploy/import-n8n-morning-brief.sh --apply
|
|
# deploy/import-n8n-morning-brief.sh --apply --no-restart
|
|
#
|
|
# Environment overrides:
|
|
# CONTAINER n8n container name (default n8n)
|
|
# WORKFLOW workflow json path (default <repo>/deploy/n8n-morning-brief.json)
|
|
# KEY private key path (default ~/.ssh/id_ed25519_school_scan)
|
|
# SSH_HOST host the brief runs on (default 192.168.20.13)
|
|
# SSH_USER user on that host (default sam)
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
CONTAINER="${CONTAINER:-n8n}"
|
|
WORKFLOW="${WORKFLOW:-$REPO_ROOT/deploy/n8n-morning-brief.json}"
|
|
KEY="${KEY:-$HOME/.ssh/id_ed25519_school_scan}"
|
|
SSH_HOST="${SSH_HOST:-192.168.20.13}"
|
|
SSH_USER="${SSH_USER:-sam}"
|
|
|
|
WF_ID="schoolMorningBrf"
|
|
WF_NAME="Compass / NHS — 6:00am morning brief"
|
|
CRED_ID="schoolScanKey"
|
|
CRED_NAME="school-scan restricted key"
|
|
|
|
IN_CONTAINER_WF="/tmp/n8n-morning-brief.json"
|
|
IN_CONTAINER_CRED="/tmp/n8n-school-scan-cred.json"
|
|
|
|
APPLY=0
|
|
RESTART=1
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--apply) APPLY=1 ;;
|
|
--no-restart) RESTART=0 ;;
|
|
-h|--help) sed -n '2,30p' "$0"; exit 0 ;;
|
|
*) echo "unknown argument: $arg" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -f "$WORKFLOW" ]] || { echo "workflow not found: $WORKFLOW" >&2; exit 1; }
|
|
|
|
n8n_cli() { docker exec "$CONTAINER" n8n "$@"; }
|
|
|
|
echo "workflow file : $WORKFLOW"
|
|
echo "n8n container : $CONTAINER"
|
|
echo "key : $KEY"
|
|
echo "ssh target : $SSH_USER@$SSH_HOST"
|
|
echo "restart n8n : $([[ "$RESTART" -eq 1 ]] && echo yes || echo no)"
|
|
|
|
if [[ "$APPLY" -eq 0 ]]; then
|
|
echo
|
|
echo "DRY RUN — no change to n8n. Re-run with --apply."
|
|
echo "steps:"
|
|
echo " 1. build the SSH credential from $KEY and import it (id $CRED_ID)"
|
|
echo " 2. copy the workflow into the container"
|
|
echo " 3. n8n import:workflow"
|
|
echo " 4. n8n publish:workflow --id=$WF_ID"
|
|
echo " 5. $([[ "$RESTART" -eq 1 ]] && echo 'docker restart n8n (≈10s)' || echo 'skip restart')"
|
|
echo " 6. confirm the n8n log shows the workflow activated"
|
|
exit 0
|
|
fi
|
|
|
|
[[ -f "$KEY" ]] || { echo "private key not found: $KEY (run deploy/setup-n8n-scan-key.sh --apply)" >&2; exit 1; }
|
|
|
|
CRED_TMP="$(mktemp)"
|
|
AUTH_TMP="$(mktemp)"
|
|
cleanup() { rm -f "$CRED_TMP" "$AUTH_TMP"; docker exec "$CONTAINER" rm -f "$IN_CONTAINER_CRED" "$IN_CONTAINER_WF" >/dev/null 2>&1 || true; }
|
|
trap cleanup EXIT
|
|
|
|
# 1. Build the credential JSON without ever printing the key.
|
|
umask 077
|
|
python3 - "$KEY" "$CRED_ID" "$CRED_NAME" "$SSH_HOST" "$SSH_USER" > "$CRED_TMP" <<'PY'
|
|
import json, sys, pathlib
|
|
key_path, cred_id, cred_name, host, user = sys.argv[1:6]
|
|
key = pathlib.Path(key_path).read_text()
|
|
json.dump([{
|
|
"id": cred_id,
|
|
"name": cred_name,
|
|
"type": "sshPrivateKey",
|
|
"data": {"host": host, "port": 22, "username": user, "privateKey": key, "passphrase": ""},
|
|
}], sys.stdout, indent=2)
|
|
PY
|
|
|
|
echo
|
|
echo "== 1. import the SSH credential =="
|
|
docker exec -i "$CONTAINER" sh -c "cat > $IN_CONTAINER_CRED" < "$CRED_TMP"
|
|
n8n_cli import:credentials --input="$IN_CONTAINER_CRED"
|
|
|
|
echo "== 2. copy the workflow into the container =="
|
|
docker exec -i "$CONTAINER" sh -c "cat > $IN_CONTAINER_WF" < "$WORKFLOW"
|
|
|
|
echo "== 3. import =="
|
|
n8n_cli import:workflow --input="$IN_CONTAINER_WF"
|
|
|
|
echo "== 4. publish =="
|
|
n8n_cli publish:workflow --id="$WF_ID"
|
|
|
|
echo "== 5. restart =="
|
|
if [[ "$RESTART" -eq 1 ]]; then
|
|
docker restart "$CONTAINER" >/dev/null
|
|
echo "n8n restarted; waiting for the API to answer…"
|
|
for _ in $(seq 1 30); do
|
|
if curl -s -o /dev/null --max-time 3 "http://127.0.0.1:5678/healthz"; then
|
|
echo "n8n is up"
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
sleep 3
|
|
fi
|
|
|
|
echo "== 6. verify activation in the log =="
|
|
if docker logs "$CONTAINER" 2>&1 | grep -q "Activated workflow \"$WF_NAME\""; then
|
|
echo "activated: $WF_NAME"
|
|
else
|
|
echo "the n8n log does not show an activation for '$WF_NAME' yet" >&2
|
|
docker logs "$CONTAINER" 2>&1 | grep -iE "schoolMorningBrf|$WF_NAME" | tail -5 >&2 || true
|
|
echo "check: docker logs $CONTAINER | grep -i 'did fail'" >&2
|
|
exit 1
|
|
fi
|
|
n8n_cli list:workflow --active=true | grep "$WF_ID" || true
|