|
| 1 | +#!/usr/bin/env bash |
| 2 | +# coinpay-vault-watch --check: read-only capability and signing-format report. |
| 3 | +# Remind Anthony before CoinPay's operator vault locks, and tell him at once when it is locked |
| 4 | +# (only applicable when the deployed runtime supports the vault). |
| 5 | +# Capability and pending signing formats come from a read-only runtime probe. |
| 6 | +# For vault-capable runtimes, status comes from the CURRENT container log: |
| 7 | +# "[operator-keys] unlocked until <ISO>" / "operator keys locked". |
| 8 | +# Cron: */15 * * * * coinpay-vault-watch (stamps in ~/.local/state/coinpay-vault-watch) |
| 9 | +set -uo pipefail |
| 10 | +export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:/usr/local/bin:/usr/bin:/bin" |
| 11 | +C=coinpayportalcom-app-1; S="$HOME/.local/state/coinpay-vault-watch"; mkdir -p "$S" |
| 12 | +URL="https://coinpayportal.com/admin/operator-vault" |
| 13 | +TO=$(grep -h '^ALERT_EMAIL=' "$HOME/.config/alert/env" 2>/dev/null | cut -d= -f2- | tr -d '"') |
| 14 | +log(){ printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >> "$S/watch.log"; } |
| 15 | +notify(){ # <subject> <body> — one critical alert; smshub chooses the delivery channel. |
| 16 | + if [ -n "$TO" ]; then |
| 17 | + env -u RESEND_API_KEY mail send --to "$TO" --subject "$1" --via resend --body "$2" >/dev/null 2>&1 || log "email failed: $1" |
| 18 | + fi |
| 19 | + alert --severity critical --key coinpay-vault-call --every 6h "$1" >/dev/null 2>&1 || log "critical alert failed" |
| 20 | + log "notified: $1" |
| 21 | +} |
| 22 | +notify_issue(){ |
| 23 | + local now last |
| 24 | + now=$(date -u +%s); last=$(cat "$S/locked" 2>/dev/null || echo 0) |
| 25 | + if [ $((now - last)) -ge 21600 ]; then |
| 26 | + echo "$now" > "$S/locked" |
| 27 | + notify "$1" "$2" |
| 28 | + fi |
| 29 | +} |
| 30 | +# Ask the CURRENT runtime and database, rather than interpreting missing logs as a lock. |
| 31 | +tool_root=$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd) |
| 32 | +probe_file="${COINPAY_WATCH_PROBE:-$tool_root/lib/coinpay-vault-watch/probe.mjs}" |
| 33 | +probe=$(docker exec -i -w /app "$C" node --input-type=module < "$probe_file" 2>/dev/null) || probe='{"mode":"probe-error"}' |
| 34 | +if [ "${1:-}" = --check ]; then printf '%s\n' "$probe"; exit 0; fi |
| 35 | +mode=$(printf '%s' "$probe" | python3 -c 'import json,sys;print(json.load(sys.stdin)["mode"])' 2>/dev/null || echo probe-error) |
| 36 | +sealed=$(printf '%s' "$probe" | python3 -c 'import json,sys;print(json.load(sys.stdin).get("sealedSigningRows",0))' 2>/dev/null || echo 0) |
| 37 | +case "$mode" in |
| 38 | + legacy) |
| 39 | + if [ "$sealed" -gt 0 ]; then |
| 40 | + notify_issue "CoinPay signing mismatch: deployed app cannot read $sealed vault-sealed payment keys" "The deployed CoinPay version has no operator-vault workflow, but $sealed payment signing records require that format. Restore a compatible signing version or complete a reviewed key migration. The old /admin/operator-vault link is unavailable on this deployment. No keys or payments were changed by this monitor." |
| 41 | + else |
| 42 | + log "legacy signing: no vault-sealed payment keys requiring forwarding; vault unlock alert not applicable" |
| 43 | + fi |
| 44 | + exit 0 ;; |
| 45 | + operator-vault) ;; |
| 46 | + *) |
| 47 | + notify_issue "CoinPay signing readiness could not be verified" "The current runtime capability probe returned $mode. This does not establish that the vault is locked or that payments have failed. Check the deployment and monitor." |
| 48 | + exit 1 ;; |
| 49 | +esac |
| 50 | +line=$(docker logs "$C" 2>&1 | grep -E '\[operator-keys\] unlocked until|operator keys locked' | tail -1) |
| 51 | +now=$(date -u +%s) |
| 52 | +if [[ "$line" =~ unlocked\ until\ ([0-9TZ:.-]+) ]] && until=$(date -u -d "${BASH_REMATCH[1]}" +%s 2>/dev/null) && [ "$until" -gt "$now" ]; then |
| 53 | + rm -f "$S/locked" |
| 54 | + left=$(( (until - now) / 3600 )); when=$(date -u -d "@$until" '+%a %b %-d %H:%M UTC') |
| 55 | + for h in 48 24 6 1; do |
| 56 | + stamp="$S/remind-$until-$h" |
| 57 | + if [ $((until - now)) -le $((h * 3600)) ] && [ ! -e "$stamp" ]; then |
| 58 | + touch "$stamp" |
| 59 | + notify "CoinPay vault locks in ${left}h (${when})" "CoinPay's operator vault locks at ${when}. When it locks, customer payments stop being forwarded to merchants. |
| 60 | +
|
| 61 | +Unlock it before then: ${URL} (click the eye, type your password, Unlock, 7 days). |
| 62 | +
|
| 63 | +-- riotcoder (coinpay-vault-watch on dev2)" |
| 64 | + break |
| 65 | + fi |
| 66 | + done |
| 67 | + exit 0 |
| 68 | +fi |
| 69 | +# A container under 10 minutes old is still mid-deploy: deploy-app.sh hands the old |
| 70 | +# process's unlock to it once healthy (operator-vault-handoff.sh). Judge it next tick. |
| 71 | +started=$(date -u -d "$(docker inspect -f '{{.State.StartedAt}}' "$C" 2>/dev/null)" +%s 2>/dev/null || echo 0) |
| 72 | +[ $((now - started)) -lt 600 ] && exit 0 |
| 73 | +if [[ ! "$line" =~ operator\ keys\ locked ]] && [[ ! "$line" =~ unlocked\ until ]]; then |
| 74 | + notify_issue "CoinPay vault state is unknown; signing readiness needs verification" "This deployed version supports the operator vault, but its log has no explicit locked or unlocked status. No claim that payments are blocked can be made from missing logs. Check ${URL}." |
| 75 | + exit 0 |
| 76 | +fi |
| 77 | +# Locked (expired, redeployed or restarted). Repeat every 6h until unlocked. |
| 78 | +last=$(cat "$S/locked" 2>/dev/null || echo 0) |
| 79 | +if [ $((now - last)) -ge 21600 ]; then |
| 80 | + echo "$now" > "$S/locked" |
| 81 | + notify "CoinPay vault is LOCKED: payments are not forwarding" "CoinPay's operator vault is locked (it expired, or CoinPay was redeployed/restarted). Customer payments are NOT being forwarded to merchants until you unlock it. |
| 82 | +
|
| 83 | +Unlock now: ${URL} (click the eye, type your password, Unlock, 7 days). |
| 84 | +
|
| 85 | +This repeats every 6 hours until it is unlocked. |
| 86 | +-- riotcoder (coinpay-vault-watch on dev2)" |
| 87 | +fi |
0 commit comments