Files

86 lines
3.0 KiB
Bash
Executable File

#!/usr/bin/env sh
# connect-watchdog.sh — keep a `dws dev connect` connector alive using the
# health contract from `dws dev connect status --json`.
#
# This is the "脚本" side of the 0701 review: a small, inspectable local watchdog
# that (1) asks dws whether the connection is actually healthy — not just whether
# a process exists — and (2) relaunches it when it is down/degraded. Drop it into
# cron or launchd; it is idempotent, so running it every few minutes is safe.
#
# It consumes the machine-readable contract, so it never parses `ps` or guesses.
#
# Usage:
# connect-watchdog.sh --client-id <clientId> [--dry-run] -- <launch command...>
#
# Example (relaunch a daemon connector if it is not healthy):
# connect-watchdog.sh --client-id ding123 -- \
# dws dev connect --robot-client-id ding123 --channel opencode --daemon
#
# cron (every 5 minutes):
# */5 * * * * /path/to/connect-watchdog.sh --client-id ding123 -- \
# dws dev connect --robot-client-id ding123 --channel opencode --daemon >> ~/.dws/connect/ding123/watchdog.log 2>&1
#
# Exit codes: 0 = healthy (no action) or relaunch issued; 1 = usage error.
set -eu
DWS="${DWS_BIN:-dws}"
CLIENT_ID=""
DRY_RUN=0
while [ $# -gt 0 ]; do
case "$1" in
--client-id) CLIENT_ID="$2"; shift 2 ;;
--dry-run) DRY_RUN=1; shift ;;
--) shift; break ;;
*) echo "unknown arg: $1" >&2; exit 1 ;;
esac
done
if [ -z "$CLIENT_ID" ]; then
echo "usage: connect-watchdog.sh --client-id <clientId> [--dry-run] -- <launch command...>" >&2
exit 1
fi
if [ $# -eq 0 ]; then
echo "error: missing launch command after --" >&2
exit 1
fi
ts() { date '+%Y-%m-%d %H:%M:%S'; }
# Ask dws for the health verdict. `--json` is the stable contract.
STATUS_JSON="$("$DWS" dev connect status --robot-client-id "$CLIENT_ID" --json 2>/dev/null || true)"
# Extract "state" without a hard jq dependency (fall back to jq when present).
if command -v jq >/dev/null 2>&1; then
STATE="$(printf '%s' "$STATUS_JSON" | jq -r '.state // empty' 2>/dev/null)"
else
STATE="$(printf '%s' "$STATUS_JSON" | sed -n 's/.*"state"[[:space:]]*:[[:space:]]*"\([a-z_]*\)".*/\1/p' | head -n1)"
fi
# Treat empty, "null", or any non-word value as unknown so the watchdog relaunches.
case "$STATE" in ""|null) STATE="unknown" ;; esac
case "$STATE" in
healthy)
echo "$(ts) [watchdog] $CLIENT_ID healthy — no action"
exit 0
;;
not_running|down|degraded|unknown)
echo "$(ts) [watchdog] $CLIENT_ID state=$STATE — relaunching: $*"
if [ "$DRY_RUN" -eq 1 ]; then
echo "$(ts) [watchdog] dry-run, not executing"
exit 0
fi
# For down/degraded, stop the old connector first so we do not fight the
# single-instance lock; not_running has nothing to stop (ignore failures).
if [ "$STATE" = "down" ] || [ "$STATE" = "degraded" ]; then
"$DWS" dev connect stop --robot-client-id "$CLIENT_ID" >/dev/null 2>&1 || true
fi
exec "$@"
;;
*)
echo "$(ts) [watchdog] $CLIENT_ID unexpected state=$STATE — no action" >&2
exit 0
;;
esac