Auto backup: 2026-02-21 07:01
This commit is contained in:
@@ -75,7 +75,7 @@ openclaw cron list
|
||||
|
||||
**What it does:**
|
||||
- Checks Gitea, n8n, Home Assistant, FreshRSS every 15 minutes
|
||||
- Alerts via Telegram + Gotify when services go down
|
||||
- Alerts via Telegram + ntfy when services go down
|
||||
- Attempts auto-recovery (where possible)
|
||||
- Daily 8 AM health report with uptime stats
|
||||
- Cooldowns prevent spam (1 hour between alerts)
|
||||
|
||||
@@ -18,19 +18,29 @@ if ! git config --global credential.helper &>/dev/null; then
|
||||
echo " Run: git config --global credential.helper store" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# Gotify alert function
|
||||
send_gotify() {
|
||||
# ntfy alert function
|
||||
send_ntfy() {
|
||||
local title="$1"
|
||||
local message="$2"
|
||||
local priority="${3:-0}"
|
||||
local gotify_url="${GOTIFY_URL:-http://runtipi.kangaroo-eel.ts.net:8129}"
|
||||
local gotify_token="${GOTIFY_API_KEY}"
|
||||
|
||||
if [[ -n "$gotify_token" ]]; then
|
||||
curl -s -X POST "$gotify_url/message?token=$gotify_token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"title\":\"$title\",\"message\":\"$message\",\"priority\":$priority}" > /dev/null 2>&1
|
||||
local priority="${3:-4}"
|
||||
local sound="${4:-default}"
|
||||
|
||||
[[ -z "${NTFY_URL:-}" ]] && return 0
|
||||
[[ -z "${NTFY_TOPIC:-}" ]] && return 0
|
||||
|
||||
# Enforce minimum priority (default 4)
|
||||
local minp="${NTFY_MIN_PRIORITY:-4}"
|
||||
if [[ "$priority" =~ ^[0-9]+$ ]] && [[ "$minp" =~ ^[0-9]+$ ]]; then
|
||||
if (( priority < minp )); then
|
||||
priority="$minp"
|
||||
fi
|
||||
fi
|
||||
|
||||
curl -s -X POST "${NTFY_URL%/}/${NTFY_TOPIC}" \
|
||||
-H "Title: $title" \
|
||||
-H "Priority: $priority" \
|
||||
-H "Sound: $sound" \
|
||||
-d "$message" > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Snapshot critical OpenClaw state into workspace repo so updates/reinstalls are recoverable
|
||||
@@ -86,7 +96,7 @@ backup_repo() {
|
||||
echo "✅ $name: Backup successful" | tee -a "$LOG_FILE"
|
||||
else
|
||||
echo "❌ $name: Push failed - check credentials" | tee -a "$LOG_FILE"
|
||||
send_gotify "⚠️ Backup Failed" "$name push to Gitea failed. Check credentials." 5
|
||||
send_ntfy "⚠️ Backup Failed" "$name push to Gitea failed. Check credentials." 4 default
|
||||
fi
|
||||
else
|
||||
echo "⏭️ $name: No changes to backup" | tee -a "$LOG_FILE"
|
||||
@@ -108,4 +118,4 @@ echo "📊 Recent backups:" | tee -a "$LOG_FILE"
|
||||
cd /home/openclaw/.openclaw/workspace && git log --oneline -3 | tee -a "$LOG_FILE"
|
||||
|
||||
# Send success notification
|
||||
send_gotify "✅ Backup Complete" "OpenClaw backup to Gitea succeeded"
|
||||
send_ntfy "✅ Backup Complete" "OpenClaw backup to Gitea succeeded" 4 default
|
||||
|
||||
@@ -11,8 +11,9 @@ LOG_FILE="$SCRIPT_DIR/reminder-log.json"
|
||||
source "$SCRIPT_DIR/../../.env" 2>/dev/null || true
|
||||
|
||||
TELEGRAM_CHAT="${TELEGRAM_CHAT:-1793951355}"
|
||||
GOTIFY_URL="${GOTIFY_URL:-http://runtipi.kangaroo-eel.ts.net:8129}"
|
||||
GOTIFY_TOKEN="${GOTIFY_TOKEN:-AGKnHafW3FGzBlt}"
|
||||
NTFY_URL="${NTFY_URL:-}"
|
||||
NTFY_TOPIC="${NTFY_TOPIC:-}"
|
||||
NTFY_MIN_PRIORITY="${NTFY_MIN_PRIORITY:-4}"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||
@@ -167,20 +168,29 @@ send_telegram() {
|
||||
}
|
||||
}
|
||||
|
||||
# Send Gotify message
|
||||
send_gotify() {
|
||||
# Send ntfy message
|
||||
send_ntfy() {
|
||||
local title="$1"
|
||||
local message="$2"
|
||||
local priority="${3:-5}"
|
||||
|
||||
curl -s -X POST "${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"title\": \"$title\",
|
||||
\"message\": \"$message\",
|
||||
\"priority\": $priority
|
||||
}" > /dev/null || {
|
||||
log "ERROR: Failed to send Gotify message"
|
||||
local priority="${3:-4}"
|
||||
local sound="${4:-default}"
|
||||
|
||||
[[ -z "$NTFY_URL" ]] && return 0
|
||||
[[ -z "$NTFY_TOPIC" ]] && return 0
|
||||
|
||||
# Enforce minimum priority (default 4)
|
||||
if [[ "$priority" =~ ^[0-9]+$ ]] && [[ "$NTFY_MIN_PRIORITY" =~ ^[0-9]+$ ]]; then
|
||||
if (( priority < NTFY_MIN_PRIORITY )); then
|
||||
priority="$NTFY_MIN_PRIORITY"
|
||||
fi
|
||||
fi
|
||||
|
||||
curl -s -X POST "${NTFY_URL%/}/${NTFY_TOPIC}" \
|
||||
-H "Title: $title" \
|
||||
-H "Priority: $priority" \
|
||||
-H "Sound: $sound" \
|
||||
-d "$message" > /dev/null 2>&1 || {
|
||||
log "ERROR: Failed to send ntfy message"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
@@ -269,12 +279,12 @@ check_birthdays() {
|
||||
log "Sending $reminder_type reminder for $name"
|
||||
send_telegram "$message"
|
||||
|
||||
# Also send to Gotify (strip markdown for cleaner display)
|
||||
# Also send to ntfy (strip markdown for cleaner display)
|
||||
local plain_message
|
||||
plain_message=$(echo -e "$message" | sed 's/\*//g')
|
||||
local priority=5
|
||||
[[ "$reminder_type" == "today" ]] && priority=8
|
||||
send_gotify "Birthday: $name" "$plain_message" "$priority"
|
||||
send_ntfy "Birthday: $name" "$plain_message" "$priority" default
|
||||
|
||||
log_reminder "$name" "$reminder_type"
|
||||
((reminders_sent++))
|
||||
|
||||
@@ -12,8 +12,9 @@ source "$SCRIPT_DIR/../../.env" 2>/dev/null || true
|
||||
ICAL_URL="${GOOGLE_CALENDAR_ICAL:-https://calendar.google.com/calendar/ical/anthonymau%40gmail.com/private-3636d9c51e7beda202676124684619ad/basic.ics}"
|
||||
BIRTHDAY_DATA="$SCRIPT_DIR/../birthday-tracker/birthdays.json"
|
||||
TELEGRAM_CHAT="${TELEGRAM_CHAT:-1793951355}"
|
||||
GOTIFY_URL="${GOTIFY_URL:-http://runtipi.kangaroo-eel.ts.net:8129}"
|
||||
GOTIFY_TOKEN="${GOTIFY_TOKEN:-AGKnHafW3FGzBlt}"
|
||||
NTFY_URL="${NTFY_URL:-}"
|
||||
NTFY_TOPIC="${NTFY_TOPIC:-}"
|
||||
NTFY_MIN_PRIORITY="${NTFY_MIN_PRIORITY:-4}"
|
||||
TEMP_ICS="/tmp/gcal-birthdays.ics"
|
||||
|
||||
log() {
|
||||
@@ -135,10 +136,14 @@ merge_birthdays() {
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"chat_id\": \"$TELEGRAM_CHAT\", \"text\": \"$message\", \"parse_mode\": \"Markdown\"}" > /dev/null || true
|
||||
|
||||
# Gotify
|
||||
curl -s -X POST "${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"title\": \"Birthday Sync\", \"message\": \"Added $count new birthdays from Google Calendar: $names\", \"priority\": 5}" > /dev/null || true
|
||||
# ntfy
|
||||
if [[ -n "${NTFY_URL:-}" && -n "${NTFY_TOPIC:-}" ]]; then
|
||||
curl -s -X POST "${NTFY_URL%/}/${NTFY_TOPIC}" \
|
||||
-H "Title: Birthday Sync" \
|
||||
-H "Priority: ${NTFY_MIN_PRIORITY:-4}" \
|
||||
-H "Sound: default" \
|
||||
-d "Added $count new birthdays from Google Calendar: $names" > /dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Show current stats
|
||||
|
||||
@@ -1,51 +1,101 @@
|
||||
#!/bin/bash
|
||||
# Check Krilly's AgentMail inbox and alert Anthony of new emails
|
||||
# SILENT MODE: Only outputs anything if there are new emails to report
|
||||
# Check Anthony's Gmail inbox (IMAP) and alert Anthony of new unread emails.
|
||||
#
|
||||
# SILENT MODE: only sends a Telegram notification if there are NEW unread emails
|
||||
# since the last run.
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
INBOX="krilly@agentmail.to"
|
||||
API_KEY="am_us_22a6a04a84144467993d5b90be8bbd5d1482ca615a4e17561682dd3d6831f932"
|
||||
LAST_CHECK_FILE="/tmp/krilly-last-email-check"
|
||||
WORKDIR="/home/openclaw/.openclaw/workspace"
|
||||
IMAP_TOOL="$WORKDIR/skills/imap-smtp-email/scripts/imap.js"
|
||||
|
||||
# Get current timestamp
|
||||
CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
|
||||
|
||||
# Get last check time (default to 1 hour ago if file doesn't exist)
|
||||
if [[ -f "$LAST_CHECK_FILE" ]]; then
|
||||
LAST_CHECK=$(cat "$LAST_CHECK_FILE")
|
||||
else
|
||||
LAST_CHECK=$(date -u -d '1 hour ago' +"%Y-%m-%dT%H:%M:%S.000Z")
|
||||
# Load Telegram bot settings (shared with other automations)
|
||||
if [[ -f "$WORKDIR/.env" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$WORKDIR/.env"
|
||||
fi
|
||||
|
||||
# Get messages since last check
|
||||
MESSAGES=$(curl -s -H "Authorization: Bearer $API_KEY" \
|
||||
"https://api.agentmail.to/v0/inboxes/$INBOX/messages?limit=20&after=$LAST_CHECK")
|
||||
send_ntfy() {
|
||||
local title="$1"
|
||||
local body="$2"
|
||||
local priority="${3:-4}" # 1-5 or low|default|high|urgent
|
||||
local sound="${4:-default}"
|
||||
|
||||
# Simple check for new messages (look for messages without "sent" label)
|
||||
if echo "$MESSAGES" | grep -q '"messages":\s*\[\s*\]'; then
|
||||
NEW_COUNT=0
|
||||
elif echo "$MESSAGES" | grep -v '"sent"' | grep -q '"message_id"'; then
|
||||
NEW_COUNT=$(echo "$MESSAGES" | grep -o '"message_id"' | grep -v -A5 -B5 '"sent"' | wc -l)
|
||||
if [[ "$NEW_COUNT" -lt 1 ]]; then
|
||||
NEW_COUNT=1 # At least one if we found messages
|
||||
[[ -z "${NTFY_URL:-}" ]] && return 0
|
||||
[[ -z "${NTFY_TOPIC:-}" ]] && return 0
|
||||
|
||||
# Enforce minimum priority (default 4)
|
||||
local minp="${NTFY_MIN_PRIORITY:-4}"
|
||||
if [[ "$priority" =~ ^[0-9]+$ ]] && [[ "$minp" =~ ^[0-9]+$ ]]; then
|
||||
if (( priority < minp )); then
|
||||
priority="$minp"
|
||||
fi
|
||||
fi
|
||||
|
||||
curl -s -X POST "${NTFY_URL%/}/$NTFY_TOPIC" \
|
||||
-H "Title: $title" \
|
||||
-H "Priority: $priority" \
|
||||
-H "Sound: $sound" \
|
||||
-d "$body" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Track the newest unread UID we've already notified about to avoid repeat pings.
|
||||
STATE_FILE="/tmp/krilly-gmail-last-notified-uid"
|
||||
|
||||
# How far back to look on each run.
|
||||
RECENT_WINDOW="30m"
|
||||
LIMIT="10"
|
||||
|
||||
result_json=$(node "$IMAP_TOOL" check --unseen true --recent "$RECENT_WINDOW" --limit "$LIMIT" 2>/dev/null || true)
|
||||
|
||||
# If IMAP tool errored, stay silent (cron will still have logs if needed).
|
||||
if [[ -z "$result_json" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
unread_count=$(echo "$result_json" | jq 'length' 2>/dev/null || echo 0)
|
||||
if [[ "$unread_count" -lt 1 ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
newest_uid=$(echo "$result_json" | jq -r 'map(.uid // 0) | max' 2>/dev/null || echo 0)
|
||||
last_uid=0
|
||||
if [[ -f "$STATE_FILE" ]]; then
|
||||
last_uid=$(cat "$STATE_FILE" 2>/dev/null || echo 0)
|
||||
fi
|
||||
|
||||
if [[ "$newest_uid" -le "$last_uid" ]]; then
|
||||
# Nothing newer than what we've already notified.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build a short preview list (subject + from). In this tool, `from` is usually a
|
||||
# single string like: "Name" <email@domain>
|
||||
preview=$(echo "$result_json" | jq -r '.[:5][] | "• \(.from // "unknown") — \(.subject // "(no subject)")"' 2>/dev/null)
|
||||
|
||||
MESSAGE=$(cat <<EOF
|
||||
🦀 New unread email(s) in Gmail: *$unread_count*
|
||||
|
||||
$preview
|
||||
EOF
|
||||
)
|
||||
|
||||
# Send via Telegram Bot API directly (more reliable than routing through CLI)
|
||||
if [[ -n "${TELEGRAM_BOT_TOKEN:-}" && -n "${TELEGRAM_CHAT:-}" ]]; then
|
||||
tg_payload=$(jq -nc --arg chat_id "$TELEGRAM_CHAT" --arg text "$MESSAGE" '{chat_id:$chat_id,text:$text,parse_mode:"Markdown",disable_web_page_preview:true}')
|
||||
tg_resp=$(curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$tg_payload" || true)
|
||||
|
||||
if ! echo "$tg_resp" | jq -e '.ok == true' >/dev/null 2>&1; then
|
||||
# Stay non-fatal (cron should not spam errors), but log for debugging
|
||||
echo "ERROR: Telegram send failed: $tg_resp" >&2
|
||||
fi
|
||||
else
|
||||
NEW_COUNT=0
|
||||
echo "ERROR: TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT not set; cannot notify" >&2
|
||||
fi
|
||||
|
||||
# SILENT: Only output and notify if there are new emails
|
||||
if [[ "$NEW_COUNT" -gt 0 ]]; then
|
||||
# Send Telegram notification
|
||||
MESSAGE="🦀 **Krilly received $NEW_COUNT new email(s)!**
|
||||
# Send via ntfy (push)
|
||||
send_ntfy "🦀 New email" "New unread email(s): $unread_count\n\n$preview" "4" "default"
|
||||
|
||||
📧 Check full inbox: https://sendclaw.com/dashboard"
|
||||
|
||||
/home/openclaw/.npm-global/bin/openclaw message send \
|
||||
--channel telegram \
|
||||
--target "telegram:1793951355" \
|
||||
--message "$MESSAGE" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Always update last check time (silently)
|
||||
echo "$CURRENT_TIME" > "$LAST_CHECK_FILE"
|
||||
echo "$newest_uid" > "$STATE_FILE"
|
||||
|
||||
@@ -12,8 +12,9 @@ source "$SCRIPT_DIR/../../.env" 2>/dev/null || true
|
||||
FRESHRSS_URL="${FRESHRSS_URL:-http://freshrss.kangaroo-eel.ts.net}"
|
||||
FRESHRSS_USER="${FRESHRSS_USER:-anthony}"
|
||||
TELEGRAM_CHAT="${TELEGRAM_CHAT:-1793951355}"
|
||||
GOTIFY_URL="${GOTIFY_URL:-http://runtipi.kangaroo-eel.ts.net:8129}"
|
||||
GOTIFY_TOKEN="${GOTIFY_TOKEN:-AGKnHafW3FGzBlt}"
|
||||
NTFY_URL="${NTFY_URL:-}"
|
||||
NTFY_TOPIC="${NTFY_TOPIC:-}"
|
||||
NTFY_MIN_PRIORITY="${NTFY_MIN_PRIORITY:-4}"
|
||||
|
||||
# Your interest keywords for relevance ranking
|
||||
INTERESTS=(
|
||||
@@ -174,40 +175,58 @@ send_telegram() {
|
||||
|
||||
log "Sending to Telegram..."
|
||||
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
if [[ -z "${TELEGRAM_BOT_TOKEN:-}" ]]; then
|
||||
log "ERROR: TELEGRAM_BOT_TOKEN is not set"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tg_response
|
||||
tg_response=$(curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"chat_id\": \"$TELEGRAM_CHAT\",
|
||||
\"text\": \"$message\",
|
||||
\"parse_mode\": \"Markdown\",
|
||||
\"disable_web_page_preview\": true
|
||||
}" > /dev/null || {
|
||||
}") || {
|
||||
log "ERROR: Failed to send Telegram message"
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! echo "$tg_response" | jq -e '.ok == true' > /dev/null 2>&1; then
|
||||
log "ERROR: Telegram API rejected message: $tg_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "Sent to Telegram successfully"
|
||||
}
|
||||
|
||||
send_gotify() {
|
||||
send_ntfy() {
|
||||
local title="$1"
|
||||
local message="$2"
|
||||
local priority="${3:-5}"
|
||||
|
||||
log "Sending to Gotify..."
|
||||
|
||||
curl -s -X POST "${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"title\": \"$title\",
|
||||
\"message\": \"$message\",
|
||||
\"priority\": $priority
|
||||
}" > /dev/null || {
|
||||
log "ERROR: Failed to send Gotify message"
|
||||
local priority="${3:-4}"
|
||||
local sound="${4:-default}"
|
||||
|
||||
[[ -z "$NTFY_URL" ]] && return 0
|
||||
[[ -z "$NTFY_TOPIC" ]] && return 0
|
||||
|
||||
# Enforce minimum priority (default 4)
|
||||
if [[ "$priority" =~ ^[0-9]+$ ]] && [[ "$NTFY_MIN_PRIORITY" =~ ^[0-9]+$ ]]; then
|
||||
if (( priority < NTFY_MIN_PRIORITY )); then
|
||||
priority="$NTFY_MIN_PRIORITY"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Sending to ntfy..."
|
||||
curl -s -X POST "${NTFY_URL%/}/${NTFY_TOPIC}" \
|
||||
-H "Title: $title" \
|
||||
-H "Priority: $priority" \
|
||||
-H "Sound: $sound" \
|
||||
-d "$message" > /dev/null || {
|
||||
log "ERROR: Failed to send ntfy message"
|
||||
return 1
|
||||
}
|
||||
|
||||
log "Sent to Gotify successfully"
|
||||
log "Sent to ntfy successfully"
|
||||
}
|
||||
|
||||
main() {
|
||||
@@ -224,10 +243,10 @@ main() {
|
||||
# Send to both channels
|
||||
send_telegram "$digest"
|
||||
|
||||
# Plain text version for Gotify (strip markdown)
|
||||
# Plain text version for ntfy (strip markdown)
|
||||
local plain_digest
|
||||
plain_digest=$(echo -e "$digest" | sed 's/\*//g' | sed 's/\[\([^]]*\)\]([^)]*)/\1/g')
|
||||
send_gotify "FreshRSS Digest" "$plain_digest" 5
|
||||
send_ntfy "FreshRSS Digest" "$plain_digest" 4 default
|
||||
|
||||
log "Digest complete!"
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ DATA_FILE="$SCRIPT_DIR/monitor-state.json"
|
||||
source "$SCRIPT_DIR/../../.env" 2>/dev/null || true
|
||||
|
||||
TELEGRAM_CHAT="${TELEGRAM_CHAT:-1793951355}"
|
||||
GOTIFY_URL="${GOTIFY_URL:-http://runtipi.kangaroo-eel.ts.net:8129}"
|
||||
GOTIFY_TOKEN="${GOTIFY_TOKEN:-AGKnHafW3FGzBlt}"
|
||||
NTFY_URL="${NTFY_URL:-}"
|
||||
NTFY_TOPIC="${NTFY_TOPIC:-}"
|
||||
NTFY_MIN_PRIORITY="${NTFY_MIN_PRIORITY:-4}"
|
||||
|
||||
# Services to monitor
|
||||
# Format: name|url|type|restart_command(optional)
|
||||
@@ -139,19 +140,28 @@ send_telegram() {
|
||||
}" > /dev/null || log "Failed to send Telegram"
|
||||
}
|
||||
|
||||
# Send Gotify alert
|
||||
send_gotify() {
|
||||
# Send ntfy alert
|
||||
send_ntfy() {
|
||||
local title="$1"
|
||||
local message="$2"
|
||||
local priority="${3:-5}"
|
||||
|
||||
curl -s -X POST "${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"title\": \"$title\",
|
||||
\"message\": \"$message\",
|
||||
\"priority\": $priority
|
||||
}" > /dev/null || log "Failed to send Gotify"
|
||||
local priority="${3:-4}"
|
||||
local sound="${4:-default}"
|
||||
|
||||
[[ -z "$NTFY_URL" ]] && return 0
|
||||
[[ -z "$NTFY_TOPIC" ]] && return 0
|
||||
|
||||
# Enforce minimum priority (default 4)
|
||||
if [[ "$priority" =~ ^[0-9]+$ ]] && [[ "$NTFY_MIN_PRIORITY" =~ ^[0-9]+$ ]]; then
|
||||
if (( priority < NTFY_MIN_PRIORITY )); then
|
||||
priority="$NTFY_MIN_PRIORITY"
|
||||
fi
|
||||
fi
|
||||
|
||||
curl -s -X POST "${NTFY_URL%/}/${NTFY_TOPIC}" \
|
||||
-H "Title: $title" \
|
||||
-H "Priority: $priority" \
|
||||
-H "Sound: $sound" \
|
||||
-d "$message" > /dev/null 2>&1 || log "Failed to send ntfy"
|
||||
}
|
||||
|
||||
# Attempt self-healing
|
||||
@@ -241,7 +251,7 @@ check_services() {
|
||||
# Send alert
|
||||
if ! alert_cooldown_active "$name" "down"; then
|
||||
send_telegram "🚨 *Service Down: $name*\n\nStatus: $status_code\nURL: $url\n\nAuto-heal failed. Manual intervention may be needed."
|
||||
send_gotify "Service Down: $name" "$name is down (status: $status_code)" 8
|
||||
send_ntfy "🚨 Service Down: $name" "$name is down (status: $status_code)\nURL: $url" 4 default
|
||||
log_alert "$name" "down"
|
||||
|
||||
# Update failure stats
|
||||
|
||||
@@ -9,8 +9,9 @@ Verify daily that backup pushed to Gitea includes critical files.
|
||||
3. Configure credentials:
|
||||
- HTTP Header Auth for Gitea API (Authorization: token <YOUR_GITEA_TOKEN>)
|
||||
- Telegram credential named `Telegram account`
|
||||
4. Set env var in n8n for Gotify:
|
||||
- `GOTIFY_TOKEN=<your token>`
|
||||
4. Set env vars in n8n for ntfy:
|
||||
- `NTFY_URL=https://ntfy.sh`
|
||||
- `NTFY_TOPIC=anthony-krilly-9f3k2`
|
||||
|
||||
## What it checks
|
||||
- AGENTS.md
|
||||
@@ -21,8 +22,8 @@ Verify daily that backup pushed to Gitea includes critical files.
|
||||
- state-backup/devices/paired.json
|
||||
|
||||
## Alerts
|
||||
- Success: Gotify priority 2
|
||||
- Failure: Gotify priority 9 + Telegram alert
|
||||
- Success: ntfy (priority header not configured in this workflow yet) + Telegram
|
||||
- Failure: ntfy + Telegram alert
|
||||
|
||||
## Notes
|
||||
- Schedule is 02:20 AWST by default (after backup)
|
||||
|
||||
@@ -17,16 +17,35 @@
|
||||
"name": "Daily 02:20 AWST",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [200, 300]
|
||||
"position": [
|
||||
200,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{ "name": "repoOwner", "type": "string", "value": "Anthony" },
|
||||
{ "name": "repoName", "type": "string", "value": "openclaw-backups" },
|
||||
{ "name": "branch", "type": "string", "value": "main" },
|
||||
{ "name": "requiredPaths", "type": "string", "value": "AGENTS.md,MEMORY.md,TOOLS.md,state-backup/openclaw.json,state-backup/cron/jobs.json,state-backup/devices/paired.json" }
|
||||
{
|
||||
"name": "repoOwner",
|
||||
"type": "string",
|
||||
"value": "Anthony"
|
||||
},
|
||||
{
|
||||
"name": "repoName",
|
||||
"type": "string",
|
||||
"value": "openclaw-backups"
|
||||
},
|
||||
{
|
||||
"name": "branch",
|
||||
"type": "string",
|
||||
"value": "main"
|
||||
},
|
||||
{
|
||||
"name": "requiredPaths",
|
||||
"type": "string",
|
||||
"value": "AGENTS.md,MEMORY.md,TOOLS.md,state-backup/openclaw.json,state-backup/cron/jobs.json,state-backup/devices/paired.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -34,20 +53,28 @@
|
||||
"name": "Set Context",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [420, 300]
|
||||
"position": [
|
||||
420,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "=http://gitea.kangaroo-eel.ts.net:3000/api/v1/repos/{{$json.repoOwner}}/{{$json.repoName}}/branches/{{$json.branch}}",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"options": { "timeout": 10000 }
|
||||
"options": {
|
||||
"timeout": 10000
|
||||
}
|
||||
},
|
||||
"id": "get-branch",
|
||||
"name": "Get Branch Head",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [660, 300],
|
||||
"position": [
|
||||
660,
|
||||
300
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 3,
|
||||
"waitBetweenTries": 2000
|
||||
@@ -57,13 +84,18 @@
|
||||
"url": "=http://gitea.kangaroo-eel.ts.net:3000/api/v1/repos/{{$node[\"Set Context\"].json.repoOwner}}/{{$node[\"Set Context\"].json.repoName}}/git/trees/{{$json.commit.id}}?recursive=1",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"options": { "timeout": 10000 }
|
||||
"options": {
|
||||
"timeout": 10000
|
||||
}
|
||||
},
|
||||
"id": "get-tree",
|
||||
"name": "Get Repo Tree",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [900, 300],
|
||||
"position": [
|
||||
900,
|
||||
300
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 3,
|
||||
"waitBetweenTries": 2000
|
||||
@@ -76,13 +108,20 @@
|
||||
"name": "Validate Required Files",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [1140, 300]
|
||||
"position": [
|
||||
1140,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"string": [
|
||||
{ "value1": "={{$json.status}}", "operation": "equals", "value2": "ok" }
|
||||
{
|
||||
"value1": "={{$json.status}}",
|
||||
"operation": "equals",
|
||||
"value2": "ok"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -90,59 +129,93 @@
|
||||
"name": "Backup OK?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [1360, 300]
|
||||
"position": [
|
||||
1360,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "=http://runtipi.kangaroo-eel.ts.net:8129/message?token={{$env.GOTIFY_TOKEN}}",
|
||||
"url": "={{$env.NTFY_URL || 'https://ntfy.sh'}}/{{ $env.NTFY_TOPIC || 'anthony-krilly-9f3k2' }}",
|
||||
"sendBody": true,
|
||||
"contentType": "json",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{ "name": "title", "value": "✅ Backup Verified" },
|
||||
{ "name": "message", "value": "={{`Repo ${$json.repo} verified. SHA: ${$json.headSha.slice(0,7)}. Checked ${$json.checked} required files.`}}" },
|
||||
{ "name": "priority", "value": "2" }
|
||||
{
|
||||
"name": "title",
|
||||
"value": "\u2705 Backup Verified"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"value": "={{`Repo ${$json.repo} verified. SHA: ${$json.headSha.slice(0,7)}. Checked ${$json.checked} required files.`}}"
|
||||
},
|
||||
{
|
||||
"name": "priority",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": { "timeout": 10000 }
|
||||
"options": {
|
||||
"timeout": 10000
|
||||
}
|
||||
},
|
||||
"id": "notify-ok",
|
||||
"name": "Gotify Success",
|
||||
"name": "ntfy Success",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1600, 200]
|
||||
"position": [
|
||||
1600,
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "=http://runtipi.kangaroo-eel.ts.net:8129/message?token={{$env.GOTIFY_TOKEN}}",
|
||||
"url": "={{$env.NTFY_URL || 'https://ntfy.sh'}}/{{ $env.NTFY_TOPIC || 'anthony-krilly-9f3k2' }}",
|
||||
"sendBody": true,
|
||||
"contentType": "json",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{ "name": "title", "value": "🚨 Backup Verification Failed" },
|
||||
{ "name": "message", "value": "={{`Repo ${$json.repo} failed verification. Missing: ${$json.missing.join(', ')}. SHA: ${$json.headSha.slice(0,7)}`}}" },
|
||||
{ "name": "priority", "value": "9" }
|
||||
{
|
||||
"name": "title",
|
||||
"value": "\ud83d\udea8 Backup Verification Failed"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"value": "={{`Repo ${$json.repo} failed verification. Missing: ${$json.missing.join(', ')}. SHA: ${$json.headSha.slice(0,7)}`}}"
|
||||
},
|
||||
{
|
||||
"name": "priority",
|
||||
"value": "9"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": { "timeout": 10000 }
|
||||
"options": {
|
||||
"timeout": 10000
|
||||
}
|
||||
},
|
||||
"id": "notify-fail-gotify",
|
||||
"name": "Gotify Failure",
|
||||
"name": "ntfy Failure",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1600, 360]
|
||||
"position": [
|
||||
1600,
|
||||
360
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"chatId": "1793951355",
|
||||
"text": "={{`🦀 Backup verifier failed\nRepo: ${$json.repo}\nMissing: ${$json.missing.join(', ')}\nSHA: ${$json.headSha.slice(0,7)}`}}",
|
||||
"text": "={{`\ud83e\udd80 Backup verifier failed\nRepo: ${$json.repo}\nMissing: ${$json.missing.join(', ')}\nSHA: ${$json.headSha.slice(0,7)}`}}",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "notify-fail-telegram",
|
||||
"name": "Telegram Failure",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [1810, 360],
|
||||
"position": [
|
||||
1810,
|
||||
360
|
||||
],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"name": "Telegram account"
|
||||
@@ -151,19 +224,93 @@
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Daily 02:20 AWST": { "main": [[{ "node": "Set Context", "type": "main", "index": 0 }]] },
|
||||
"Set Context": { "main": [[{ "node": "Get Branch Head", "type": "main", "index": 0 }]] },
|
||||
"Get Branch Head": { "main": [[{ "node": "Get Repo Tree", "type": "main", "index": 0 }]] },
|
||||
"Get Repo Tree": { "main": [[{ "node": "Validate Required Files", "type": "main", "index": 0 }]] },
|
||||
"Validate Required Files": { "main": [[{ "node": "Backup OK?", "type": "main", "index": 0 }]] },
|
||||
"Backup OK?": {
|
||||
"Daily 02:20 AWST": {
|
||||
"main": [
|
||||
[{ "node": "Gotify Success", "type": "main", "index": 0 }],
|
||||
[{ "node": "Gotify Failure", "type": "main", "index": 0 }]
|
||||
[
|
||||
{
|
||||
"node": "Set Context",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Gotify Failure": { "main": [[{ "node": "Telegram Failure", "type": "main", "index": 0 }]] }
|
||||
"Set Context": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Branch Head",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get Branch Head": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Repo Tree",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get Repo Tree": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Validate Required Files",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Validate Required Files": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Backup OK?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Backup OK?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "ntfy Success",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "ntfy Failure",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"ntfy Failure": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Telegram Failure",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"settings": { "executionOrder": "v1" },
|
||||
"active": false
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,24 @@
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {"interval": [{"field": "cronExpression", "expression": "15 7 * * *"}]},
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "cronExpression",
|
||||
"expression": "15 7 * * *"
|
||||
}
|
||||
]
|
||||
},
|
||||
"timezone": "Australia/Perth"
|
||||
},
|
||||
"id": "cron",
|
||||
"name": "Daily 07:15 AWST",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [220, 300]
|
||||
"position": [
|
||||
220,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -23,7 +33,10 @@
|
||||
"name": "Webhook Execute (POST)",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [220, 460]
|
||||
"position": [
|
||||
220,
|
||||
460
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -41,7 +54,10 @@
|
||||
"name": "Set Prompt",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [470, 380]
|
||||
"position": [
|
||||
470,
|
||||
380
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -52,47 +68,112 @@
|
||||
"contentType": "json",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{"name": "text", "value": "={{$json.wakeText}}"},
|
||||
{"name": "mode", "value": "now"}
|
||||
{
|
||||
"name": "text",
|
||||
"value": "={{$json.wakeText}}"
|
||||
},
|
||||
{
|
||||
"name": "mode",
|
||||
"value": "now"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {"timeout": 15000}
|
||||
"options": {
|
||||
"timeout": 15000
|
||||
}
|
||||
},
|
||||
"id": "wake",
|
||||
"name": "Trigger OpenClaw Briefing",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [730, 380],
|
||||
"position": [
|
||||
730,
|
||||
380
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 3,
|
||||
"waitBetweenTries": 2000
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "http://runtipi.kangaroo-eel.ts.net:8129/message?token=AGKnHafW3FGzBlt",
|
||||
"url": "http://runtipi.kangaroo-eel.ts.net:8129/message?token=AGoV3cAUyUMDbyt",
|
||||
"sendBody": true,
|
||||
"contentType": "json",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{"name": "title", "value": "📰 Newsletter Compressor Triggered"},
|
||||
{"name": "message", "value": "Morning newsletter compression job was triggered via n8n."},
|
||||
{"name": "priority", "value": "3"}
|
||||
{
|
||||
"name": "title",
|
||||
"value": "\ud83d\udcf0 Newsletter Compressor Triggered"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"value": "Morning newsletter compression job was triggered via n8n."
|
||||
},
|
||||
{
|
||||
"name": "priority",
|
||||
"value": "3"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "gotify",
|
||||
"name": "Gotify Status",
|
||||
"name": "ntfy Status",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [980, 380]
|
||||
"position": [
|
||||
980,
|
||||
380
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Daily 07:15 AWST": {"main": [[{"node": "Set Prompt", "type": "main", "index": 0}]]},
|
||||
"Webhook Execute (POST)": {"main": [[{"node": "Set Prompt", "type": "main", "index": 0}]]},
|
||||
"Set Prompt": {"main": [[{"node": "Trigger OpenClaw Briefing", "type": "main", "index": 0}]]},
|
||||
"Trigger OpenClaw Briefing": {"main": [[{"node": "Gotify Status", "type": "main", "index": 0}]]}
|
||||
"Daily 07:15 AWST": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set Prompt",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Webhook Execute (POST)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set Prompt",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Set Prompt": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Trigger OpenClaw Briefing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Trigger OpenClaw Briefing": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "ntfy Status",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"settings": {"executionOrder": "v1"},
|
||||
"active": false
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,44 @@
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {"interval": [{"field": "cronExpression", "expression": "45 6 * * *"}]},
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "cronExpression",
|
||||
"expression": "45 6 * * *"
|
||||
}
|
||||
]
|
||||
},
|
||||
"timezone": "Australia/Perth"
|
||||
},
|
||||
"id": "cron",
|
||||
"name": "Daily 06:45 AWST",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [220, 320]
|
||||
"position": [
|
||||
220,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{"name": "repoOwner", "type": "string", "value": "Anthony"},
|
||||
{"name": "repoName", "type": "string", "value": "openclaw-backups"},
|
||||
{"name": "branch", "type": "string", "value": "main"}
|
||||
{
|
||||
"name": "repoOwner",
|
||||
"type": "string",
|
||||
"value": "Anthony"
|
||||
},
|
||||
{
|
||||
"name": "repoName",
|
||||
"type": "string",
|
||||
"value": "openclaw-backups"
|
||||
},
|
||||
{
|
||||
"name": "branch",
|
||||
"type": "string",
|
||||
"value": "main"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -26,18 +48,26 @@
|
||||
"name": "Set Repo Context",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [440, 320]
|
||||
"position": [
|
||||
440,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "=http://gitea.kangaroo-eel.ts.net:3000/api/v1/repos/{{$json.repoOwner}}/{{$json.repoName}}/commits?sha={{$json.branch}}&limit=2",
|
||||
"options": {"timeout": 10000}
|
||||
"options": {
|
||||
"timeout": 10000
|
||||
}
|
||||
},
|
||||
"id": "commits",
|
||||
"name": "Get Last 2 Commits",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [680, 320],
|
||||
"position": [
|
||||
680,
|
||||
320
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 3,
|
||||
"waitBetweenTries": 1500
|
||||
@@ -50,28 +80,47 @@
|
||||
"name": "Prepare Compare SHAs",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [920, 320]
|
||||
"position": [
|
||||
920,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {"string": [{"value1": "={{$json.status}}", "operation": "equals", "value2": "ok"}]}
|
||||
"conditions": {
|
||||
"string": [
|
||||
{
|
||||
"value1": "={{$json.status}}",
|
||||
"operation": "equals",
|
||||
"value2": "ok"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "if-ready",
|
||||
"name": "Ready to Compare?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [1140, 320]
|
||||
"position": [
|
||||
1140,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "=http://gitea.kangaroo-eel.ts.net:3000/api/v1/repos/Anthony/openclaw-backups/compare/{{$json.base}}...{{$json.head}}",
|
||||
"options": {"timeout": 10000}
|
||||
"options": {
|
||||
"timeout": 10000
|
||||
}
|
||||
},
|
||||
"id": "compare",
|
||||
"name": "Compare Commits",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1360, 260],
|
||||
"position": [
|
||||
1360,
|
||||
260
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 3,
|
||||
"waitBetweenTries": 1500
|
||||
@@ -84,59 +133,187 @@
|
||||
"name": "Analyze Drift",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [1580, 260]
|
||||
"position": [
|
||||
1580,
|
||||
260
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {"boolean": [{"value1": "={{$json.drift}}", "operation": "true"}]}
|
||||
"conditions": {
|
||||
"boolean": [
|
||||
{
|
||||
"value1": "={{$json.drift}}",
|
||||
"operation": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "if-drift",
|
||||
"name": "Drift Detected?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [1800, 260]
|
||||
"position": [
|
||||
1800,
|
||||
260
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "http://runtipi.kangaroo-eel.ts.net:8129/message?token=AGKnHafW3FGzBlt",
|
||||
"url": "http://runtipi.kangaroo-eel.ts.net:8129/message?token=AGoV3cAUyUMDbyt",
|
||||
"sendBody": true,
|
||||
"contentType": "json",
|
||||
"bodyParameters": {"parameters": [
|
||||
{"name": "title", "value": "🦀 OpenClaw Drift Detected"},
|
||||
{"name": "message", "value": "={{`Changed files: ${$json.changed.join(', ')}${$json.criticalCount>0 ? '\nCritical state changed.' : ''}`}}"},
|
||||
{"name": "priority", "value": "={{$json.criticalCount>0 ? 9 : 6}}"}
|
||||
]}
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "title",
|
||||
"value": "\ud83e\udd80 OpenClaw Drift Detected"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"value": "={{`Changed files: ${$json.changed.join(', ')}${$json.criticalCount>0 ? '\nCritical state changed.' : ''}`}}"
|
||||
},
|
||||
{
|
||||
"name": "priority",
|
||||
"value": "={{$json.criticalCount>0 ? 9 : 6}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "gotify",
|
||||
"name": "Gotify Drift Alert",
|
||||
"name": "ntfy Drift Alert",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [2020, 220]
|
||||
"position": [
|
||||
2020,
|
||||
220
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"chatId": "1793951355",
|
||||
"text": "={{`🦀 OpenClaw drift detected\nChanged: ${$json.changed.join(', ')}${$json.criticalCount>0 ? '\n⚠️ Critical state changed' : ''}`}}"
|
||||
"text": "={{`\ud83e\udd80 OpenClaw drift detected\nChanged: ${$json.changed.join(', ')}${$json.criticalCount>0 ? '\n\u26a0\ufe0f Critical state changed' : ''}`}}"
|
||||
},
|
||||
"id": "tg",
|
||||
"name": "Telegram Drift Alert",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [2240, 220],
|
||||
"credentials": {"telegramApi": {"name": "Telegram account"}}
|
||||
"position": [
|
||||
2240,
|
||||
220
|
||||
],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Daily 06:45 AWST": {"main": [[{"node": "Set Repo Context", "type": "main", "index": 0}]]},
|
||||
"Set Repo Context": {"main": [[{"node": "Get Last 2 Commits", "type": "main", "index": 0}]]},
|
||||
"Get Last 2 Commits": {"main": [[{"node": "Prepare Compare SHAs", "type": "main", "index": 0}]]},
|
||||
"Prepare Compare SHAs": {"main": [[{"node": "Ready to Compare?", "type": "main", "index": 0}]]},
|
||||
"Ready to Compare?": {"main": [[{"node": "Compare Commits", "type": "main", "index": 0}], []]},
|
||||
"Compare Commits": {"main": [[{"node": "Analyze Drift", "type": "main", "index": 0}]]},
|
||||
"Analyze Drift": {"main": [[{"node": "Drift Detected?", "type": "main", "index": 0}]]},
|
||||
"Drift Detected?": {"main": [[{"node": "Gotify Drift Alert", "type": "main", "index": 0}], []]},
|
||||
"Gotify Drift Alert": {"main": [[{"node": "Telegram Drift Alert", "type": "main", "index": 0}]]}
|
||||
"Daily 06:45 AWST": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set Repo Context",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Set Repo Context": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Last 2 Commits",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get Last 2 Commits": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Compare SHAs",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Compare SHAs": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Ready to Compare?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Ready to Compare?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compare Commits",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Compare Commits": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Analyze Drift",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Analyze Drift": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Drift Detected?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Drift Detected?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "ntfy Drift Alert",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
"ntfy Drift Alert": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Telegram Drift Alert",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"settings": {"executionOrder": "v1"},
|
||||
"active": false
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,24 @@
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {"interval": [{"field": "cronExpression", "expression": "0 16 * * 5"}]},
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "cronExpression",
|
||||
"expression": "0 16 * * 5"
|
||||
}
|
||||
]
|
||||
},
|
||||
"timezone": "Australia/Perth"
|
||||
},
|
||||
"id": "cron",
|
||||
"name": "Friday 4:00 PM AWST",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [220, 280]
|
||||
"position": [
|
||||
220,
|
||||
280
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -23,14 +33,25 @@
|
||||
"name": "Webhook Execute (POST)",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [220, 430]
|
||||
"position": [
|
||||
220,
|
||||
430
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{"name": "draftPrompt", "type": "string", "value": "Create a weekend plan draft for Anthony in Perth. Use: calendar items next 3 days, Perth weather, and practical recommendations. Output concise sections: Saturday, Sunday, Prep List, and one fun optional idea. Keep it useful and warm."},
|
||||
{"name": "approvalHint", "type": "string", "value": "Reply APPROVE to send full plan, or REGEN to regenerate."}
|
||||
{
|
||||
"name": "draftPrompt",
|
||||
"type": "string",
|
||||
"value": "Create a weekend plan draft for Anthony in Perth. Use: calendar items next 3 days, Perth weather, and practical recommendations. Output concise sections: Saturday, Sunday, Prep List, and one fun optional idea. Keep it useful and warm."
|
||||
},
|
||||
{
|
||||
"name": "approvalHint",
|
||||
"type": "string",
|
||||
"value": "Reply APPROVE to send full plan, or REGEN to regenerate."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -38,7 +59,10 @@
|
||||
"name": "Set Planner Prompt",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [450, 350]
|
||||
"position": [
|
||||
450,
|
||||
350
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -47,60 +71,142 @@
|
||||
"contentType": "json",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{"name": "text", "value": "={{$json.draftPrompt}}"},
|
||||
{"name": "mode", "value": "now"}
|
||||
{
|
||||
"name": "text",
|
||||
"value": "={{$json.draftPrompt}}"
|
||||
},
|
||||
{
|
||||
"name": "mode",
|
||||
"value": "now"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {"timeout": 15000}
|
||||
"options": {
|
||||
"timeout": 15000
|
||||
}
|
||||
},
|
||||
"id": "wake-draft",
|
||||
"name": "Generate Plan Draft",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [700, 350],
|
||||
"position": [
|
||||
700,
|
||||
350
|
||||
],
|
||||
"retryOnFail": true,
|
||||
"maxTries": 3,
|
||||
"waitBetweenTries": 2000
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "http://runtipi.kangaroo-eel.ts.net:8129/message?token=AGKnHafW3FGzBlt",
|
||||
"url": "http://runtipi.kangaroo-eel.ts.net:8129/message?token=AGoV3cAUyUMDbyt",
|
||||
"sendBody": true,
|
||||
"contentType": "json",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{"name": "title", "value": "🗓️ Weekend Planner Draft Ready"},
|
||||
{"name": "message", "value": "={{`Draft generated. ${$node['Set Planner Prompt'].json.approvalHint}`}}"},
|
||||
{"name": "priority", "value": "5"}
|
||||
{
|
||||
"name": "title",
|
||||
"value": "\ud83d\uddd3\ufe0f Weekend Planner Draft Ready"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"value": "={{`Draft generated. ${$node['Set Planner Prompt'].json.approvalHint}`}}"
|
||||
},
|
||||
{
|
||||
"name": "priority",
|
||||
"value": "5"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "gotify-draft",
|
||||
"name": "Gotify Draft Ready",
|
||||
"name": "ntfy Draft Ready",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [940, 350]
|
||||
"position": [
|
||||
940,
|
||||
350
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"chatId": "1793951355",
|
||||
"text": "🦀 Weekend Planner draft is ready. Reply here with APPROVE to send final plan, or REGEN to regenerate."
|
||||
"text": "\ud83e\udd80 Weekend Planner draft is ready. Reply here with APPROVE to send final plan, or REGEN to regenerate."
|
||||
},
|
||||
"id": "telegram-review",
|
||||
"name": "Telegram Review Prompt",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [1160, 350],
|
||||
"credentials": {"telegramApi": {"name": "Telegram account"}}
|
||||
"position": [
|
||||
1160,
|
||||
350
|
||||
],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Friday 4:00 PM AWST": {"main": [[{"node": "Set Planner Prompt", "type": "main", "index": 0}]]},
|
||||
"Webhook Execute (POST)": {"main": [[{"node": "Set Planner Prompt", "type": "main", "index": 0}]]},
|
||||
"Set Planner Prompt": {"main": [[{"node": "Generate Plan Draft", "type": "main", "index": 0}]]},
|
||||
"Generate Plan Draft": {"main": [[{"node": "Gotify Draft Ready", "type": "main", "index": 0}]]},
|
||||
"Gotify Draft Ready": {"main": [[{"node": "Telegram Review Prompt", "type": "main", "index": 0}]]}
|
||||
"Friday 4:00 PM AWST": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set Planner Prompt",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Webhook Execute (POST)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set Planner Prompt",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Set Planner Prompt": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Generate Plan Draft",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Generate Plan Draft": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "ntfy Draft Ready",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"ntfy Draft Ready": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Telegram Review Prompt",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"settings": {"executionOrder": "v1"},
|
||||
"active": false
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# Smart Newsletter Digest
|
||||
|
||||
**Runs:** Daily at 8:00 PM (Australia/Perth)
|
||||
**Sends to:** Telegram + Gotify
|
||||
**Sends to:** Telegram + ntfy
|
||||
**Learns from your feedback!**
|
||||
|
||||
## How It Works
|
||||
|
||||
@@ -10,8 +10,9 @@ source "$SCRIPT_DIR/../../.env" 2>/dev/null || true
|
||||
source "$IMAP_SKILL/.env" 2>/dev/null || true
|
||||
|
||||
TELEGRAM_CHAT="${TELEGRAM_CHAT:-1793951355}"
|
||||
GOTIFY_URL="${GOTIFY_URL:-http://runtipi.kangaroo-eel.ts.net:8129}"
|
||||
GOTIFY_TOKEN="${GOTIFY_TOKEN:-AGKnHafW3FGzBlt}"
|
||||
NTFY_URL="${NTFY_URL:-}"
|
||||
NTFY_TOPIC="${NTFY_TOPIC:-}"
|
||||
NTFY_MIN_PRIORITY="${NTFY_MIN_PRIORITY:-4}"
|
||||
PREFERENCES_FILE="$SCRIPT_DIR/newsletter-preferences.json"
|
||||
|
||||
log() {
|
||||
@@ -146,11 +147,27 @@ send_telegram() {
|
||||
-d "{\"chat_id\": \"$TELEGRAM_CHAT\", \"text\": \"$message\", \"parse_mode\": \"Markdown\", \"disable_web_page_preview\": true}" > /dev/null
|
||||
}
|
||||
|
||||
send_gotify() {
|
||||
local msg="$1"
|
||||
curl -s -X POST "${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"title\": \"Newsletter Digest\", \"message\": \"$msg\", \"priority\": 5}" > /dev/null || true
|
||||
send_ntfy() {
|
||||
local title="$1"
|
||||
local msg="$2"
|
||||
local priority="${3:-4}"
|
||||
local sound="${4:-default}"
|
||||
|
||||
[[ -z "$NTFY_URL" ]] && return 0
|
||||
[[ -z "$NTFY_TOPIC" ]] && return 0
|
||||
|
||||
# Enforce minimum priority (default 4)
|
||||
if [[ "$priority" =~ ^[0-9]+$ ]] && [[ "$NTFY_MIN_PRIORITY" =~ ^[0-9]+$ ]]; then
|
||||
if (( priority < NTFY_MIN_PRIORITY )); then
|
||||
priority="$NTFY_MIN_PRIORITY"
|
||||
fi
|
||||
fi
|
||||
|
||||
curl -s -X POST "${NTFY_URL}/$NTFY_TOPIC" \
|
||||
-H "Title: $title" \
|
||||
-H "Priority: $priority" \
|
||||
-H "Sound: $sound" \
|
||||
-d "$msg" > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
main() {
|
||||
@@ -161,7 +178,7 @@ main() {
|
||||
digest=$(fetch_and_build_digest)
|
||||
|
||||
send_telegram "$digest"
|
||||
send_gotify "Digest sent to Telegram"
|
||||
send_ntfy "🦀 Newsletter digest" "Digest sent to Telegram" 4 default
|
||||
|
||||
log "Done!"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user