chore(deps): scheduled dependency update + vulnerability scan every 20 days
scripts/update-deps.sh (systemd user timer skatespots-deps.timer, daily tick, self-gated to 20 days): scans npm outdated + npm audit, applies non-breaking updates only, gates on npm test + smoke, then commits+pushes on green or restores the lockfile on red. Never --force; skips on a dirty tree; bounds the signed push.
This commit is contained in:
parent
bb1cedb68d
commit
4ff5e25fee
5 changed files with 165 additions and 0 deletions
|
|
@ -2075,6 +2075,15 @@ Rules:
|
||||||
and enables them. Logs: `journalctl --user -u skatespots-archive`.
|
and enables them. Logs: `journalctl --user -u skatespots-archive`.
|
||||||
- Commits touching only `scripts/`, `*.md`, `cvm-server/`, `skills/`, `.agents/`
|
- Commits touching only `scripts/`, `*.md`, `cvm-server/`, `skills/`, `.agents/`
|
||||||
are **skipped by the deploy timer** → no Netlify credits spent.
|
are **skipped by the deploy timer** → no Netlify credits spent.
|
||||||
|
- **Dependency updates + vulnerability scan (2026-10-12):** `scripts/update-deps.sh`
|
||||||
|
runs `npm outdated` + `npm audit` (dated report in `~/.local/state/skatespots-deps/`),
|
||||||
|
applies **non-breaking** updates only (`npm update` + `npm audit fix`, never
|
||||||
|
`--force`), then gates on `npm test` + `smoke-test.mjs`: **green → commit + push**
|
||||||
|
(the deploy timer ships it); **red → restores the previous lockfile**. Self-gates
|
||||||
|
to one real run per **20 days** (`DEPS_INTERVAL_DAYS`), skips on a dirty tree, and
|
||||||
|
bounds the push (`timeout 180`) since the git remote signs via the remote signer.
|
||||||
|
Timer: `scripts/install-deps-timer.sh` → `skatespots-deps.timer` (daily tick).
|
||||||
|
Flags: `--scan-only`, `--no-push`, `--force`. Logs: `journalctl --user -u skatespots-deps`.
|
||||||
|
|
||||||
### Community relay — self-hosted Newlay (2026-10-12)
|
### Community relay — self-hosted Newlay (2026-10-12)
|
||||||
|
|
||||||
|
|
|
||||||
16
scripts/install-deps-timer.sh
Executable file
16
scripts/install-deps-timer.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Install the dependency-update + vulnerability-scan systemd user timer.
|
||||||
|
# scripts/install-deps-timer.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SRC="$(cd "$(dirname "$0")/systemd" && pwd)"
|
||||||
|
DEST="$HOME/.config/systemd/user"
|
||||||
|
mkdir -p "$DEST"
|
||||||
|
|
||||||
|
cp "$SRC/skatespots-deps.service" "$DEST/"
|
||||||
|
cp "$SRC/skatespots-deps.timer" "$DEST/"
|
||||||
|
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable --now skatespots-deps.timer
|
||||||
|
systemctl --user list-timers skatespots-deps.timer --no-pager
|
||||||
6
scripts/systemd/skatespots-deps.service
Normal file
6
scripts/systemd/skatespots-deps.service
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[Unit]
|
||||||
|
Description=SkateSpots scheduled dependency update + vulnerability scan
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=%h/SkateSpots/scripts/update-deps.sh
|
||||||
10
scripts/systemd/skatespots-deps.timer
Normal file
10
scripts/systemd/skatespots-deps.timer
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Daily tick for the SkateSpots dependency job (self-gated to ~20 days)
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=daily
|
||||||
|
Persistent=true
|
||||||
|
RandomizedDelaySec=30min
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
124
scripts/update-deps.sh
Executable file
124
scripts/update-deps.sh
Executable file
|
|
@ -0,0 +1,124 @@
|
||||||
|
#!/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
|
||||||
Loading…
Reference in a new issue