SkateSpots/scripts/ops-report.sh
SkateSpots 8473aaa994 chore(ops): plain-English daily report + NIP-17 alerts to both admins
- scripts/ops-report.sh builds a simple daily report (app, website, deps,
  security, weekly scan); --send DMs it to the two admins from src/lib/admins.ts
  via NIP-17 (scripts/send-ops-report.ts), sender = app identity.
- Delivery discovers kind-10050 inboxes + NIP-65 write relays and always
  includes the community relay; OPS_DRY_RUN=1 previews without publishing.
- scripts/send-ops-alert.sh sends short alerts; wired into auto-deploy
  (preflight/smoke), update-deps (full+fallback failure) and ops-health --notify.
- skatespots-ops-report.timer daily 07:00 + installer.
- Signer moved into the 0600 zsp.env; old history copies removed.
2026-10-27 09:36:44 +07:00

214 lines
8.5 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Plain-English SkateSpots ops report.
#
# scripts/ops-report.sh # print it
# scripts/ops-report.sh --send # print it and DM it to both admins (NIP-17)
#
# The daily timer runs `--send` at 07:00. The sender is the app identity in
# ~/.config/skatespots/zsp.env (mode 600); recipients are src/lib/admins.ts.
set -euo pipefail
REPO="/home/sovereignsk8/SkateSpots"
cd "$REPO"
if [ -d "$HOME/.nvm/versions/node" ]; then
NODE_BIN="$(ls -d "$HOME"/.nvm/versions/node/*/bin 2>/dev/null | sort -V | tail -1)"
[ -n "$NODE_BIN" ] && export PATH="$NODE_BIN:$PATH"
fi
export PATH="$HOME/go/bin:$PATH"
SEND=0
[ "${1:-}" = "--send" ] && SEND=1
now="$(date +%s)"
now_human="$(LC_ALL=C date '+%A %-d %B %Y, %H:%M')"
state_deploy="$HOME/.local/state/skatespots-deploy"
state_deps="$HOME/.local/state/skatespots-deps"
deployed_sha="$(cat "$state_deploy/last-deployed-sha" 2>/dev/null || echo unknown)"
origin_sha="$(git rev-parse origin/main 2>/dev/null || echo unknown)"
deploy_epoch="$(cat "$state_deploy/last-deploy-epoch" 2>/dev/null || echo 0)"
skip_file="$state_deploy/last-skip"
deps_last_file="$state_deps/last-run"
deps_fail_file="$state_deps/last-failure"
human_left() {
local s="$1" d h m
[ "$s" -lt 0 ] && s=0
d=$((s / 86400)); h=$(((s % 86400) / 3600)); m=$(((s % 3600) / 60))
if [ "$d" -gt 0 ]; then
printf '%s day%s %s hour%s' "$d" "$([ "$d" -eq 1 ] || echo s)" "$h" "$([ "$h" -eq 1 ] || echo s)"
elif [ "$h" -gt 0 ]; then
printf '%s hour%s %s minute%s' "$h" "$([ "$h" -eq 1 ] || echo s)" "$m" "$([ "$m" -eq 1 ] || echo s)"
else
printf '%s minute%s' "$m" "$([ "$m" -eq 1 ] || echo s)"
fi
}
version="$(node -p "require('./package.json').version")"
problems=()
# ── app ─────────────────────────────────────────────────────────────────────
app_line="version $version"
if command -v nak >/dev/null 2>&1; then
if timeout 8 nak req -k 30063 -d "org.skatespots.app@$version" wss://relay.zapstore.dev 2>/dev/null \
| grep -qE "\"version\"[[:space:]]*,[[:space:]]*\"$version\""; then
app_line="version $version is published on Zapstore"
else
app_line="version $version; the Zapstore release could not be confirmed just now"
fi
else
app_line="version $version"
fi
# ── website ─────────────────────────────────────────────────────────────────
web_line=""
if [ "$deployed_sha" = "$origin_sha" ]; then
web_line="the website is up to date"
else
if [ -f "$skip_file" ]; then
IFS='|' read -r sk_at sk_reason sk_sha sk_detail < "$skip_file"
case "${sk_reason:-}" in
throttled)
# `sk_at` is the last CHECK time (the timer polls every 3 min); the
# 24h window is measured from the last successful DEPLOY.
remain=$((86400 - (now - ${deploy_epoch:-0})))
if [ "$remain" -gt 0 ]; then
web_line="new changes are waiting; it will update automatically in about $(human_left "$remain")"
else
web_line="new changes are waiting; it should update at the next check"
fi ;;
non-site-only|dist-unchanged)
web_line="up to date (the latest changes did not affect the website)" ;;
dirty-tree)
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
web_line="paused because the server has uncommitted changes"
problems+=("The website deploy is paused because the server has uncommitted changes.")
else
remain=$((86400 - (now - ${deploy_epoch:-0})))
if [ "$remain" -gt 0 ]; then
web_line="new changes are waiting; it will update automatically in about $(human_left "$remain")"
else
web_line="new changes are waiting; the next check will deploy them"
fi
fi ;;
preflight-failed)
web_line="paused because the tests failed"
problems+=("The website deploy is paused because the tests failed.") ;;
smoke-failed)
web_line="paused because the built app did not start"
problems+=("The website deploy is paused because the built app did not start.") ;;
diverged)
web_line="paused because the server code is out of sync"
problems+=("The website deploy is paused because the server code is out of sync.") ;;
fetch-failed)
web_line="waiting because the server could not reach the code host"
problems+=("The website deploy could not reach the code host.") ;;
*)
web_line="new changes are waiting (reason: ${sk_reason:-unknown})" ;;
esac
else
web_line="new changes are waiting; the next check will deploy them"
fi
fi
deploy_timer="$(systemctl --user is-active skatespots-deploy.timer 2>/dev/null || echo unknown)"
if [ "$deploy_timer" != "active" ]; then
web_line="$web_line; the website deploy timer is stopped"
problems+=("The website deploy timer is stopped.")
fi
# ── dependencies ────────────────────────────────────────────────────────────
deps_line=""
deps_timer="$(systemctl --user is-active skatespots-deps.timer 2>/dev/null || echo unknown)"
scan_timer="$(systemctl --user is-active skatespots-deps-scan.timer 2>/dev/null || echo unknown)"
if [ "$deps_timer" = "active" ]; then
if [ -f "$deps_last_file" ]; then
deps_last="$(cat "$deps_last_file" 2>/dev/null || echo 0)"
deps_due=$((deps_last + 16 * 86400))
if [ "$deps_due" -gt "$now" ]; then
deps_line="the next automatic dependency check is in about $(human_left $((deps_due - now)))"
else
deps_line="the next automatic dependency check runs at the next daily check (around midnight)"
fi
else
deps_line="the first automatic check will run at the next daily check"
fi
else
deps_line="the dependency timer is stopped"
problems+=("The dependency update timer is stopped.")
fi
if [ -f "$deps_fail_file" ]; then
deps_line="$deps_line. The last update failed and was rolled back; the next retry is in about 3 days"
problems+=("The last dependency update failed and was rolled back.")
fi
if [ "$scan_timer" != "active" ]; then
problems+=("The weekly security scan timer is stopped.")
fi
# ── security ────────────────────────────────────────────────────────────────
security_line="no security scan report is available yet"
latest_report="$(ls -1t "$state_deps"/report-*.txt 2>/dev/null | head -1 || true)"
if [ -n "$latest_report" ]; then
osv_line="$(grep -E '^Total [0-9]+ packages affected' "$latest_report" | tail -1 || true)"
if [[ "$osv_line" =~ Total\ ([0-9]+)\ packages\ affected\ by\ ([0-9]+)\ known\ vulnerabilities\ \(([0-9]+)\ Critical ]]; then
pkgs="${BASH_REMATCH[1]}"; vulns="${BASH_REMATCH[2]}"; crit="${BASH_REMATCH[3]}"
if [ "$crit" -eq 0 ]; then
security_line="the latest security scan found $vulns known issues in $pkgs packages; none are critical. They are in build/development tools and the Excel export, which only writes files"
else
security_line="the latest security scan found $vulns known issues in $pkgs packages, including $crit critical"
problems+=("The security scan found $crit critical issue(s).")
fi
else
security_line="the latest security scan finished; check the server report for details"
fi
fi
scan_line="every Monday at 06:15"
if [ "$scan_timer" != "active" ]; then
scan_line="the timer is stopped"
fi
# ── compose ─────────────────────────────────────────────────────────────────
if [ "${#problems[@]}" -eq 0 ]; then
overall="Overall: all good."
else
overall="Overall: needs attention."
fi
report="$(cat <<EOF
SkateSpots daily report
$now_human
$overall
App: $app_line.
Website: $web_line.
Dependencies: $deps_line.
Security: $security_line.
Weekly scan: $scan_line.
EOF
)"
if [ "${#problems[@]}" -gt 0 ]; then
report="$report
What needs attention:"
for problem in "${problems[@]}"; do
report="$report
- $problem"
done
fi
report="$report
This message was sent automatically to the SkateSpots admins."
printf '%s\n' "$report"
if [ "$SEND" -eq 1 ]; then
printf '%s\n' "$report" | timeout 90 "$REPO/node_modules/.bin/tsx" "$REPO/scripts/send-ops-report.ts"
fi