#!/usr/bin/env bash # # Scheduled dependency update + vulnerability scan (every ~20 days). # # Driven by the systemd user timer `skatespots-deps.timer` (a daily tick; this # script self-gates to one real run per INTERVAL_DAYS). What it does: # 1. bails if the working tree is dirty (never mixes with your own changes); # 2. scans `npm outdated` + `npm audit` into a dated report; # 3. applies NON-breaking updates (`npm update` + `npm audit fix` — never --force); # 4. runs the full test suite (`npm test`) AND the boot smoke test; # 5. on success commits + pushes the lockfile/package.json (the deploy timer then # ships it); on failure it RESTORES the previous lockfile and reports. # # Flags: # --scan-only report only (no apply, no commit) # --no-push apply + test + commit locally, but don't push # --force ignore the interval gate # # Env: DEPS_INTERVAL_DAYS (default 20), DEPS_STATE_DIR (default ~/.local/state/skatespots-deps) set -uo pipefail REPO="/home/sovereignsk8/SkateSpots" export PATH="/home/sovereignsk8/.nvm/versions/node/v22.23.1/bin:$PATH" cd "$REPO" INTERVAL_DAYS="${DEPS_INTERVAL_DAYS:-20}" STATE_DIR="${DEPS_STATE_DIR:-$HOME/.local/state/skatespots-deps}" mkdir -p "$STATE_DIR" LAST="$STATE_DIR/last-run" REPORT="$STATE_DIR/report-$(LC_ALL=C date +%F).txt" SCAN_ONLY=0; NO_PUSH=0; FORCE=0 for a in "$@"; do case "$a" in --scan-only) SCAN_ONLY=1 ;; --no-push) NO_PUSH=1 ;; --force) FORCE=1 ;; esac done log() { echo "[$(date -Is)] $*"; } notify() { command -v notify-send >/dev/null 2>&1 && notify-send -a SkateSpots "Dependency job" "$1" 2>/dev/null || true } finish_timer() { date +%s > "$LAST"; } # ── interval gate ─────────────────────────────────────────────────────────── if [ "$FORCE" -eq 0 ] && [ "$SCAN_ONLY" -eq 0 ] && [ -f "$LAST" ]; then last=$(cat "$LAST" 2>/dev/null || echo 0) now=$(date +%s) elapsed=$(( now - last )) if [ "$elapsed" -lt $(( INTERVAL_DAYS * 86400 )) ]; then log "not due yet ($(( (INTERVAL_DAYS * 86400 - elapsed) / 86400 ))d left)" exit 0 fi fi # ── scan ──────────────────────────────────────────────────────────────────── { echo "=== SkateSpots dependency scan — $(date -Is) ===" echo echo "=== npm outdated ===" npm outdated 2>&1 || true echo echo "=== npm audit ===" npm audit 2>&1 || true } | tee "$REPORT" if [ "$SCAN_ONLY" -eq 1 ]; then log "scan-only; report: $REPORT" exit 0 fi # ── never mix with the user's uncommitted work ────────────────────────────── if [ -n "$(git status --porcelain)" ]; then log "working tree is dirty — skipping (commit or stash first)" exit 0 fi cp package.json "$STATE_DIR/package.json.bak" 2>/dev/null || true cp package-lock.json "$STATE_DIR/package-lock.json.bak" 2>/dev/null || true # ── apply non-breaking updates ────────────────────────────────────────────── log "applying non-breaking updates (npm update + npm audit fix)…" npm update 2>&1 | tail -3 npm audit fix 2>&1 | tail -3 { echo; echo "=== post-update npm audit ==="; npm audit 2>&1 || true } | tee -a "$REPORT" # ── gate: full tests + boot smoke ─────────────────────────────────────────── log "running tests + smoke…" if npm test >"$STATE_DIR/test.log" 2>&1 && node scripts/smoke-test.mjs >"$STATE_DIR/smoke.log" 2>&1; then if [ -n "$(git status --porcelain package.json package-lock.json)" ]; then git add package.json package-lock.json git commit -m "chore(deps): scheduled dependency update + audit ($(LC_ALL=C date +%F))" >/dev/null 2>&1 || true if [ "$NO_PUSH" -eq 0 ]; then # Bound the push: the git remote signs via the remote signer and can stall. if timeout 180 git push origin main >"$STATE_DIR/push.log" 2>&1; then log "pushed dependency update" notify "Dependency update applied, tests pass, pushed." else log "push failed/skipped (see $STATE_DIR/push.log)" notify "Dependency update committed locally but the push failed." fi else log "committed locally (--no-push)" notify "Dependency update applied and committed locally." fi else log "no dependency changes to commit" notify "Dependency scan complete: nothing to update." fi finish_timer log "done" else log "tests FAILED — restoring the previous lockfile" cp "$STATE_DIR/package.json.bak" package.json 2>/dev/null || true cp "$STATE_DIR/package-lock.json.bak" package-lock.json 2>/dev/null || true npm i --silent >/dev/null 2>&1 || true notify "Dependency update REVERTED — tests failed (see $STATE_DIR/test.log)." finish_timer exit 1 fi