@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.
Files changed (121) hide show
  1. package/agent/AGENTS.md +61 -0
  2. package/agent/IDENTITY.md +18 -0
  3. package/agent/SOUL.md +56 -0
  4. package/dist/checks/agent-verify.d.ts +15 -0
  5. package/dist/checks/agent-verify.d.ts.map +1 -0
  6. package/dist/checks/agent-verify.js +100 -0
  7. package/dist/checks/agent-verify.js.map +1 -0
  8. package/dist/checks/api-connectivity.d.ts +14 -0
  9. package/dist/checks/api-connectivity.d.ts.map +1 -0
  10. package/dist/checks/api-connectivity.js +77 -0
  11. package/dist/checks/api-connectivity.js.map +1 -0
  12. package/dist/checks/check-context.d.ts +36 -0
  13. package/dist/checks/check-context.d.ts.map +1 -0
  14. package/dist/checks/check-context.js +42 -0
  15. package/dist/checks/check-context.js.map +1 -0
  16. package/dist/checks/check-runner.d.ts +18 -0
  17. package/dist/checks/check-runner.d.ts.map +1 -0
  18. package/dist/checks/check-runner.js +109 -0
  19. package/dist/checks/check-runner.js.map +1 -0
  20. package/dist/checks/data-integrity.d.ts +16 -0
  21. package/dist/checks/data-integrity.d.ts.map +1 -0
  22. package/dist/checks/data-integrity.js +152 -0
  23. package/dist/checks/data-integrity.js.map +1 -0
  24. package/dist/checks/gateway-health.d.ts +17 -0
  25. package/dist/checks/gateway-health.d.ts.map +1 -0
  26. package/dist/checks/gateway-health.js +208 -0
  27. package/dist/checks/gateway-health.js.map +1 -0
  28. package/dist/checks/system-resources.d.ts +21 -0
  29. package/dist/checks/system-resources.d.ts.map +1 -0
  30. package/dist/checks/system-resources.js +136 -0
  31. package/dist/checks/system-resources.js.map +1 -0
  32. package/dist/cli/bin.d.ts +8 -0
  33. package/dist/cli/bin.d.ts.map +1 -0
  34. package/dist/cli/bin.js +12 -0
  35. package/dist/cli/bin.js.map +1 -0
  36. package/dist/cli/runner.d.ts +11 -0
  37. package/dist/cli/runner.d.ts.map +1 -0
  38. package/dist/cli/runner.js +99 -0
  39. package/dist/cli/runner.js.map +1 -0
  40. package/dist/cli/setup.d.ts +28 -0
  41. package/dist/cli/setup.d.ts.map +1 -0
  42. package/dist/cli/setup.js +238 -0
  43. package/dist/cli/setup.js.map +1 -0
  44. package/dist/config/config-schema.d.ts +56 -0
  45. package/dist/config/config-schema.d.ts.map +1 -0
  46. package/dist/config/config-schema.js +34 -0
  47. package/dist/config/config-schema.js.map +1 -0
  48. package/dist/config/defaults.d.ts +31 -0
  49. package/dist/config/defaults.d.ts.map +1 -0
  50. package/dist/config/defaults.js +31 -0
  51. package/dist/config/defaults.js.map +1 -0
  52. package/dist/config/fleet-topology.d.ts +22 -0
  53. package/dist/config/fleet-topology.d.ts.map +1 -0
  54. package/dist/config/fleet-topology.js +81 -0
  55. package/dist/config/fleet-topology.js.map +1 -0
  56. package/dist/history/incident-log.d.ts +19 -0
  57. package/dist/history/incident-log.d.ts.map +1 -0
  58. package/dist/history/incident-log.js +50 -0
  59. package/dist/history/incident-log.js.map +1 -0
  60. package/dist/history/trend-analyzer.d.ts +18 -0
  61. package/dist/history/trend-analyzer.d.ts.map +1 -0
  62. package/dist/history/trend-analyzer.js +80 -0
  63. package/dist/history/trend-analyzer.js.map +1 -0
  64. package/dist/history/uptime-tracker.d.ts +15 -0
  65. package/dist/history/uptime-tracker.d.ts.map +1 -0
  66. package/dist/history/uptime-tracker.js +56 -0
  67. package/dist/history/uptime-tracker.js.map +1 -0
  68. package/dist/index.d.ts +49 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +124 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/notify/alert-classifier.d.ts +19 -0
  73. package/dist/notify/alert-classifier.d.ts.map +1 -0
  74. package/dist/notify/alert-classifier.js +45 -0
  75. package/dist/notify/alert-classifier.js.map +1 -0
  76. package/dist/notify/report-formatter.d.ts +21 -0
  77. package/dist/notify/report-formatter.d.ts.map +1 -0
  78. package/dist/notify/report-formatter.js +139 -0
  79. package/dist/notify/report-formatter.js.map +1 -0
  80. package/dist/notify/telegram-notifier.d.ts +28 -0
  81. package/dist/notify/telegram-notifier.d.ts.map +1 -0
  82. package/dist/notify/telegram-notifier.js +69 -0
  83. package/dist/notify/telegram-notifier.js.map +1 -0
  84. package/dist/plugin.d.ts +18 -0
  85. package/dist/plugin.d.ts.map +1 -0
  86. package/dist/plugin.js +289 -0
  87. package/dist/plugin.js.map +1 -0
  88. package/dist/store/json-store.d.ts +22 -0
  89. package/dist/store/json-store.d.ts.map +1 -0
  90. package/dist/store/json-store.js +128 -0
  91. package/dist/store/json-store.js.map +1 -0
  92. package/dist/store/types.d.ts +19 -0
  93. package/dist/store/types.d.ts.map +1 -0
  94. package/dist/store/types.js +5 -0
  95. package/dist/store/types.js.map +1 -0
  96. package/dist/types/events.d.ts +33 -0
  97. package/dist/types/events.d.ts.map +1 -0
  98. package/dist/types/events.js +5 -0
  99. package/dist/types/events.js.map +1 -0
  100. package/dist/types/fleet.d.ts +27 -0
  101. package/dist/types/fleet.d.ts.map +1 -0
  102. package/dist/types/fleet.js +6 -0
  103. package/dist/types/fleet.js.map +1 -0
  104. package/dist/types/health.d.ts +58 -0
  105. package/dist/types/health.d.ts.map +1 -0
  106. package/dist/types/health.js +5 -0
  107. package/dist/types/health.js.map +1 -0
  108. package/dist/types/incident.d.ts +35 -0
  109. package/dist/types/incident.d.ts.map +1 -0
  110. package/dist/types/incident.js +5 -0
  111. package/dist/types/incident.js.map +1 -0
  112. package/openclaw.plugin.json +56 -0
  113. package/package.json +71 -0
  114. package/src/watchdog/README.md +123 -0
  115. package/src/watchdog/install.sh +244 -0
  116. package/src/watchdog/lib/config.sh +44 -0
  117. package/src/watchdog/lib/logging.sh +29 -0
  118. package/src/watchdog/lib/notify.sh +46 -0
  119. package/src/watchdog/morning-check.sh +107 -0
  120. package/src/watchdog/openclaw-gateway.service +43 -0
  121. 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