- NIP-17 wraps now carry the app's ['skatespots','push'] marker on every admin copy (including the sender), so the push service wakes the phone with the app closed. Push test wraps 39fbbd4e…/c88c9f55… verified on the community relay. - The weekly scan-only job DMs only when the OSV summary changes; unchanged scans are logged, not messaged. - auto-deploy alerts on Netlify failure and on a post-deploy production URL health-check failure; deploy-failed/health-failed appear in ops-health and the daily report.
222 lines
8.8 KiB
Bash
Executable file
222 lines
8.8 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=""
|
|
skip_reason=""; sk_at=""; sk_sha=""; sk_detail=""
|
|
if [ -f "$skip_file" ]; then
|
|
IFS='|' read -r sk_at skip_reason sk_sha sk_detail < "$skip_file"
|
|
fi
|
|
|
|
if [ "$skip_reason" = "health-failed" ]; then
|
|
web_line="deployed but did not answer its health check"
|
|
problems+=("The website deployed but did not answer its health check.")
|
|
elif [ "$deployed_sha" = "$origin_sha" ]; then
|
|
web_line="the website is up to date"
|
|
else
|
|
case "${skip_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.") ;;
|
|
deploy-failed)
|
|
web_line="the deploy failed at Netlify"
|
|
problems+=("The website deploy failed at Netlify.") ;;
|
|
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; the next check will deploy them" ;;
|
|
*)
|
|
web_line="new changes are waiting (reason: ${skip_reason})" ;;
|
|
esac
|
|
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 morning"
|
|
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
|