removed set -euo pipefail and added auto container restarting to signaling server

This commit is contained in:
jenz 2026-07-29 10:26:13 +01:00
parent 9755e2ccf5
commit b84c728470
2 changed files with 64 additions and 13 deletions

View File

@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
set -euo pipefail
# ── Clean up stale sockets from previous run ────────────────────────────────── # ── Clean up stale sockets from previous run ──────────────────────────────────
rm -f /tmp/.X99-lock rm -f /tmp/.X99-lock
@ -117,6 +116,7 @@ echo "socat mic listener started on TCP :12345"
# gst_agent exits with code 1 on ICE failure or other fatal errors. # gst_agent exits with code 1 on ICE failure or other fatal errors.
# Without a restart loop, the container sits dead until manually restarted. # Without a restart loop, the container sits dead until manually restarted.
# 3 second delay before each restart to avoid a tight crash loop. # 3 second delay before each restart to avoid a tight crash loop.
#
while true; do while true; do
python3 ~/streaming-agent/gst_agent.py python3 ~/streaming-agent/gst_agent.py
echo "[gst_agent] exited with code $?, restarting in 3s..." echo "[gst_agent] exited with code $?, restarting in 3s..."

View File

@ -252,17 +252,6 @@ function performCssRestart(reason) {
containerRestarting = true; containerRestarting = true;
broadcastQueueState(); broadcastQueueState();
// Use Wine's own proper teardown instead of pkill + a fixed sleep guess.
// `wineserver -k` signals every Wine process under this prefix to exit;
// `wineserver -w` BLOCKS until wineserver has actually fully released all
// its shared memory / locks / sockets, rather than gambling on a fixed
// delay being long enough. This is what previously let a fresh CSS
// instance start before the old one had genuinely finished tearing down,
// inheriting a wedged state and landing right back on the same stuck
// "Connecting to server..." screen even after repeated restarts.
// Wrapped in `timeout` so a manual restart can never hang indefinitely if
// wineserver somehow never exits — after 15s we give up waiting and
// proceed to relaunch anyway.
try { try {
execSync(`docker exec ${CONTAINER} bash -c "wineserver -k; timeout 15 wineserver -w || true"`); execSync(`docker exec ${CONTAINER} bash -c "wineserver -k; timeout 15 wineserver -w || true"`);
console.log('[sig] CSS restart: wineserver fully torn down'); console.log('[sig] CSS restart: wineserver fully torn down');
@ -296,7 +285,14 @@ function performCssRestart(reason) {
// the actual cause of "keyboard stuck, mouse/video fine" after restarts. // the actual cause of "keyboard stuck, mouse/video fine" after restarts.
setTimeout(() => { setTimeout(() => {
try { try {
execSync(`docker exec ${CONTAINER} bash -c "DISPLAY=:99 WIN=\$(xdotool search --name '' | head -1) && xdotool windowfocus --sync \$WIN" || true`); // A fresh `docker exec` session gets a NEW environment built from
// the image's defaults, not the runtime environment of the
// container's PID 1 (start.sh) — unlike gst_agent.py, which
// inherits start.sh's environment directly as a child process and
// has always found xdotool fine. Explicitly ensure common binary
// paths are present here too, since this exact command was
// silently failing with "xdotool: not found" every time.
execSync(`docker exec ${CONTAINER} bash -c "export PATH=\$PATH:/usr/bin:/usr/local/bin:/bin; DISPLAY=:99 WIN=\$(xdotool search --name '' | head -1) && xdotool windowfocus --sync \$WIN" || true`);
console.log('[sig] CSS restart: focused new window'); console.log('[sig] CSS restart: focused new window');
} catch (e) { } catch (e) {
console.error('[sig] CSS restart: failed to focus new window:', e.message); console.error('[sig] CSS restart: failed to focus new window:', e.message);
@ -313,6 +309,49 @@ function performCssRestart(reason) {
const CSS_RESTART_INTERVAL = 5 * 60 * 60 * 1000; // 5 hours const CSS_RESTART_INTERVAL = 5 * 60 * 60 * 1000; // 5 hours
setInterval(() => performCssRestart('scheduled 5-hour restart'), CSS_RESTART_INTERVAL); setInterval(() => performCssRestart('scheduled 5-hour restart'), CSS_RESTART_INTERVAL);
// ── Container liveness check ──────────────────────────────────────────────────
// Everything above (performCssRestart, the watchdog, the manual restart
// button) assumes the DOCKER CONTAINER itself is alive and only needs its
// internal Wine/CSS process cycled. If the container's own main process
// (start.sh, PID 1) dies for any reason, the whole container exits — at
// which point every docker exec call above fails outright with "container
// is not running",
//
const CONTAINER_LIVENESS_INTERVAL = 30 * 1000; // 30 seconds
function checkContainerLiveness() {
let running = false;
try {
const out = execSync(`docker inspect -f "{{.State.Running}}" ${CONTAINER}`).toString().trim();
running = out === 'true';
} catch (e) {
running = false;
}
if (!running) {
console.error(`[sig] CONTAINER ${CONTAINER} IS NOT RUNNING — attempting docker start`);
try {
execSync(`docker start ${CONTAINER}`);
console.log(`[sig] docker start ${CONTAINER} succeeded — container should reboot via start.sh`);
} catch (e) {
console.error(`[sig] docker start ${CONTAINER} failed:`, e.message);
}
// The old agent registration and controller state are now stale — the
// container is rebooting from scratch (fresh gst_agent, fresh CSS).
// Clear them so the browser doesn't sit on a dead session waiting for
// an agent that will never re-register under the old WebSocket.
agentSessions.clear();
if (currentController) {
releaseControl(currentController, 'container-restart');
}
containerRestarting = true;
broadcastQueueState();
}
}
setInterval(checkContainerLiveness, CONTAINER_LIVENESS_INTERVAL);
function releaseControl(entry, reason) { function releaseControl(entry, reason) {
if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; } if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
if (currentController !== entry) return; if (currentController !== entry) return;
@ -441,6 +480,18 @@ wss.on('connection', (ws, req) => {
agentWs: ws, agentWs: ws,
}); });
console.log(`[sig] agent registered — sessionId=${agentSessionId}`); console.log(`[sig] agent registered — sessionId=${agentSessionId}`);
// A fresh agent registering is the actual completion signal for a
// full container reboot (checkContainerLiveness sets
// containerRestarting=true immediately after `docker start`, but that
// only kicks off the boot — the full start.sh sequence, Weston,
// Xwayland, PipeWire, CSS launch, then gst_agent itself starting and
// registering, takes much longer than a same-container CSS restart).
// Clear it here so the browser's loading overlay doesn't stay stuck.
if (containerRestarting) {
containerRestarting = false;
broadcastQueueState();
}
} }
else if (msg.type === 'request_control') { else if (msg.type === 'request_control') {