233 lines
8.2 KiB
Bash
Executable File
233 lines
8.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# Daily auto-update: OpenClaw, Claude Code, OS packages
|
|
# Runs at 9:00 AM ET via systemd timer
|
|
# Logs results to memory/updates/ for morning briefing
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
WORKSPACE="$(dirname "$SCRIPT_DIR")"
|
|
LOG_DIR="$WORKSPACE/memory/updates"
|
|
DATE=$(date +%Y-%m-%d)
|
|
LOG="$LOG_DIR/$DATE.json"
|
|
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
# Initialize log
|
|
cat > "$LOG" <<'EOF'
|
|
{
|
|
"date": "DATE_PLACEHOLDER",
|
|
"timestamp": "TS_PLACEHOLDER",
|
|
"openclaw": {},
|
|
"claude_code": {},
|
|
"os": {},
|
|
"mission_control": {},
|
|
"caddy_pi": {},
|
|
"gateway_restarted": false
|
|
}
|
|
EOF
|
|
sed -i "s/DATE_PLACEHOLDER/$DATE/" "$LOG"
|
|
sed -i "s/TS_PLACEHOLDER/$(date -Iseconds)/" "$LOG"
|
|
|
|
update_json() {
|
|
local key="$1" value="$2"
|
|
python3 << PYEOF
|
|
import json
|
|
with open("$LOG") as f: d = json.load(f)
|
|
keys = "$key".split(".")
|
|
obj = d
|
|
for k in keys[:-1]: obj = obj[k]
|
|
raw = '''$value'''
|
|
# Try parsing as JSON first (handles strings, arrays, numbers, booleans)
|
|
try:
|
|
obj[keys[-1]] = json.loads(raw)
|
|
except (json.JSONDecodeError, ValueError):
|
|
obj[keys[-1]] = raw
|
|
with open("$LOG", "w") as f: json.dump(d, f, indent=2)
|
|
PYEOF
|
|
}
|
|
|
|
echo "=== Daily Update Check: $DATE ==="
|
|
|
|
# --- OpenClaw ---
|
|
echo ""
|
|
echo "--- OpenClaw ---"
|
|
OC_BEFORE=$(openclaw --version 2>/dev/null | head -1 || echo "unknown")
|
|
OC_LATEST=$(npm show openclaw version 2>/dev/null || echo "unknown")
|
|
echo "Current: $OC_BEFORE | Latest: $OC_LATEST"
|
|
update_json "openclaw.before" "\"$OC_BEFORE\""
|
|
update_json "openclaw.latest" "\"$OC_LATEST\""
|
|
|
|
# Use LLM to decide if these refer to the same version — no fragile string parsing
|
|
OC_SAME=$(claude --print --permission-mode bypassPermissions "Do these two strings refer to the same software version? Answer only 'yes' or 'no'. String 1: '$OC_BEFORE' String 2: '$OC_LATEST'" 2>/dev/null | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
|
|
|
|
if [ "$OC_SAME" = "yes" ]; then
|
|
update_json "openclaw.updated" "false"
|
|
echo "Up to date: $OC_BEFORE"
|
|
else
|
|
echo "Update available, running npm update -g openclaw..."
|
|
if npm update -g openclaw 2>&1; then
|
|
OC_AFTER=$(openclaw --version 2>/dev/null | head -1 || echo "unknown")
|
|
update_json "openclaw.after" "\"$OC_AFTER\""
|
|
update_json "openclaw.updated" "true"
|
|
echo "Updated: $OC_BEFORE → $OC_AFTER"
|
|
/home/johan/clawd/scripts/openclaw-post-update-patches.sh 2>&1
|
|
else
|
|
update_json "openclaw.updated" "false"
|
|
update_json "openclaw.error" "\"npm update failed\""
|
|
echo "Update failed"
|
|
fi
|
|
fi
|
|
|
|
# --- Claude Code ---
|
|
echo ""
|
|
echo "--- Claude Code ---"
|
|
CC_BEFORE=$(claude --version 2>/dev/null | sed 's/ (Claude Code)//' || echo "unknown")
|
|
CC_LATEST=$(npm show @anthropic-ai/claude-code version 2>/dev/null || echo "unknown")
|
|
echo "Current: $CC_BEFORE | Latest: $CC_LATEST"
|
|
update_json "claude_code.before" "\"$CC_BEFORE\""
|
|
update_json "claude_code.latest" "\"$CC_LATEST\""
|
|
|
|
CC_SAME=$(claude --print --permission-mode bypassPermissions "Do these two strings refer to the same software version? Answer only 'yes' or 'no'. String 1: '$CC_BEFORE' String 2: '$CC_LATEST'" 2>/dev/null | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
|
|
|
|
if [ "$CC_SAME" = "yes" ]; then
|
|
update_json "claude_code.updated" "false"
|
|
echo "Up to date: $CC_BEFORE"
|
|
else
|
|
echo "Update available, running npm update -g @anthropic-ai/claude-code..."
|
|
if npm update -g @anthropic-ai/claude-code 2>&1; then
|
|
CC_AFTER=$(claude --version 2>/dev/null | sed 's/ (Claude Code)//' || echo "unknown")
|
|
update_json "claude_code.after" "\"$CC_AFTER\""
|
|
update_json "claude_code.updated" "true"
|
|
echo "Updated: $CC_BEFORE → $CC_AFTER"
|
|
else
|
|
update_json "claude_code.updated" "false"
|
|
update_json "claude_code.error" "\"npm update failed\""
|
|
echo "Update failed"
|
|
fi
|
|
fi
|
|
|
|
# --- OS Packages ---
|
|
echo ""
|
|
echo "--- OS Packages ---"
|
|
# Capture upgradable list before updating
|
|
sudo apt-get update -qq 2>/dev/null
|
|
|
|
UPGRADABLE=$(apt list --upgradable 2>/dev/null | grep -v "^Listing" || true)
|
|
PKG_COUNT=$(echo "$UPGRADABLE" | grep -c . || echo "0")
|
|
|
|
update_json "os.available" "$PKG_COUNT"
|
|
|
|
if [ "$PKG_COUNT" -gt 0 ]; then
|
|
echo "$PKG_COUNT packages upgradable"
|
|
# Capture package names and versions
|
|
PKG_LIST=$(echo "$UPGRADABLE" | head -50 | python3 -c "
|
|
import sys, json
|
|
pkgs = []
|
|
for line in sys.stdin:
|
|
line = line.strip()
|
|
if not line: continue
|
|
parts = line.split('/')
|
|
if len(parts) >= 2:
|
|
name = parts[0]
|
|
rest = '/'.join(parts[1:])
|
|
# Extract version info
|
|
ver_parts = rest.split(' ')
|
|
new_ver = ver_parts[1] if len(ver_parts) > 1 else 'unknown'
|
|
old_ver = ver_parts[-1].strip('[]') if '[' in rest else 'unknown'
|
|
pkgs.append({'name': name, 'from': old_ver, 'to': new_ver})
|
|
print(json.dumps(pkgs))
|
|
" 2>/dev/null || echo "[]")
|
|
update_json "os.packages" "$PKG_LIST"
|
|
|
|
echo "Upgrading..."
|
|
if sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq 2>&1 | tail -5; then
|
|
update_json "os.updated" "true"
|
|
echo "OS packages updated"
|
|
|
|
# Check if reboot required
|
|
if [ -f /var/run/reboot-required ]; then
|
|
update_json "os.reboot_required" "true"
|
|
echo "⚠️ Reboot required!"
|
|
else
|
|
update_json "os.reboot_required" "false"
|
|
fi
|
|
else
|
|
update_json "os.updated" "false"
|
|
update_json "os.error" "\"apt upgrade failed\""
|
|
fi
|
|
else
|
|
update_json "os.updated" "false"
|
|
update_json "os.packages" "[]"
|
|
echo "All packages up to date"
|
|
fi
|
|
|
|
# --- Mission Control ---
|
|
echo ""
|
|
echo "--- Mission Control ---"
|
|
MC_DIR="/home/johan/mission-control"
|
|
MC_BEFORE=$(cd "$MC_DIR" && git describe --tags --abbrev=0 2>/dev/null || git rev-parse --short HEAD)
|
|
MC_LATEST=$(cd "$MC_DIR" && git fetch --tags -q origin 2>/dev/null && git describe --tags --abbrev=0 origin/main 2>/dev/null || echo "unknown")
|
|
echo "Current: $MC_BEFORE | Latest: $MC_LATEST"
|
|
update_json "mission_control.before" "\"$MC_BEFORE\""
|
|
update_json "mission_control.latest" "\"$MC_LATEST\""
|
|
|
|
if [ "$MC_BEFORE" = "$MC_LATEST" ]; then
|
|
update_json "mission_control.updated" "false"
|
|
echo "Up to date: $MC_BEFORE"
|
|
else
|
|
echo "Update available ($MC_BEFORE → $MC_LATEST), pulling and rebuilding..."
|
|
if cd "$MC_DIR" && git pull --ff-only origin main 2>&1 && npm install --legacy-peer-deps 2>&1 | tail -3 && npm run build 2>&1 | tail -5; then
|
|
MC_AFTER=$(cd "$MC_DIR" && git describe --tags --abbrev=0 2>/dev/null || git rev-parse --short HEAD)
|
|
update_json "mission_control.after" "\"$MC_AFTER\""
|
|
update_json "mission_control.updated" "true"
|
|
systemctl --user restart mission-control
|
|
echo "Updated: $MC_BEFORE → $MC_AFTER, service restarted"
|
|
else
|
|
update_json "mission_control.updated" "false"
|
|
update_json "mission_control.error" "\"pull or build failed\""
|
|
echo "Update failed — MC unchanged"
|
|
fi
|
|
fi
|
|
|
|
# --- Gateway Restart (only if OpenClaw updated) ---
|
|
echo ""
|
|
OC_UPDATED=$(python3 -c "import json; print(json.load(open('$LOG'))['openclaw'].get('updated', False))")
|
|
if [ "$OC_UPDATED" = "True" ]; then
|
|
echo "OpenClaw was updated — restarting gateway..."
|
|
systemctl --user restart openclaw-gateway
|
|
update_json "gateway_restarted" "true"
|
|
echo "Gateway restarted"
|
|
else
|
|
echo "No gateway restart needed"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Update complete. Log: $LOG ==="
|
|
|
|
# --- Caddy Pi (192.168.0.2) ---
|
|
echo ""
|
|
echo "--- Caddy Pi ---"
|
|
CADDY_RESULT=$(ssh -o ConnectTimeout=10 -o BatchMode=yes root@192.168.0.2 "
|
|
apt-get update -qq 2>/dev/null
|
|
UPGRADABLE=\$(apt list --upgradable 2>/dev/null | grep -v Listing | wc -l)
|
|
if [ \"\$UPGRADABLE\" -gt 0 ]; then
|
|
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq 2>&1 | tail -3
|
|
echo \"upgraded:\$UPGRADABLE\"
|
|
else
|
|
echo \"upgraded:0\"
|
|
fi
|
|
# Commit and push any Caddyfile changes
|
|
cd /etc/caddy
|
|
if ! git diff --quiet HEAD Caddyfile 2>/dev/null; then
|
|
git add Caddyfile && git commit -m \"auto: Caddyfile update \$(date +%Y-%m-%d)\" && git push
|
|
echo 'caddyfile:committed'
|
|
else
|
|
echo 'caddyfile:unchanged'
|
|
fi
|
|
# Reboot if needed
|
|
[ -f /var/run/reboot-required ] && echo 'reboot:required' || echo 'reboot:no'
|
|
" 2>/dev/null || echo "ssh:failed")
|
|
echo "Caddy Pi: $CADDY_RESULT"
|
|
update_json "caddy_pi.result" "\"$CADDY_RESULT\""
|