@aiassesstech/mighty-mark 0.3.29 → 0.3.32
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/dist/checks/backup-orchestrator.d.ts +33 -0
- package/dist/checks/backup-orchestrator.d.ts.map +1 -0
- package/dist/checks/backup-orchestrator.js +260 -0
- package/dist/checks/backup-orchestrator.js.map +1 -0
- package/dist/checks/check-runner.d.ts +7 -0
- package/dist/checks/check-runner.d.ts.map +1 -1
- package/dist/checks/check-runner.js +22 -2
- package/dist/checks/check-runner.js.map +1 -1
- package/dist/checks/data-integrity.d.ts +8 -1
- package/dist/checks/data-integrity.d.ts.map +1 -1
- package/dist/checks/data-integrity.js +99 -1
- package/dist/checks/data-integrity.js.map +1 -1
- package/package.json +3 -1
- package/src/watchdog/fleet-backup/config.sh +74 -0
- package/src/watchdog/fleet-backup/fleet-backup.sh +363 -0
- package/src/watchdog/fleet-backup/fleet-restore.sh +437 -0
- package/src/watchdog/fleet-backup/test/test-backup.sh +395 -0
- package/src/watchdog/fleet-backup/test/test-restore.sh +302 -0
- package/src/watchdog/install.sh +112 -4
- package/src/watchdog/morning-check.sh +6 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
3
|
+
# nole-fleet-backup — Restore from a backup archive
|
|
4
|
+
#
|
|
5
|
+
# Supports both full and light tiered backups, and archives that
|
|
6
|
+
# were split into chunks for GitHub storage.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ./fleet-restore.sh # List all
|
|
10
|
+
# ./fleet-restore.sh --list # List all
|
|
11
|
+
# ./fleet-restore.sh --inspect fleet-full-2026-03-02-090000 # Show contents
|
|
12
|
+
# ./fleet-restore.sh --verify fleet-full-2026-03-02-090000 # Verify checksum
|
|
13
|
+
# ./fleet-restore.sh --extract fleet-full-2026-03-02-090000 # Extract to /tmp
|
|
14
|
+
# ./fleet-restore.sh --restore fleet-full-2026-03-02-090000 # Full restore
|
|
15
|
+
# ./fleet-restore.sh --from-github # Download + restore from GitHub
|
|
16
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
|
|
22
|
+
if [[ -f "$SCRIPT_DIR/config.local.sh" ]]; then
|
|
23
|
+
source "$SCRIPT_DIR/config.local.sh"
|
|
24
|
+
else
|
|
25
|
+
source "$SCRIPT_DIR/config.sh"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
RED='\033[0;31m'
|
|
29
|
+
GREEN='\033[0;32m'
|
|
30
|
+
YELLOW='\033[1;33m'
|
|
31
|
+
NC='\033[0m'
|
|
32
|
+
|
|
33
|
+
log() { echo -e "${GREEN}[restore]${NC} $1"; }
|
|
34
|
+
warn() { echo -e "${YELLOW}[restore]${NC} $1"; }
|
|
35
|
+
err() { echo -e "${RED}[restore]${NC} $1" >&2; }
|
|
36
|
+
|
|
37
|
+
list_backups() {
|
|
38
|
+
echo "Available backups in $BACKUP_DIR:"
|
|
39
|
+
echo ""
|
|
40
|
+
if [[ ! -d "$BACKUP_DIR" ]]; then
|
|
41
|
+
err "Backup directory not found: $BACKUP_DIR"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
local count=0
|
|
46
|
+
for archive in $(find "$BACKUP_DIR" -name 'fleet-*.tar.gz' | sort -r); do
|
|
47
|
+
local name=$(basename "$archive" .tar.gz)
|
|
48
|
+
local size=$(stat -c%s "$archive" 2>/dev/null || stat -f%z "$archive" 2>/dev/null || echo 0)
|
|
49
|
+
local size_mb=$(awk "BEGIN {printf \"%.1f\", $size / 1048576}")
|
|
50
|
+
|
|
51
|
+
local tier="unknown"
|
|
52
|
+
if [[ "$name" == *"-full-"* ]]; then
|
|
53
|
+
tier="FULL"
|
|
54
|
+
elif [[ "$name" == *"-light-"* ]]; then
|
|
55
|
+
tier="light"
|
|
56
|
+
elif [[ "$name" == *"fleet-backup-"* ]]; then
|
|
57
|
+
tier="legacy"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
printf " %-8s %-42s %8s MB\n" "[$tier]" "$name" "$size_mb"
|
|
61
|
+
count=$((count + 1))
|
|
62
|
+
done
|
|
63
|
+
|
|
64
|
+
echo ""
|
|
65
|
+
echo "$count backup(s) found. Retention: $RETENTION_DAYS days."
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get_archive_path() {
|
|
69
|
+
local id="$1"
|
|
70
|
+
local path="$BACKUP_DIR/${id}.tar.gz"
|
|
71
|
+
|
|
72
|
+
if [[ -f "$path" ]]; then
|
|
73
|
+
echo "$path"
|
|
74
|
+
return
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Try legacy naming (fleet-backup-YYYY-MM-DD.tar.gz)
|
|
78
|
+
local legacy="$BACKUP_DIR/fleet-backup-${id}.tar.gz"
|
|
79
|
+
if [[ -f "$legacy" ]]; then
|
|
80
|
+
echo "$legacy"
|
|
81
|
+
return
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Search for partial match
|
|
85
|
+
local matches
|
|
86
|
+
matches=$(find "$BACKUP_DIR" -name "*${id}*.tar.gz" 2>/dev/null | head -1)
|
|
87
|
+
if [[ -n "$matches" ]]; then
|
|
88
|
+
echo "$matches"
|
|
89
|
+
return
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
err "No backup found matching: $id"
|
|
93
|
+
err "Expected: $path"
|
|
94
|
+
echo ""
|
|
95
|
+
list_backups
|
|
96
|
+
exit 1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
verify_backup() {
|
|
100
|
+
local id="$1"
|
|
101
|
+
local archive
|
|
102
|
+
archive=$(get_archive_path "$id")
|
|
103
|
+
|
|
104
|
+
log "Verifying checksum for $(basename "$archive")..."
|
|
105
|
+
|
|
106
|
+
local actual
|
|
107
|
+
actual=$(sha256sum "$archive" 2>/dev/null | awk '{print $1}' || shasum -a 256 "$archive" | awk '{print $1}')
|
|
108
|
+
|
|
109
|
+
if [[ -f "$MANIFEST_FILE" ]]; then
|
|
110
|
+
local manifest_checksum
|
|
111
|
+
manifest_checksum=$(grep -o '"checksum": *"[^"]*"' "$MANIFEST_FILE" | head -1 | sed 's/.*: *"sha256://;s/"//')
|
|
112
|
+
local manifest_archive
|
|
113
|
+
manifest_archive=$(grep -o '"archive": *"[^"]*"' "$MANIFEST_FILE" | head -1 | sed 's/.*: *"//;s/"//')
|
|
114
|
+
|
|
115
|
+
if [[ "$(basename "$archive")" == "$manifest_archive" && -n "$manifest_checksum" ]]; then
|
|
116
|
+
if [[ "$actual" == "$manifest_checksum" ]]; then
|
|
117
|
+
log "Checksum VERIFIED: sha256:$actual"
|
|
118
|
+
return 0
|
|
119
|
+
else
|
|
120
|
+
err "Checksum MISMATCH!"
|
|
121
|
+
err " Expected: $manifest_checksum"
|
|
122
|
+
err " Actual: $actual"
|
|
123
|
+
return 1
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
log "Checksum: sha256:$actual (no matching manifest to compare against)"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
inspect_backup() {
|
|
132
|
+
local id="$1"
|
|
133
|
+
local archive
|
|
134
|
+
archive=$(get_archive_path "$id")
|
|
135
|
+
|
|
136
|
+
log "Contents of $(basename "$archive"):"
|
|
137
|
+
echo ""
|
|
138
|
+
tar -tzf "$archive" | head -100
|
|
139
|
+
echo ""
|
|
140
|
+
|
|
141
|
+
local total
|
|
142
|
+
total=$(tar -tzf "$archive" | wc -l | tr -d ' ')
|
|
143
|
+
log "Total entries: $total"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
extract_backup() {
|
|
147
|
+
local id="$1"
|
|
148
|
+
local archive
|
|
149
|
+
archive=$(get_archive_path "$id")
|
|
150
|
+
local name=$(basename "$archive" .tar.gz)
|
|
151
|
+
local extract_dir="/tmp/fleet-restore-${name}"
|
|
152
|
+
|
|
153
|
+
if [[ -d "$extract_dir" ]]; then
|
|
154
|
+
warn "Extract directory already exists: $extract_dir"
|
|
155
|
+
read -p "Remove and re-extract? (y/N): " confirm
|
|
156
|
+
[[ "$confirm" =~ ^[Yy]$ ]] || exit 0
|
|
157
|
+
rm -rf "$extract_dir"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
log "Extracting to $extract_dir..."
|
|
161
|
+
mkdir -p "$extract_dir"
|
|
162
|
+
tar -xzf "$archive" -C "$extract_dir"
|
|
163
|
+
|
|
164
|
+
log "Extracted. Review contents:"
|
|
165
|
+
echo ""
|
|
166
|
+
ls -la "$extract_dir/fleet-backup/"
|
|
167
|
+
echo ""
|
|
168
|
+
log "To inspect: ls $extract_dir/fleet-backup/"
|
|
169
|
+
log "To clean up: rm -rf $extract_dir"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
from_github() {
|
|
173
|
+
if [[ ! -d "${GITHUB_REPO_DIR:-}" ]]; then
|
|
174
|
+
err "GITHUB_REPO_DIR not set or does not exist: ${GITHUB_REPO_DIR:-<unset>}"
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
log "Fetching latest backup from GitHub..."
|
|
179
|
+
local work_dir
|
|
180
|
+
work_dir=$(mktemp -d "/tmp/fleet-github-restore-XXXXXX")
|
|
181
|
+
|
|
182
|
+
(
|
|
183
|
+
cd "$GITHUB_REPO_DIR"
|
|
184
|
+
git fetch origin "${GITHUB_BACKUP_BRANCH}" 2>/dev/null || {
|
|
185
|
+
err "Could not fetch branch: $GITHUB_BACKUP_BRANCH"
|
|
186
|
+
exit 1
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
git worktree add "$work_dir" "origin/$GITHUB_BACKUP_BRANCH" 2>/dev/null || {
|
|
190
|
+
# Fallback: archive from remote
|
|
191
|
+
git archive "origin/$GITHUB_BACKUP_BRANCH" | tar -x -C "$work_dir"
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
cd "$work_dir"
|
|
196
|
+
|
|
197
|
+
# Check for chunked archive and reassemble
|
|
198
|
+
local chunks
|
|
199
|
+
chunks=$(find . -name 'fleet-*.tar.gz.part-*' 2>/dev/null | sort)
|
|
200
|
+
if [[ -n "$chunks" ]]; then
|
|
201
|
+
local base_name
|
|
202
|
+
base_name=$(echo "$chunks" | head -1 | sed 's/\.part-[0-9]*//')
|
|
203
|
+
base_name=$(basename "$base_name")
|
|
204
|
+
log "Reassembling ${base_name} from $(echo "$chunks" | wc -l | tr -d ' ') chunks..."
|
|
205
|
+
cat $chunks > "$BACKUP_DIR/$base_name"
|
|
206
|
+
log "Reassembled: $BACKUP_DIR/$base_name"
|
|
207
|
+
else
|
|
208
|
+
local archive
|
|
209
|
+
archive=$(find . -name 'fleet-*.tar.gz' 2>/dev/null | head -1)
|
|
210
|
+
if [[ -n "$archive" ]]; then
|
|
211
|
+
cp "$archive" "$BACKUP_DIR/"
|
|
212
|
+
log "Copied: $(basename "$archive") to $BACKUP_DIR/"
|
|
213
|
+
else
|
|
214
|
+
err "No archive found on the backups branch"
|
|
215
|
+
rm -rf "$work_dir"
|
|
216
|
+
exit 1
|
|
217
|
+
fi
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# Copy manifest
|
|
221
|
+
[[ -f "latest-manifest.json" ]] && cp "latest-manifest.json" "$MANIFEST_FILE"
|
|
222
|
+
|
|
223
|
+
rm -rf "$work_dir"
|
|
224
|
+
git -C "$GITHUB_REPO_DIR" worktree prune 2>/dev/null || true
|
|
225
|
+
|
|
226
|
+
log "Archive downloaded from GitHub. Use --list to see it, --restore to apply."
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
restore_backup() {
|
|
230
|
+
local id="$1"
|
|
231
|
+
local archive
|
|
232
|
+
archive=$(get_archive_path "$id")
|
|
233
|
+
local name=$(basename "$archive" .tar.gz)
|
|
234
|
+
|
|
235
|
+
local tier="unknown"
|
|
236
|
+
if [[ "$name" == *"-full-"* ]]; then
|
|
237
|
+
tier="FULL"
|
|
238
|
+
elif [[ "$name" == *"-light-"* ]]; then
|
|
239
|
+
tier="LIGHT"
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
echo ""
|
|
243
|
+
warn "═══════════════════════════════════════════════════════"
|
|
244
|
+
warn " $tier RESTORE from backup: $name"
|
|
245
|
+
warn "═══════════════════════════════════════════════════════"
|
|
246
|
+
echo ""
|
|
247
|
+
|
|
248
|
+
if [[ "$tier" == "LIGHT" ]]; then
|
|
249
|
+
warn "This is a LIGHT backup. It contains only:"
|
|
250
|
+
echo " - Agent .md files (identity, prompts, memory markdown)"
|
|
251
|
+
echo " - Sanitized openclaw.json configs"
|
|
252
|
+
echo " - Systemd/cron/logrotate configs"
|
|
253
|
+
echo ""
|
|
254
|
+
warn "It does NOT contain:"
|
|
255
|
+
echo " - Extension dist/ code"
|
|
256
|
+
echo " - Vector indexes (.sqlite)"
|
|
257
|
+
echo " - Data stores (grillo-data, .noah-data, etc.)"
|
|
258
|
+
echo " - Session transcripts"
|
|
259
|
+
echo " - Fleet-bus workspace"
|
|
260
|
+
echo ""
|
|
261
|
+
warn "For a complete restore, use the latest FULL backup first,"
|
|
262
|
+
warn "then overlay a LIGHT backup for the most recent .md state."
|
|
263
|
+
else
|
|
264
|
+
warn "This will overwrite the following on this system:"
|
|
265
|
+
echo " - $OPENCLAW_HOME/extensions/*/dist/"
|
|
266
|
+
echo " - $OPENCLAW_HOME/extensions/*/agent/"
|
|
267
|
+
echo " - $OPENCLAW_HOME/agents/"
|
|
268
|
+
echo " - Agent data stores (grillo-data, .noah-data, etc.)"
|
|
269
|
+
echo " - Fleet-bus workspace"
|
|
270
|
+
echo " - Mighty Mark watchdog scripts"
|
|
271
|
+
echo " - Systemd/cron configs"
|
|
272
|
+
fi
|
|
273
|
+
echo ""
|
|
274
|
+
warn "Secrets (.env, credentials, API keys) are NOT in the"
|
|
275
|
+
warn "backup and will NOT be affected."
|
|
276
|
+
echo ""
|
|
277
|
+
|
|
278
|
+
read -p "Stop openclaw-gateway before restore? (Y/n): " stop_gw
|
|
279
|
+
if [[ ! "$stop_gw" =~ ^[Nn]$ ]]; then
|
|
280
|
+
log "Stopping openclaw-gateway..."
|
|
281
|
+
systemctl stop openclaw-gateway 2>/dev/null || warn "Could not stop gateway (may not be running)"
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
verify_backup "$id" || {
|
|
285
|
+
err "Checksum verification failed. Aborting restore."
|
|
286
|
+
exit 1
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
local extract_dir="/tmp/fleet-restore-${name}"
|
|
290
|
+
rm -rf "$extract_dir"
|
|
291
|
+
mkdir -p "$extract_dir"
|
|
292
|
+
tar -xzf "$archive" -C "$extract_dir"
|
|
293
|
+
local src="$extract_dir/fleet-backup"
|
|
294
|
+
|
|
295
|
+
read -p "Proceed with restore? Type YES to confirm: " confirm
|
|
296
|
+
if [[ "$confirm" != "YES" ]]; then
|
|
297
|
+
err "Restore cancelled."
|
|
298
|
+
rm -rf "$extract_dir"
|
|
299
|
+
exit 1
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
# Restore extensions
|
|
303
|
+
if [[ -d "$src/extensions" ]]; then
|
|
304
|
+
log "Restoring extensions..."
|
|
305
|
+
for agent_dir in "$src/extensions"/*/; do
|
|
306
|
+
agent_name=$(basename "$agent_dir")
|
|
307
|
+
target="$OPENCLAW_HOME/extensions/$agent_name"
|
|
308
|
+
if [[ -d "$target" ]]; then
|
|
309
|
+
[[ -d "$agent_dir/dist" ]] && cp -r "$agent_dir/dist"/* "$target/dist/" 2>/dev/null || true
|
|
310
|
+
[[ -d "$agent_dir/agent" ]] && cp -r "$agent_dir/agent"/* "$target/agent/" 2>/dev/null || true
|
|
311
|
+
[[ -f "$agent_dir/package.json" ]] && cp "$agent_dir/package.json" "$target/"
|
|
312
|
+
log " Restored: $agent_name"
|
|
313
|
+
else
|
|
314
|
+
warn " Skipped $agent_name (target dir does not exist)"
|
|
315
|
+
fi
|
|
316
|
+
done
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
# Restore agent workspaces
|
|
320
|
+
if [[ -d "$src/agents" ]]; then
|
|
321
|
+
log "Restoring agent workspaces..."
|
|
322
|
+
cp -r "$src/agents"/* "$OPENCLAW_HOME/agents/" 2>/dev/null || true
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
# Full-only restores
|
|
326
|
+
if [[ "$tier" == "FULL" ]] || [[ "$tier" == "unknown" ]]; then
|
|
327
|
+
|
|
328
|
+
# Restore vector indexes
|
|
329
|
+
if [[ -d "$src/memory" ]]; then
|
|
330
|
+
log "Restoring vector indexes..."
|
|
331
|
+
mkdir -p "$OPENCLAW_HOME/memory"
|
|
332
|
+
cp "$src/memory"/*.sqlite "$OPENCLAW_HOME/memory/" 2>/dev/null || true
|
|
333
|
+
fi
|
|
334
|
+
|
|
335
|
+
# Restore data stores
|
|
336
|
+
for data_dir in grillo-data .noah-data nole-data .mark-data .fleet-data .sam-artifacts; do
|
|
337
|
+
if [[ -d "$src/$data_dir" ]]; then
|
|
338
|
+
log "Restoring data store: $data_dir"
|
|
339
|
+
mkdir -p "$OPENCLAW_HOME/$data_dir"
|
|
340
|
+
cp -r "$src/$data_dir"/* "$OPENCLAW_HOME/$data_dir/" 2>/dev/null || true
|
|
341
|
+
fi
|
|
342
|
+
done
|
|
343
|
+
|
|
344
|
+
# Restore workspace
|
|
345
|
+
if [[ -d "$src/workspace" ]]; then
|
|
346
|
+
log "Restoring workspace (fleet-bus, skills)..."
|
|
347
|
+
mkdir -p "$OPENCLAW_HOME/workspace"
|
|
348
|
+
cp -r "$src/workspace"/* "$OPENCLAW_HOME/workspace/" 2>/dev/null || true
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
# Restore watchdog
|
|
352
|
+
if [[ -d "$src/mighty-mark-watchdog" ]]; then
|
|
353
|
+
log "Restoring Mighty Mark watchdog scripts..."
|
|
354
|
+
cp -r "$src/mighty-mark-watchdog"/* "$MIGHTY_MARK_OPT/" 2>/dev/null || true
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
# Restore system configs
|
|
360
|
+
if [[ -d "$src/system" ]]; then
|
|
361
|
+
log "Restoring system configs..."
|
|
362
|
+
for f in "$src/system"/*; do
|
|
363
|
+
fname=$(basename "$f")
|
|
364
|
+
case "$fname" in
|
|
365
|
+
*.service) cp "$f" /etc/systemd/system/ && log " Restored: $fname" ;;
|
|
366
|
+
mighty-mark|fleet-backup) cp "$f" /etc/cron.d/ && log " Restored cron: $fname" ;;
|
|
367
|
+
*) log " Skipped system file: $fname" ;;
|
|
368
|
+
esac
|
|
369
|
+
done
|
|
370
|
+
systemctl daemon-reload 2>/dev/null || true
|
|
371
|
+
fi
|
|
372
|
+
|
|
373
|
+
rm -rf "$extract_dir"
|
|
374
|
+
|
|
375
|
+
echo ""
|
|
376
|
+
log "Restore complete from backup: $name [$tier]"
|
|
377
|
+
echo ""
|
|
378
|
+
warn "IMPORTANT — Manual steps remaining:"
|
|
379
|
+
echo " 1. Verify openclaw.json has correct API keys (not in backup)"
|
|
380
|
+
echo " 2. Verify /opt/mighty-mark/.env has Telegram credentials"
|
|
381
|
+
echo " 3. Verify ~/.nole/credentials if Nole wallet is configured"
|
|
382
|
+
echo " 4. Re-install fleet-bus for each extension (npm pack pattern)"
|
|
383
|
+
echo " 5. Restart gateway: systemctl start openclaw-gateway"
|
|
384
|
+
echo " 6. Verify logs: journalctl -u openclaw-gateway --no-pager -n 50"
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# ─── CLI ──────────────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
case "${1:---list}" in
|
|
390
|
+
--list|-l)
|
|
391
|
+
list_backups
|
|
392
|
+
;;
|
|
393
|
+
--inspect|-i)
|
|
394
|
+
[[ -z "${2:-}" ]] && { err "Usage: $0 --inspect ARCHIVE-NAME"; exit 1; }
|
|
395
|
+
inspect_backup "$2"
|
|
396
|
+
;;
|
|
397
|
+
--verify|-v)
|
|
398
|
+
[[ -z "${2:-}" ]] && { err "Usage: $0 --verify ARCHIVE-NAME"; exit 1; }
|
|
399
|
+
verify_backup "$2"
|
|
400
|
+
;;
|
|
401
|
+
--extract|-e)
|
|
402
|
+
[[ -z "${2:-}" ]] && { err "Usage: $0 --extract ARCHIVE-NAME"; exit 1; }
|
|
403
|
+
extract_backup "$2"
|
|
404
|
+
;;
|
|
405
|
+
--restore|-r)
|
|
406
|
+
[[ -z "${2:-}" ]] && { err "Usage: $0 --restore ARCHIVE-NAME"; exit 1; }
|
|
407
|
+
restore_backup "$2"
|
|
408
|
+
;;
|
|
409
|
+
--from-github|-g)
|
|
410
|
+
from_github
|
|
411
|
+
;;
|
|
412
|
+
--help|-h)
|
|
413
|
+
echo "Usage: $0 [command] [archive-name]"
|
|
414
|
+
echo ""
|
|
415
|
+
echo "Commands:"
|
|
416
|
+
echo " --list, -l List available backups (default)"
|
|
417
|
+
echo " --inspect, -i ARCHIVE-NAME Show archive contents"
|
|
418
|
+
echo " --verify, -v ARCHIVE-NAME Verify checksum against manifest"
|
|
419
|
+
echo " --extract, -e ARCHIVE-NAME Extract to /tmp for review"
|
|
420
|
+
echo " --restore, -r ARCHIVE-NAME Restore (interactive, with confirmation)"
|
|
421
|
+
echo " --from-github, -g Download latest backup from GitHub"
|
|
422
|
+
echo " --help, -h Show this help"
|
|
423
|
+
echo ""
|
|
424
|
+
echo "Archive name: e.g. fleet-full-2026-03-02-090000"
|
|
425
|
+
echo " (omit .tar.gz extension)"
|
|
426
|
+
echo ""
|
|
427
|
+
echo "Recommended disaster recovery:"
|
|
428
|
+
echo " 1. $0 --from-github # download from GitHub"
|
|
429
|
+
echo " 2. $0 --restore fleet-full-YYYY-MM-DD-HHMMSS # restore latest full"
|
|
430
|
+
echo " 3. $0 --restore fleet-light-YYYY-MM-DD-HHMMSS # overlay latest light"
|
|
431
|
+
;;
|
|
432
|
+
*)
|
|
433
|
+
err "Unknown command: $1"
|
|
434
|
+
err "Run $0 --help for usage"
|
|
435
|
+
exit 1
|
|
436
|
+
;;
|
|
437
|
+
esac
|