@aiassesstech/mighty-mark 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/AGENTS.md +61 -0
- package/agent/IDENTITY.md +18 -0
- package/agent/SOUL.md +56 -0
- package/dist/checks/agent-verify.d.ts +15 -0
- package/dist/checks/agent-verify.d.ts.map +1 -0
- package/dist/checks/agent-verify.js +100 -0
- package/dist/checks/agent-verify.js.map +1 -0
- package/dist/checks/api-connectivity.d.ts +14 -0
- package/dist/checks/api-connectivity.d.ts.map +1 -0
- package/dist/checks/api-connectivity.js +77 -0
- package/dist/checks/api-connectivity.js.map +1 -0
- package/dist/checks/check-context.d.ts +36 -0
- package/dist/checks/check-context.d.ts.map +1 -0
- package/dist/checks/check-context.js +42 -0
- package/dist/checks/check-context.js.map +1 -0
- package/dist/checks/check-runner.d.ts +18 -0
- package/dist/checks/check-runner.d.ts.map +1 -0
- package/dist/checks/check-runner.js +109 -0
- package/dist/checks/check-runner.js.map +1 -0
- package/dist/checks/data-integrity.d.ts +16 -0
- package/dist/checks/data-integrity.d.ts.map +1 -0
- package/dist/checks/data-integrity.js +152 -0
- package/dist/checks/data-integrity.js.map +1 -0
- package/dist/checks/gateway-health.d.ts +17 -0
- package/dist/checks/gateway-health.d.ts.map +1 -0
- package/dist/checks/gateway-health.js +208 -0
- package/dist/checks/gateway-health.js.map +1 -0
- package/dist/checks/system-resources.d.ts +21 -0
- package/dist/checks/system-resources.d.ts.map +1 -0
- package/dist/checks/system-resources.js +136 -0
- package/dist/checks/system-resources.js.map +1 -0
- package/dist/cli/bin.d.ts +8 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +12 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/runner.d.ts +11 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +99 -0
- package/dist/cli/runner.js.map +1 -0
- package/dist/cli/setup.d.ts +28 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +238 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/config/config-schema.d.ts +56 -0
- package/dist/config/config-schema.d.ts.map +1 -0
- package/dist/config/config-schema.js +34 -0
- package/dist/config/config-schema.js.map +1 -0
- package/dist/config/defaults.d.ts +31 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +31 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/fleet-topology.d.ts +22 -0
- package/dist/config/fleet-topology.d.ts.map +1 -0
- package/dist/config/fleet-topology.js +81 -0
- package/dist/config/fleet-topology.js.map +1 -0
- package/dist/history/incident-log.d.ts +19 -0
- package/dist/history/incident-log.d.ts.map +1 -0
- package/dist/history/incident-log.js +50 -0
- package/dist/history/incident-log.js.map +1 -0
- package/dist/history/trend-analyzer.d.ts +18 -0
- package/dist/history/trend-analyzer.d.ts.map +1 -0
- package/dist/history/trend-analyzer.js +80 -0
- package/dist/history/trend-analyzer.js.map +1 -0
- package/dist/history/uptime-tracker.d.ts +15 -0
- package/dist/history/uptime-tracker.d.ts.map +1 -0
- package/dist/history/uptime-tracker.js +56 -0
- package/dist/history/uptime-tracker.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +124 -0
- package/dist/index.js.map +1 -0
- package/dist/notify/alert-classifier.d.ts +19 -0
- package/dist/notify/alert-classifier.d.ts.map +1 -0
- package/dist/notify/alert-classifier.js +45 -0
- package/dist/notify/alert-classifier.js.map +1 -0
- package/dist/notify/report-formatter.d.ts +21 -0
- package/dist/notify/report-formatter.d.ts.map +1 -0
- package/dist/notify/report-formatter.js +139 -0
- package/dist/notify/report-formatter.js.map +1 -0
- package/dist/notify/telegram-notifier.d.ts +28 -0
- package/dist/notify/telegram-notifier.d.ts.map +1 -0
- package/dist/notify/telegram-notifier.js +69 -0
- package/dist/notify/telegram-notifier.js.map +1 -0
- package/dist/plugin.d.ts +18 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +289 -0
- package/dist/plugin.js.map +1 -0
- package/dist/store/json-store.d.ts +22 -0
- package/dist/store/json-store.d.ts.map +1 -0
- package/dist/store/json-store.js +128 -0
- package/dist/store/json-store.js.map +1 -0
- package/dist/store/types.d.ts +19 -0
- package/dist/store/types.d.ts.map +1 -0
- package/dist/store/types.js +5 -0
- package/dist/store/types.js.map +1 -0
- package/dist/types/events.d.ts +33 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +5 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/fleet.d.ts +27 -0
- package/dist/types/fleet.d.ts.map +1 -0
- package/dist/types/fleet.js +6 -0
- package/dist/types/fleet.js.map +1 -0
- package/dist/types/health.d.ts +58 -0
- package/dist/types/health.d.ts.map +1 -0
- package/dist/types/health.js +5 -0
- package/dist/types/health.js.map +1 -0
- package/dist/types/incident.d.ts +35 -0
- package/dist/types/incident.d.ts.map +1 -0
- package/dist/types/incident.js +5 -0
- package/dist/types/incident.js.map +1 -0
- package/openclaw.plugin.json +56 -0
- package/package.json +71 -0
- package/src/watchdog/README.md +123 -0
- package/src/watchdog/install.sh +244 -0
- package/src/watchdog/lib/config.sh +44 -0
- package/src/watchdog/lib/logging.sh +29 -0
- package/src/watchdog/lib/notify.sh +46 -0
- package/src/watchdog/morning-check.sh +107 -0
- package/src/watchdog/openclaw-gateway.service +43 -0
- package/src/watchdog/watchdog.sh +124 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# /opt/mighty-mark/install.sh
|
|
3
|
+
# Mighty Mark — Watchdog Installer for Hetzner VPS
|
|
4
|
+
#
|
|
5
|
+
# What this does:
|
|
6
|
+
# 1. Creates /opt/mighty-mark/ directory structure
|
|
7
|
+
# 2. Copies watchdog.sh, morning-check.sh, and lib/ scripts
|
|
8
|
+
# 3. Installs cron job in /etc/cron.d/mighty-mark (CRON_TZ=America/Chicago)
|
|
9
|
+
# 4. Sets up logrotate in /etc/logrotate.d/mighty-mark
|
|
10
|
+
# 5. Installs systemd service file (optional)
|
|
11
|
+
# 6. Creates .env template for Telegram credentials
|
|
12
|
+
# 7. Initializes state files
|
|
13
|
+
# 8. Tests Telegram connectivity
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# sudo bash install.sh [--telegram-token <token>] [--telegram-chat-id <id>]
|
|
17
|
+
#
|
|
18
|
+
# Must be run as root.
|
|
19
|
+
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
# ── Parse Arguments ──
|
|
23
|
+
TELEGRAM_TOKEN=""
|
|
24
|
+
TELEGRAM_CHAT_ID=""
|
|
25
|
+
SKIP_CRON=false
|
|
26
|
+
SKIP_LOGROTATE=false
|
|
27
|
+
DRY_RUN=false
|
|
28
|
+
|
|
29
|
+
while [[ $# -gt 0 ]]; do
|
|
30
|
+
case $1 in
|
|
31
|
+
--telegram-token) TELEGRAM_TOKEN="$2"; shift 2 ;;
|
|
32
|
+
--telegram-chat-id) TELEGRAM_CHAT_ID="$2"; shift 2 ;;
|
|
33
|
+
--skip-cron) SKIP_CRON=true; shift ;;
|
|
34
|
+
--skip-logrotate) SKIP_LOGROTATE=true; shift ;;
|
|
35
|
+
--dry-run) DRY_RUN=true; shift ;;
|
|
36
|
+
*) echo "Unknown option: $1"; exit 1 ;;
|
|
37
|
+
esac
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
# ── Check root ──
|
|
41
|
+
if [ "$(id -u)" -ne 0 ] && [ "$DRY_RUN" = false ]; then
|
|
42
|
+
echo "ERROR: This script must be run as root (sudo bash install.sh)"
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
MARK_HOME="/opt/mighty-mark"
|
|
47
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
48
|
+
|
|
49
|
+
echo "╔══════════════════════════════════════════════════╗"
|
|
50
|
+
echo "║ Mighty Mark — Watchdog Installer ║"
|
|
51
|
+
echo "╚══════════════════════════════════════════════════╝"
|
|
52
|
+
echo ""
|
|
53
|
+
|
|
54
|
+
# ── Step 1: Create directory structure ──
|
|
55
|
+
echo "[1/7] Creating directory structure..."
|
|
56
|
+
|
|
57
|
+
if [ "$DRY_RUN" = true ]; then
|
|
58
|
+
echo " [DRY RUN] Would create: ${MARK_HOME}/{logs,state,lib}"
|
|
59
|
+
else
|
|
60
|
+
mkdir -p "${MARK_HOME}"/{logs,state,lib}
|
|
61
|
+
echo " Created: ${MARK_HOME}/"
|
|
62
|
+
echo " Created: ${MARK_HOME}/logs/"
|
|
63
|
+
echo " Created: ${MARK_HOME}/state/"
|
|
64
|
+
echo " Created: ${MARK_HOME}/lib/"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# ── Step 2: Copy scripts ──
|
|
68
|
+
echo "[2/7] Installing scripts..."
|
|
69
|
+
|
|
70
|
+
if [ "$DRY_RUN" = true ]; then
|
|
71
|
+
echo " [DRY RUN] Would copy watchdog.sh, morning-check.sh, lib/*.sh"
|
|
72
|
+
else
|
|
73
|
+
cp "${SOURCE_DIR}/watchdog.sh" "${MARK_HOME}/watchdog.sh"
|
|
74
|
+
cp "${SOURCE_DIR}/morning-check.sh" "${MARK_HOME}/morning-check.sh"
|
|
75
|
+
cp "${SOURCE_DIR}/lib/config.sh" "${MARK_HOME}/lib/config.sh"
|
|
76
|
+
cp "${SOURCE_DIR}/lib/logging.sh" "${MARK_HOME}/lib/logging.sh"
|
|
77
|
+
cp "${SOURCE_DIR}/lib/notify.sh" "${MARK_HOME}/lib/notify.sh"
|
|
78
|
+
|
|
79
|
+
chmod +x "${MARK_HOME}/watchdog.sh"
|
|
80
|
+
chmod +x "${MARK_HOME}/morning-check.sh"
|
|
81
|
+
|
|
82
|
+
echo " Installed: watchdog.sh"
|
|
83
|
+
echo " Installed: morning-check.sh"
|
|
84
|
+
echo " Installed: lib/config.sh, logging.sh, notify.sh"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# ── Step 3: Create .env file ──
|
|
88
|
+
echo "[3/7] Setting up environment..."
|
|
89
|
+
|
|
90
|
+
if [ "$DRY_RUN" = true ]; then
|
|
91
|
+
echo " [DRY RUN] Would create ${MARK_HOME}/.env"
|
|
92
|
+
else
|
|
93
|
+
if [ ! -f "${MARK_HOME}/.env" ]; then
|
|
94
|
+
cat > "${MARK_HOME}/.env" <<ENVEOF
|
|
95
|
+
# Mighty Mark — Environment Configuration
|
|
96
|
+
# Created by install.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
97
|
+
|
|
98
|
+
# Telegram Bot (@MightyMarkBot)
|
|
99
|
+
MIGHTY_MARK_TELEGRAM_TOKEN="${TELEGRAM_TOKEN}"
|
|
100
|
+
MIGHTY_MARK_TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID}"
|
|
101
|
+
|
|
102
|
+
# Thresholds (uncomment to override defaults)
|
|
103
|
+
# MAX_RESTART_ATTEMPTS=3
|
|
104
|
+
# DISK_WARN_PERCENT=20
|
|
105
|
+
# MEMORY_WARN_MB=256
|
|
106
|
+
# CPU_WARN_LOAD=3.0
|
|
107
|
+
ENVEOF
|
|
108
|
+
chmod 600 "${MARK_HOME}/.env"
|
|
109
|
+
echo " Created: ${MARK_HOME}/.env (permissions: 600)"
|
|
110
|
+
else
|
|
111
|
+
echo " ${MARK_HOME}/.env already exists, skipping"
|
|
112
|
+
if [ -n "${TELEGRAM_TOKEN}" ]; then
|
|
113
|
+
# Update token in existing .env
|
|
114
|
+
if grep -q "MIGHTY_MARK_TELEGRAM_TOKEN" "${MARK_HOME}/.env"; then
|
|
115
|
+
sed -i "s|MIGHTY_MARK_TELEGRAM_TOKEN=.*|MIGHTY_MARK_TELEGRAM_TOKEN=\"${TELEGRAM_TOKEN}\"|" "${MARK_HOME}/.env"
|
|
116
|
+
else
|
|
117
|
+
echo "MIGHTY_MARK_TELEGRAM_TOKEN=\"${TELEGRAM_TOKEN}\"" >> "${MARK_HOME}/.env"
|
|
118
|
+
fi
|
|
119
|
+
echo " Updated Telegram token in .env"
|
|
120
|
+
fi
|
|
121
|
+
if [ -n "${TELEGRAM_CHAT_ID}" ]; then
|
|
122
|
+
if grep -q "MIGHTY_MARK_TELEGRAM_CHAT_ID" "${MARK_HOME}/.env"; then
|
|
123
|
+
sed -i "s|MIGHTY_MARK_TELEGRAM_CHAT_ID=.*|MIGHTY_MARK_TELEGRAM_CHAT_ID=\"${TELEGRAM_CHAT_ID}\"|" "${MARK_HOME}/.env"
|
|
124
|
+
else
|
|
125
|
+
echo "MIGHTY_MARK_TELEGRAM_CHAT_ID=\"${TELEGRAM_CHAT_ID}\"" >> "${MARK_HOME}/.env"
|
|
126
|
+
fi
|
|
127
|
+
echo " Updated Telegram chat ID in .env"
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# ── Step 4: Install cron job ──
|
|
133
|
+
echo "[4/7] Installing cron schedule..."
|
|
134
|
+
|
|
135
|
+
if [ "$SKIP_CRON" = true ]; then
|
|
136
|
+
echo " Skipped (--skip-cron)"
|
|
137
|
+
elif [ "$DRY_RUN" = true ]; then
|
|
138
|
+
echo " [DRY RUN] Would create /etc/cron.d/mighty-mark"
|
|
139
|
+
else
|
|
140
|
+
cat > /etc/cron.d/mighty-mark <<CRONEOF
|
|
141
|
+
# /etc/cron.d/mighty-mark
|
|
142
|
+
# Mighty Mark — System Health Sentinel
|
|
143
|
+
# CRON_TZ ensures schedules are in America/Chicago regardless of DST
|
|
144
|
+
CRON_TZ=America/Chicago
|
|
145
|
+
|
|
146
|
+
# Watchdog: every 5 minutes — check gateway heartbeat
|
|
147
|
+
*/5 * * * * root ${MARK_HOME}/watchdog.sh >> ${MARK_HOME}/logs/watchdog-cron.log 2>&1
|
|
148
|
+
|
|
149
|
+
# Morning Check: daily at 6:00 AM Chicago time (auto-adjusts for CDT/CST)
|
|
150
|
+
0 6 * * * root ${MARK_HOME}/morning-check.sh >> ${MARK_HOME}/logs/morning-cron.log 2>&1
|
|
151
|
+
CRONEOF
|
|
152
|
+
chmod 644 /etc/cron.d/mighty-mark
|
|
153
|
+
echo " Installed: /etc/cron.d/mighty-mark"
|
|
154
|
+
echo " Watchdog: */5 * * * * (every 5 min)"
|
|
155
|
+
echo " Morning Check: 0 6 * * * (daily 6:00 AM CT)"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# ── Step 5: Install logrotate ──
|
|
159
|
+
echo "[5/7] Installing logrotate..."
|
|
160
|
+
|
|
161
|
+
if [ "$SKIP_LOGROTATE" = true ]; then
|
|
162
|
+
echo " Skipped (--skip-logrotate)"
|
|
163
|
+
elif [ "$DRY_RUN" = true ]; then
|
|
164
|
+
echo " [DRY RUN] Would create /etc/logrotate.d/mighty-mark"
|
|
165
|
+
else
|
|
166
|
+
cat > /etc/logrotate.d/mighty-mark <<LREOF
|
|
167
|
+
${MARK_HOME}/logs/*.log {
|
|
168
|
+
daily
|
|
169
|
+
missingok
|
|
170
|
+
rotate 14
|
|
171
|
+
compress
|
|
172
|
+
delaycompress
|
|
173
|
+
notifempty
|
|
174
|
+
create 0644 root root
|
|
175
|
+
sharedscripts
|
|
176
|
+
postrotate
|
|
177
|
+
true
|
|
178
|
+
endscript
|
|
179
|
+
}
|
|
180
|
+
LREOF
|
|
181
|
+
chmod 644 /etc/logrotate.d/mighty-mark
|
|
182
|
+
echo " Installed: /etc/logrotate.d/mighty-mark (14-day rotation)"
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# ── Step 6: Initialize state ──
|
|
186
|
+
echo "[6/7] Initializing state files..."
|
|
187
|
+
|
|
188
|
+
if [ "$DRY_RUN" = true ]; then
|
|
189
|
+
echo " [DRY RUN] Would initialize state files"
|
|
190
|
+
else
|
|
191
|
+
if [ ! -f "${MARK_HOME}/state/incident-count.json" ]; then
|
|
192
|
+
echo '{}' > "${MARK_HOME}/state/incident-count.json"
|
|
193
|
+
echo " Created: incident-count.json"
|
|
194
|
+
else
|
|
195
|
+
echo " incident-count.json already exists"
|
|
196
|
+
fi
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
# ── Step 7: Test Telegram connectivity ──
|
|
200
|
+
echo "[7/7] Testing Telegram connectivity..."
|
|
201
|
+
|
|
202
|
+
if [ "$DRY_RUN" = true ]; then
|
|
203
|
+
echo " [DRY RUN] Would test Telegram API"
|
|
204
|
+
elif [ -z "${TELEGRAM_TOKEN}" ]; then
|
|
205
|
+
echo " Skipped — no Telegram token provided"
|
|
206
|
+
echo " Set MIGHTY_MARK_TELEGRAM_TOKEN in ${MARK_HOME}/.env"
|
|
207
|
+
else
|
|
208
|
+
source "${MARK_HOME}/lib/config.sh"
|
|
209
|
+
TELEGRAM_BOT_TOKEN="${TELEGRAM_TOKEN}"
|
|
210
|
+
|
|
211
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
212
|
+
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe" 2>/dev/null || echo "000")
|
|
213
|
+
|
|
214
|
+
if [ "${HTTP_CODE}" = "200" ]; then
|
|
215
|
+
echo " Telegram Bot API reachable (HTTP 200)"
|
|
216
|
+
else
|
|
217
|
+
echo " WARNING: Telegram Bot API returned HTTP ${HTTP_CODE}"
|
|
218
|
+
echo " Verify the bot token is correct"
|
|
219
|
+
fi
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
# ── Done ──
|
|
223
|
+
echo ""
|
|
224
|
+
echo "╔══════════════════════════════════════════════════╗"
|
|
225
|
+
echo "║ Mighty Mark watchdog installed successfully! ║"
|
|
226
|
+
echo "╚══════════════════════════════════════════════════╝"
|
|
227
|
+
echo ""
|
|
228
|
+
echo " Install directory: ${MARK_HOME}"
|
|
229
|
+
echo " Cron schedule: /etc/cron.d/mighty-mark"
|
|
230
|
+
echo " Logrotate: /etc/logrotate.d/mighty-mark"
|
|
231
|
+
echo " Logs: ${MARK_HOME}/logs/"
|
|
232
|
+
echo " State: ${MARK_HOME}/state/"
|
|
233
|
+
echo ""
|
|
234
|
+
|
|
235
|
+
if [ -z "${TELEGRAM_TOKEN}" ]; then
|
|
236
|
+
echo " NEXT STEPS:"
|
|
237
|
+
echo " 1. Create @MightyMarkBot on Telegram"
|
|
238
|
+
echo " 2. Edit ${MARK_HOME}/.env with your bot token and chat ID"
|
|
239
|
+
echo " 3. Test: bash ${MARK_HOME}/watchdog.sh"
|
|
240
|
+
echo ""
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
echo " Manual test: bash ${MARK_HOME}/watchdog.sh"
|
|
244
|
+
echo " View logs: tail -f ${MARK_HOME}/logs/watchdog.log"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# /opt/mighty-mark/lib/config.sh
|
|
3
|
+
# Mighty Mark — Shared configuration for all watchdog scripts
|
|
4
|
+
# Sourced by watchdog.sh, morning-check.sh, and install.sh
|
|
5
|
+
|
|
6
|
+
# ── Paths ──
|
|
7
|
+
MARK_HOME="${MARK_HOME:-/opt/mighty-mark}"
|
|
8
|
+
MARK_LOG_DIR="${MARK_HOME}/logs"
|
|
9
|
+
MARK_STATE_DIR="${MARK_HOME}/state"
|
|
10
|
+
|
|
11
|
+
# ── OpenClaw ──
|
|
12
|
+
OPENCLAW_BIN="${OPENCLAW_BIN:-/usr/bin/openclaw}"
|
|
13
|
+
OPENCLAW_HOME="${OPENCLAW_HOME:-${HOME}/.clawdbot}"
|
|
14
|
+
OPENCLAW_CONFIG="${OPENCLAW_HOME}/openclaw.json"
|
|
15
|
+
OPENCLAW_SERVICE="openclaw-gateway"
|
|
16
|
+
|
|
17
|
+
# ── Telegram ──
|
|
18
|
+
# These MUST be set as environment variables or in /opt/mighty-mark/.env
|
|
19
|
+
TELEGRAM_BOT_TOKEN="${MIGHTY_MARK_TELEGRAM_TOKEN:-}"
|
|
20
|
+
TELEGRAM_CHAT_ID="${MIGHTY_MARK_TELEGRAM_CHAT_ID:-}"
|
|
21
|
+
|
|
22
|
+
# ── Thresholds ──
|
|
23
|
+
MAX_RESTART_ATTEMPTS="${MAX_RESTART_ATTEMPTS:-3}"
|
|
24
|
+
RESTART_COOLDOWN_SECONDS="${RESTART_COOLDOWN_SECONDS:-300}"
|
|
25
|
+
DISK_WARN_PERCENT="${DISK_WARN_PERCENT:-20}"
|
|
26
|
+
MEMORY_WARN_MB="${MEMORY_WARN_MB:-256}"
|
|
27
|
+
CPU_WARN_LOAD="${CPU_WARN_LOAD:-3.0}"
|
|
28
|
+
|
|
29
|
+
# ── State Files ──
|
|
30
|
+
INCIDENT_COUNT_FILE="${MARK_STATE_DIR}/incident-count.json"
|
|
31
|
+
LAST_CHECK_FILE="${MARK_STATE_DIR}/last-check.json"
|
|
32
|
+
|
|
33
|
+
# ── Log Files ──
|
|
34
|
+
WATCHDOG_LOG="${MARK_LOG_DIR}/watchdog.log"
|
|
35
|
+
MORNING_LOG="${MARK_LOG_DIR}/morning-check.log"
|
|
36
|
+
GATEWAY_RESTART_LOG="${MARK_LOG_DIR}/gateway-restart.log"
|
|
37
|
+
|
|
38
|
+
# ── Load .env if present ──
|
|
39
|
+
if [ -f "${MARK_HOME}/.env" ]; then
|
|
40
|
+
# shellcheck source=/dev/null
|
|
41
|
+
set -a
|
|
42
|
+
source "${MARK_HOME}/.env"
|
|
43
|
+
set +a
|
|
44
|
+
fi
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# /opt/mighty-mark/lib/logging.sh
|
|
3
|
+
# Mighty Mark — Structured logging functions
|
|
4
|
+
|
|
5
|
+
log_watchdog() {
|
|
6
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [WATCHDOG] $1" >> "${WATCHDOG_LOG:-/opt/mighty-mark/logs/watchdog.log}"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
log_morning() {
|
|
10
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [MORNING-CHECK] $1" >> "${MORNING_LOG:-/opt/mighty-mark/logs/morning-check.log}"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
log_info() {
|
|
14
|
+
local component="${1}"
|
|
15
|
+
local message="${2}"
|
|
16
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [${component}] INFO: ${message}"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
log_warn() {
|
|
20
|
+
local component="${1}"
|
|
21
|
+
local message="${2}"
|
|
22
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [${component}] WARN: ${message}"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
log_error() {
|
|
26
|
+
local component="${1}"
|
|
27
|
+
local message="${2}"
|
|
28
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [${component}] ERROR: ${message}" >&2
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# /opt/mighty-mark/lib/notify.sh
|
|
3
|
+
# Mighty Mark — Telegram notification functions
|
|
4
|
+
|
|
5
|
+
notify_telegram() {
|
|
6
|
+
local message="$1"
|
|
7
|
+
|
|
8
|
+
if [ -z "${TELEGRAM_BOT_TOKEN}" ] || [ -z "${TELEGRAM_CHAT_ID}" ]; then
|
|
9
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [NOTIFY] WARN: Telegram credentials not configured, skipping notification"
|
|
10
|
+
return 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
curl -s -X POST \
|
|
14
|
+
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
15
|
+
-d "chat_id=${TELEGRAM_CHAT_ID}" \
|
|
16
|
+
-d "text=${message}" \
|
|
17
|
+
-d "parse_mode=Markdown" \
|
|
18
|
+
> /dev/null 2>&1
|
|
19
|
+
|
|
20
|
+
if [ $? -eq 0 ]; then
|
|
21
|
+
return 0
|
|
22
|
+
else
|
|
23
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [NOTIFY] WARN: Telegram notification failed"
|
|
24
|
+
return 1
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
notify_recovery() {
|
|
29
|
+
local restart_count="$1"
|
|
30
|
+
local hostname
|
|
31
|
+
hostname=$(hostname)
|
|
32
|
+
notify_telegram "🟡 *MIGHTY MARK RECOVERY*%0A%0AOpenClaw gateway was down and has been restarted.%0ARestart #${restart_count} today.%0A%0AServer: ${hostname}%0ATime: $(date)"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
notify_max_restarts() {
|
|
36
|
+
local max="$1"
|
|
37
|
+
local hostname
|
|
38
|
+
hostname=$(hostname)
|
|
39
|
+
notify_telegram "🔴 *MIGHTY MARK ALERT*%0A%0AOpenClaw gateway has crashed ${max} times today.%0AManual intervention required.%0A%0AServer: ${hostname}%0ATime: $(date)"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
notify_restart_failed() {
|
|
43
|
+
local hostname
|
|
44
|
+
hostname=$(hostname)
|
|
45
|
+
notify_telegram "🔴 *MIGHTY MARK ALERT*%0A%0AOpenClaw gateway restart FAILED.%0AManual intervention required.%0A%0AServer: ${hostname}%0ATime: $(date)"
|
|
46
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# /opt/mighty-mark/morning-check.sh
|
|
3
|
+
# Mighty Mark Morning Check — Runs the Level 2 health check via Node.js
|
|
4
|
+
#
|
|
5
|
+
# Cron schedule: 0 6 * * * (with CRON_TZ=America/Chicago)
|
|
6
|
+
#
|
|
7
|
+
# This script is a thin bash wrapper that invokes the TypeScript health
|
|
8
|
+
# engine via npx. The actual check logic lives in the npm package.
|
|
9
|
+
# If Node.js is unavailable, it falls back to a minimal bash-only check.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
# ── Load shared config ──
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
source "${SCRIPT_DIR}/lib/config.sh"
|
|
16
|
+
source "${SCRIPT_DIR}/lib/logging.sh"
|
|
17
|
+
source "${SCRIPT_DIR}/lib/notify.sh"
|
|
18
|
+
|
|
19
|
+
# ── Ensure directories exist ──
|
|
20
|
+
mkdir -p "${MARK_LOG_DIR}" "${MARK_STATE_DIR}"
|
|
21
|
+
|
|
22
|
+
log_morning "Morning check starting"
|
|
23
|
+
|
|
24
|
+
# ── Try Node.js health check first ──
|
|
25
|
+
if command -v npx &> /dev/null; then
|
|
26
|
+
log_morning "Running TypeScript health engine via npx"
|
|
27
|
+
|
|
28
|
+
if npx @aiassesstech/mighty-mark check >> "${MORNING_LOG}" 2>&1; then
|
|
29
|
+
log_morning "Morning check completed successfully (TypeScript engine)"
|
|
30
|
+
exit 0
|
|
31
|
+
else
|
|
32
|
+
log_morning "WARN: TypeScript engine failed, falling back to bash checks"
|
|
33
|
+
fi
|
|
34
|
+
else
|
|
35
|
+
log_morning "WARN: npx not found, using bash-only checks"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# ── Bash Fallback ─────────────────────────────────────────────────
|
|
39
|
+
# Minimal health check when Node.js is unavailable
|
|
40
|
+
|
|
41
|
+
CHECKS_PASSED=0
|
|
42
|
+
CHECKS_FAILED=0
|
|
43
|
+
CHECKS_TOTAL=0
|
|
44
|
+
FAILURES=""
|
|
45
|
+
|
|
46
|
+
run_check() {
|
|
47
|
+
local name="$1"
|
|
48
|
+
local result="$2"
|
|
49
|
+
CHECKS_TOTAL=$((CHECKS_TOTAL + 1))
|
|
50
|
+
if [ "${result}" -eq 0 ]; then
|
|
51
|
+
CHECKS_PASSED=$((CHECKS_PASSED + 1))
|
|
52
|
+
log_morning " PASS: ${name}"
|
|
53
|
+
else
|
|
54
|
+
CHECKS_FAILED=$((CHECKS_FAILED + 1))
|
|
55
|
+
FAILURES="${FAILURES} - ${name}%0A"
|
|
56
|
+
log_morning " FAIL: ${name}"
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Check 1: Gateway process
|
|
61
|
+
pgrep -f "${OPENCLAW_SERVICE}" > /dev/null 2>&1
|
|
62
|
+
run_check "Gateway process alive" $?
|
|
63
|
+
|
|
64
|
+
# Check 2: Config file exists
|
|
65
|
+
test -f "${OPENCLAW_CONFIG}"
|
|
66
|
+
run_check "Gateway config exists" $?
|
|
67
|
+
|
|
68
|
+
# Check 3: Disk space > 10% free
|
|
69
|
+
DISK_FREE=$(df / | awk 'NR==2{gsub(/%/,""); print 100-$5}')
|
|
70
|
+
test "${DISK_FREE}" -gt 10 2>/dev/null
|
|
71
|
+
run_check "Disk space > 10% free (${DISK_FREE}%)" $?
|
|
72
|
+
|
|
73
|
+
# Check 4: Memory available
|
|
74
|
+
MEM_AVAIL=$(free -m 2>/dev/null | awk 'NR==2{print $7}' || echo 9999)
|
|
75
|
+
test "${MEM_AVAIL}" -gt 128 2>/dev/null
|
|
76
|
+
run_check "Memory > 128MB available (${MEM_AVAIL}MB)" $?
|
|
77
|
+
|
|
78
|
+
# Check 5: System uptime > 5 min
|
|
79
|
+
UPTIME_SEC=$(awk '{printf "%d", $1}' /proc/uptime 2>/dev/null || echo 99999)
|
|
80
|
+
test "${UPTIME_SEC}" -gt 300 2>/dev/null
|
|
81
|
+
run_check "System uptime > 5 min" $?
|
|
82
|
+
|
|
83
|
+
# ── Determine status ──
|
|
84
|
+
if [ "${CHECKS_FAILED}" -gt 0 ]; then
|
|
85
|
+
STATUS="RED"
|
|
86
|
+
EMOJI="🔴"
|
|
87
|
+
elif [ "${CHECKS_FAILED}" -eq 0 ] && [ "${CHECKS_TOTAL}" -gt 0 ]; then
|
|
88
|
+
STATUS="GREEN"
|
|
89
|
+
EMOJI="🟢"
|
|
90
|
+
else
|
|
91
|
+
STATUS="YELLOW"
|
|
92
|
+
EMOJI="🟡"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
log_morning "Morning check complete: ${STATUS} (${CHECKS_PASSED}/${CHECKS_TOTAL} passed)"
|
|
96
|
+
|
|
97
|
+
# ── Notify via Telegram ──
|
|
98
|
+
HOSTNAME=$(hostname)
|
|
99
|
+
MESSAGE="${EMOJI} *MIGHTY MARK MORNING CHECK* (bash fallback)%0A━━━━━━━━━━━━━━━━━━━━%0A*Status:* ${STATUS}%0A*Results:* ${CHECKS_PASSED}/${CHECKS_TOTAL} passed%0A*Server:* ${HOSTNAME}%0A*Time:* $(date)"
|
|
100
|
+
|
|
101
|
+
if [ "${CHECKS_FAILED}" -gt 0 ]; then
|
|
102
|
+
MESSAGE="${MESSAGE}%0A%0A❌ *Failures:*%0A${FAILURES}"
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
notify_telegram "${MESSAGE}"
|
|
106
|
+
|
|
107
|
+
log_morning "Morning check notification sent"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# /etc/systemd/system/openclaw-gateway.service
|
|
2
|
+
# OpenClaw AI Gateway — Systemd Service
|
|
3
|
+
#
|
|
4
|
+
# Belt-and-suspenders with Mighty Mark watchdog:
|
|
5
|
+
# - Systemd handles fast restarts (Restart=on-failure, RestartSec=30)
|
|
6
|
+
# - Mighty Mark watchdog handles the case where systemd gives up (StartLimitBurst=5)
|
|
7
|
+
#
|
|
8
|
+
# Install:
|
|
9
|
+
# sudo cp openclaw-gateway.service /etc/systemd/system/
|
|
10
|
+
# sudo systemctl daemon-reload
|
|
11
|
+
# sudo systemctl enable openclaw-gateway
|
|
12
|
+
# sudo systemctl start openclaw-gateway
|
|
13
|
+
|
|
14
|
+
[Unit]
|
|
15
|
+
Description=OpenClaw AI Gateway
|
|
16
|
+
After=network.target
|
|
17
|
+
Documentation=https://www.aiassesstech.com
|
|
18
|
+
|
|
19
|
+
[Service]
|
|
20
|
+
Type=simple
|
|
21
|
+
User=root
|
|
22
|
+
WorkingDirectory=/root
|
|
23
|
+
ExecStart=/usr/bin/openclaw gateway start
|
|
24
|
+
Restart=on-failure
|
|
25
|
+
RestartSec=30
|
|
26
|
+
StartLimitBurst=5
|
|
27
|
+
StartLimitIntervalSec=600
|
|
28
|
+
|
|
29
|
+
# Environment
|
|
30
|
+
Environment=NODE_ENV=production
|
|
31
|
+
|
|
32
|
+
# Logging
|
|
33
|
+
StandardOutput=journal
|
|
34
|
+
StandardError=journal
|
|
35
|
+
SyslogIdentifier=openclaw-gateway
|
|
36
|
+
|
|
37
|
+
# Hardening
|
|
38
|
+
NoNewPrivileges=true
|
|
39
|
+
ProtectSystem=strict
|
|
40
|
+
ReadWritePaths=/root/.clawdbot /root/.openclaw /tmp
|
|
41
|
+
|
|
42
|
+
[Install]
|
|
43
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# /opt/mighty-mark/watchdog.sh
|
|
3
|
+
# Mighty Mark Watchdog — Keeps the OpenClaw gateway alive
|
|
4
|
+
#
|
|
5
|
+
# Runs via cron every 5 minutes (CRON_TZ=America/Chicago)
|
|
6
|
+
# Zero Node.js dependencies — pure bash + standard Unix tools
|
|
7
|
+
#
|
|
8
|
+
# Behavior:
|
|
9
|
+
# 1. Check if openclaw-gateway process is alive
|
|
10
|
+
# 2. If alive: log heartbeat, exit 0
|
|
11
|
+
# 3. If dead: attempt restart via systemctl
|
|
12
|
+
# 4. If restart succeeds: notify Greg via Telegram
|
|
13
|
+
# 5. If restart fails: notify Greg with escalation alert
|
|
14
|
+
# 6. Max 3 restart attempts per day
|
|
15
|
+
# 7. State persists in /opt/mighty-mark/state/incident-count.json
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
# ── Load shared config ──
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
source "${SCRIPT_DIR}/lib/config.sh"
|
|
22
|
+
source "${SCRIPT_DIR}/lib/logging.sh"
|
|
23
|
+
source "${SCRIPT_DIR}/lib/notify.sh"
|
|
24
|
+
|
|
25
|
+
# ── Ensure directories exist ──
|
|
26
|
+
mkdir -p "${MARK_LOG_DIR}" "${MARK_STATE_DIR}"
|
|
27
|
+
|
|
28
|
+
# ── Functions ──
|
|
29
|
+
|
|
30
|
+
is_gateway_alive() {
|
|
31
|
+
if ! pgrep -f "${OPENCLAW_SERVICE}" > /dev/null 2>&1; then
|
|
32
|
+
return 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
local pid
|
|
36
|
+
pid=$(pgrep -f "${OPENCLAW_SERVICE}" | head -1)
|
|
37
|
+
if [ -n "${pid}" ] && [ -d "/proc/${pid}" ]; then
|
|
38
|
+
local state
|
|
39
|
+
state=$(awk '/^State:/ {print $2}' "/proc/${pid}/status" 2>/dev/null || echo "?")
|
|
40
|
+
if [ "${state}" = "Z" ]; then
|
|
41
|
+
log_watchdog "Gateway process ${pid} is zombie"
|
|
42
|
+
return 1
|
|
43
|
+
fi
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
return 1
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get_restart_count_today() {
|
|
51
|
+
if [ -f "${INCIDENT_COUNT_FILE}" ]; then
|
|
52
|
+
local today
|
|
53
|
+
today=$(date +%Y-%m-%d)
|
|
54
|
+
local count
|
|
55
|
+
count=$(jq -r --arg d "${today}" '.[$d] // 0' "${INCIDENT_COUNT_FILE}" 2>/dev/null || echo 0)
|
|
56
|
+
echo "${count}"
|
|
57
|
+
else
|
|
58
|
+
echo 0
|
|
59
|
+
fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
increment_restart_count() {
|
|
63
|
+
local today
|
|
64
|
+
today=$(date +%Y-%m-%d)
|
|
65
|
+
if [ -f "${INCIDENT_COUNT_FILE}" ]; then
|
|
66
|
+
local tmp
|
|
67
|
+
tmp=$(mktemp)
|
|
68
|
+
jq --arg d "${today}" '.[$d] = ((.[$d] // 0) + 1)' "${INCIDENT_COUNT_FILE}" > "${tmp}" && mv "${tmp}" "${INCIDENT_COUNT_FILE}"
|
|
69
|
+
else
|
|
70
|
+
echo "{\"${today}\": 1}" > "${INCIDENT_COUNT_FILE}"
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
restart_gateway() {
|
|
75
|
+
local restart_count
|
|
76
|
+
restart_count=$(get_restart_count_today)
|
|
77
|
+
|
|
78
|
+
if [ "${restart_count}" -ge "${MAX_RESTART_ATTEMPTS}" ]; then
|
|
79
|
+
log_watchdog "ERROR: Max restart attempts (${MAX_RESTART_ATTEMPTS}) reached today. Manual intervention required."
|
|
80
|
+
notify_max_restarts "${MAX_RESTART_ATTEMPTS}"
|
|
81
|
+
return 1
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
log_watchdog "Attempting gateway restart (attempt $((restart_count + 1))/${MAX_RESTART_ATTEMPTS} today)"
|
|
85
|
+
|
|
86
|
+
# Kill any remaining processes
|
|
87
|
+
pkill -f "${OPENCLAW_SERVICE}" 2>/dev/null || true
|
|
88
|
+
sleep 2
|
|
89
|
+
|
|
90
|
+
# Start gateway
|
|
91
|
+
if systemctl is-enabled "${OPENCLAW_SERVICE}" > /dev/null 2>&1; then
|
|
92
|
+
systemctl restart "${OPENCLAW_SERVICE}"
|
|
93
|
+
else
|
|
94
|
+
# Fallback: direct start
|
|
95
|
+
nohup "${OPENCLAW_BIN}" gateway start --config "${OPENCLAW_CONFIG}" \
|
|
96
|
+
>> "${GATEWAY_RESTART_LOG}" 2>&1 &
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Wait for startup
|
|
100
|
+
sleep 10
|
|
101
|
+
|
|
102
|
+
if is_gateway_alive; then
|
|
103
|
+
increment_restart_count
|
|
104
|
+
log_watchdog "Gateway restarted successfully"
|
|
105
|
+
notify_recovery "$((restart_count + 1))"
|
|
106
|
+
return 0
|
|
107
|
+
else
|
|
108
|
+
increment_restart_count
|
|
109
|
+
log_watchdog "ERROR: Gateway restart failed"
|
|
110
|
+
notify_restart_failed
|
|
111
|
+
return 1
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# ── Main ──
|
|
116
|
+
log_watchdog "Heartbeat check"
|
|
117
|
+
|
|
118
|
+
if is_gateway_alive; then
|
|
119
|
+
log_watchdog "Gateway alive (PID: $(pgrep -f ${OPENCLAW_SERVICE} | head -1))"
|
|
120
|
+
exit 0
|
|
121
|
+
else
|
|
122
|
+
log_watchdog "WARNING: Gateway is DOWN"
|
|
123
|
+
restart_gateway
|
|
124
|
+
fi
|