#!/usr/bin/env bash # # deploy/dalidou/auto-triage-watcher.sh # -------------------------------------- # Host-side watcher for on-demand auto-triage requests from the web UI. # # The web UI at /admin/triage has an "Auto-process queue" button that # POSTs to /admin/triage/request-drain, which writes a timestamp to # AtoCore project state (atocore/config/auto_triage_requested_at). # # This script runs on the Dalidou HOST (where the claude CLI is # available), polls for the flag, and runs auto_triage.py when seen. # # Installed via cron to run every 2 minutes: # */2 * * * * /srv/storage/atocore/app/deploy/dalidou/auto-triage-watcher.sh # # Safety: # - Lock file prevents concurrent runs # - Flag is cleared after processing so one request = one run # - If auto_triage hangs, the lock prevents pileup until manual cleanup set -euo pipefail ATOCORE_URL="${ATOCORE_URL:-http://127.0.0.1:8100}" APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" LOCK_FILE="/tmp/atocore-auto-triage.lock" LOG_DIR="/home/papa/atocore-logs" mkdir -p "$LOG_DIR" TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)" log() { printf '[%s] %s\n' "$TS" "$*"; } # Fetch the request flag via API (read-only, no lock needed) STATE_JSON=$(curl -sSf --max-time 5 "$ATOCORE_URL/project/state/atocore" 2>/dev/null || echo "{}") REQUESTED=$(echo "$STATE_JSON" | python3 -c " import sys, json try: d = json.load(sys.stdin) for e in d.get('entries', d.get('state', [])): if e.get('category') == 'config' and e.get('key') == 'auto_triage_requested_at': print(e.get('value', '')) break except Exception: pass " 2>/dev/null || echo "") if [[ -z "$REQUESTED" ]]; then # No request — silent exit exit 0 fi # Acquire lock (non-blocking) exec 9>"$LOCK_FILE" || exit 0 if ! flock -n 9; then log "auto-triage already running, skipping" exit 0 fi # Record we're starting curl -sSf -X POST "$ATOCORE_URL/project/state" \ -H 'Content-Type: application/json' \ -d "{\"project\":\"atocore\",\"category\":\"status\",\"key\":\"auto_triage_running\",\"value\":\"1\",\"source\":\"host watcher\"}" \ >/dev/null 2>&1 || true curl -sSf -X POST "$ATOCORE_URL/project/state" \ -H 'Content-Type: application/json' \ -d "{\"project\":\"atocore\",\"category\":\"status\",\"key\":\"auto_triage_last_started_at\",\"value\":\"$TS\",\"source\":\"host watcher\"}" \ >/dev/null 2>&1 || true LOG_FILE="$LOG_DIR/auto-triage-ondemand-$(date -u +%Y%m%d-%H%M%S).log" log "Starting auto-triage (request: $REQUESTED, log: $LOG_FILE)" # Clear the request flag FIRST so duplicate clicks queue at most one re-run # (the next watcher tick would then see a fresh request, not this one) curl -sSf -X DELETE "$ATOCORE_URL/project/state" \ -H 'Content-Type: application/json' \ -d "{\"project\":\"atocore\",\"category\":\"config\",\"key\":\"auto_triage_requested_at\"}" \ >/dev/null 2>&1 || true # Run the drain cd "$APP_DIR" export PYTHONPATH="$APP_DIR/src:${PYTHONPATH:-}" if python3 scripts/auto_triage.py --base-url "$ATOCORE_URL" >> "$LOG_FILE" 2>&1; then RESULT_LINE=$(tail -5 "$LOG_FILE" | grep "total:" | tail -1 || tail -1 "$LOG_FILE") RESULT="${RESULT_LINE:-completed}" log "auto-triage finished: $RESULT" else RESULT="ERROR — see $LOG_FILE" log "auto-triage FAILED — see $LOG_FILE" fi FINISH_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)" # Mark done curl -sSf -X POST "$ATOCORE_URL/project/state" \ -H 'Content-Type: application/json' \ -d "{\"project\":\"atocore\",\"category\":\"status\",\"key\":\"auto_triage_running\",\"value\":\"0\",\"source\":\"host watcher\"}" \ >/dev/null 2>&1 || true curl -sSf -X POST "$ATOCORE_URL/project/state" \ -H 'Content-Type: application/json' \ -d "{\"project\":\"atocore\",\"category\":\"status\",\"key\":\"auto_triage_last_finished_at\",\"value\":\"$FINISH_TS\",\"source\":\"host watcher\"}" \ >/dev/null 2>&1 || true # Escape quotes in result for JSON SAFE_RESULT=$(printf '%s' "$RESULT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read())[1:-1])") curl -sSf -X POST "$ATOCORE_URL/project/state" \ -H 'Content-Type: application/json' \ -d "{\"project\":\"atocore\",\"category\":\"status\",\"key\":\"auto_triage_last_result\",\"value\":\"$SAFE_RESULT\",\"source\":\"host watcher\"}" \ >/dev/null 2>&1 || true