From 4aff2460b88e6eafe6dcd5554886edea35b2ef9d Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 1 Sep 2026 16:15:34 -0400 Subject: [PATCH] Second TV station (actv: laer-klokken chat) + watchdog warm-up grace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit watchdog.sh is now per-station (PROFILE_DIR/display-scoped process checks, AUDIO_CHECK=0 for legitimately-quiet stations) and skips judgment while a unit is under two minutes old — an elapsed OnBootSec fired it mid-boot and it shot actv down before Chrome existed. rig.sh signal traps exit properly instead of swallowing TERM during startup, and the boot tap aims CLICK_X/CLICK_Y for stations whose center is interactive (laklok opens a message dialog). Claude-Session: https://claude.ai/code/session_01EaVptJ3ZhyeQ9ZZZNcp55T --- toolchain/tv/actv-watchdog.service | 11 ++++++++ toolchain/tv/actv-watchdog.timer | 11 ++++++++ toolchain/tv/actv.service | 15 ++++++++++ toolchain/tv/rig.sh | 9 ++++-- toolchain/tv/watchdog.sh | 45 ++++++++++++++++++++---------- 5 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 toolchain/tv/actv-watchdog.service create mode 100644 toolchain/tv/actv-watchdog.timer create mode 100644 toolchain/tv/actv.service diff --git a/toolchain/tv/actv-watchdog.service b/toolchain/tv/actv-watchdog.service new file mode 100644 index 0000000000..fe56315ef7 --- /dev/null +++ b/toolchain/tv/actv-watchdog.service @@ -0,0 +1,11 @@ +# actv-watchdog.service — health check for the AC station (fired by timer). +# AUDIO_CHECK=0: a chat room is legitimately silent; only picture + process +# vitals apply. +[Unit] +Description=Aesthetic Computer TV watchdog check + +[Service] +Type=oneshot +Environment=DISPLAY_NUM=:98 SINK=acsink UNIT=actv AUDIO_CHECK=0 +Environment=PROFILE_DIR=%h/.ac-rig-chrome +ExecStart=/bin/sh %h/actv/watchdog.sh diff --git a/toolchain/tv/actv-watchdog.timer b/toolchain/tv/actv-watchdog.timer new file mode 100644 index 0000000000..35eaa23ba5 --- /dev/null +++ b/toolchain/tv/actv-watchdog.timer @@ -0,0 +1,11 @@ +# actv-watchdog.timer — run the AC station health check every 5 minutes. +[Unit] +Description=Aesthetic Computer TV watchdog schedule + +[Timer] +OnBootSec=5min +OnUnitActiveSec=5min +AccuracySec=30s + +[Install] +WantedBy=timers.target diff --git a/toolchain/tv/actv.service b/toolchain/tv/actv.service new file mode 100644 index 0000000000..dc962d06fe --- /dev/null +++ b/toolchain/tv/actv.service @@ -0,0 +1,15 @@ +# actv.service — Aesthetic Computer TV station (laer-klokken chat, live). +# Second station on the same rig host: own display (:98), own sink, own +# Chrome profile — see actv.env. Install like wgtv.service. +[Unit] +Description=Aesthetic Computer TV → YouTube RTMP rig +After=network-online.target + +[Service] +Type=exec +ExecStart=/bin/sh %h/actv/rig.sh %h/actv/actv.env +Restart=always +RestartSec=10 + +[Install] +WantedBy=default.target diff --git a/toolchain/tv/rig.sh b/toolchain/tv/rig.sh index 90889a6a49..934e240f85 100755 --- a/toolchain/tv/rig.sh +++ b/toolchain/tv/rig.sh @@ -45,7 +45,10 @@ cleanup() { [ -n "${CHROME_PID:-}" ] && kill "$CHROME_PID" 2>/dev/null || true [ -n "${XVFB_PID:-}" ] && kill "$XVFB_PID" 2>/dev/null || true } -trap cleanup EXIT INT TERM +trap cleanup EXIT +# A bare signal trap would swallow TERM during startup and keep booting a +# unit systemd is trying to stop — exit explicitly so the EXIT trap tears down. +trap 'exit 143' INT TERM # ── virtual speaker ────────────────────────────────────────────────── pulseaudio --check 2>/dev/null || pulseaudio --start --exit-idle-time=-1 @@ -78,7 +81,9 @@ sleep 15 log "chrome watching $URL" if [ "$UNMUTE_CLICK" = "1" ]; then - DISPLAY="$DISPLAY_NUM" xdotool mousemove "$((W / 2))" "$((H / 2))" click 1 + # Default tap lands center; stations whose center is interactive (laklok + # opens a message dialog) aim CLICK_X/CLICK_Y somewhere inert instead. + DISPLAY="$DISPLAY_NUM" xdotool mousemove "${CLICK_X:-$((W / 2))}" "${CLICK_Y:-$((H / 2))}" click 1 sleep 1 # Park the cursor in the corner so it doesn't sit on the picture. DISPLAY="$DISPLAY_NUM" xdotool mousemove "$((W - 1))" "$((H - 1))" diff --git a/toolchain/tv/watchdog.sh b/toolchain/tv/watchdog.sh index ddad422811..b130f75f00 100644 --- a/toolchain/tv/watchdog.sh +++ b/toolchain/tv/watchdog.sh @@ -21,8 +21,10 @@ D="${DISPLAY_NUM:-:99}" SIZE="${SIZE:-1920x1080}" SINK="${SINK:-tvsink}" UNIT="${UNIT:-wgtv}" -WD="$HOME/wgtv/watchdog" -LOG="$HOME/wgtv/watchdog.log" +PROFILE_DIR="${PROFILE_DIR:-$HOME/.tv-rig-chrome}" +AUDIO_CHECK="${AUDIO_CHECK:-1}" # 0 for stations that are legitimately quiet +WD="${WD_DIR:-$HOME/$UNIT/watchdog}" +LOG="${WD_LOG:-$HOME/$UNIT/watchdog.log}" mkdir -p "$WD" log() { echo "[watchdog $(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG"; } @@ -38,10 +40,22 @@ strikes() { cat "$WD/$1" 2>/dev/null || echo 0; } strike() { echo $(($(strikes "$1") + 1)) > "$WD/$1"; } clear_() { rm -f "$WD/$1"; } -# ── vital 1: the process tree ──────────────────────────────────────── -pgrep -f "tv-rig-chrome" >/dev/null || restart "chrome gone" -pgrep -x Xvfb >/dev/null || restart "Xvfb gone" -pgrep -f "rtmp://" >/dev/null || restart "ffmpeg gone" +# ── warm-up grace ──────────────────────────────────────────────────── +# The timer can fire during (or right after) a station boot — Chrome takes +# ~20s to appear. Judging a station that's been up under two minutes shot +# actv down mid-startup once; never again. +enter_us=$(systemctl --user show "$UNIT" -p ActiveEnterTimestampMonotonic --value 2>/dev/null) +now_us=$(awk '{printf "%d", $1 * 1000000}' /proc/uptime) +if [ -z "$enter_us" ] || [ "$enter_us" = "0" ] || + [ $((now_us - enter_us)) -lt 120000000 ]; then + log "warming up (unit age <120s) — skipping checks" + exit 0 +fi + +# ── vital 1: the process tree (this station's, not any station's) ──── +pgrep -f "user-data-dir=$PROFILE_DIR" >/dev/null || restart "chrome gone" +pgrep -f "Xvfb $D" >/dev/null || restart "Xvfb gone" +ps -eo args | grep -v grep | grep "rtmp://" | grep -q -- "-i $D " || restart "ffmpeg gone" # ── vital 2: motion in the picture ─────────────────────────────────── md5=$(ffmpeg -v error -f x11grab -video_size "$SIZE" -i "$D" -frames:v 1 \ @@ -56,14 +70,17 @@ fi echo "$md5" > "$WD/last_md5" # ── vital 3: sound on the sink ─────────────────────────────────────── -peak=$(ffmpeg -v info -f pulse -i "${SINK}.monitor" -t 3 -af volumedetect -f null - 2>&1 | - sed -n 's/.*max_volume: \(-\{0,1\}[0-9.]*\) dB.*/\1/p') -if [ -z "$peak" ] || [ "$(echo "$peak" | cut -d. -f1)" -lt -70 ] 2>/dev/null; then - strike silence_strikes - log "silence peak=${peak:-none} (strike $(strikes silence_strikes))" - [ "$(strikes silence_strikes)" -ge 3 ] && restart "audio dead" -else - clear_ silence_strikes +peak="" +if [ "$AUDIO_CHECK" = "1" ]; then + peak=$(ffmpeg -v info -f pulse -i "${SINK}.monitor" -t 3 -af volumedetect -f null - 2>&1 | + sed -n 's/.*max_volume: \(-\{0,1\}[0-9.]*\) dB.*/\1/p') + if [ -z "$peak" ] || [ "$(echo "$peak" | cut -d. -f1)" -lt -70 ] 2>/dev/null; then + strike silence_strikes + log "silence peak=${peak:-none} (strike $(strikes silence_strikes))" + [ "$(strikes silence_strikes)" -ge 3 ] && restart "audio dead" + else + clear_ silence_strikes + fi fi log "ok md5=${md5:-none} peak=${peak:-?}dB" -- 2.51.2