From abeea6256ce1acdd79e2a502fd1360a1d0f32e94 Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 28 Aug 2026 05:45:11 +0000 Subject: [PATCH] =?UTF-8?q?ops:=20appliance=20boot=20=E2=80=94=20wait=20fo?= =?UTF-8?q?r=20cameras/LiveKit,=20power=20button=20powers=20off?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 4 +++- README.md | 25 ++++++++++++++++++---- deploy/50-power-button.conf | 9 ++++++++ deploy/livekit-cameras.service | 16 ++++++++++---- deploy/livekit-server.service | 3 ++- deploy/wait-for-cameras.sh | 17 +++++++++++++++ deploy/wait-for-livekit.sh | 39 ++++++++++++++++++++++++++++++++++ 7 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 deploy/50-power-button.conf create mode 100755 deploy/wait-for-cameras.sh create mode 100755 deploy/wait-for-livekit.sh diff --git a/.env.example b/.env.example index 720f7f2..bb45862 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,7 @@ # LiveKit server connection -LIVEKIT_URL=ws://localhost:7880 +# Local TEST server (autostarted). Switch this URL to the remote +# LiveKit later; then: systemctl disable --now livekit-server +LIVEKIT_URL=ws://127.0.0.1:7880 LIVEKIT_API_KEY=APIKey_xxx LIVEKIT_API_SECRET=APIsecret_xxx diff --git a/README.md b/README.md index 7d1ab14..200269c 100644 --- a/README.md +++ b/README.md @@ -82,12 +82,29 @@ lightweight high-pass + soft-clip DSP path so streams still work. Until the 1 s window is full, hops use the DSP fallback so LiveKit is not silent on startup. -## systemd (enabled, 24/7) +## Appliance boot / power button -Two units, enabled for multi-user.target: +This box is meant to behave like an appliance: -- `livekit-server.service` — `/usr/local/bin/livekit-server` + `deploy/livekit-server.yaml` (:7880) -- `livekit-cameras.service` — `run.py` (Requires livekit-server) +- Power button while **off** → BIOS/OS boot → `livekit-cameras` waits for + USB cameras + LiveKit, then publishes. +- Power button while **on** → full `poweroff` (not sleep). Suspend/hibernate + targets are masked. Drop-in: + `/etc/systemd/logind.conf.d/50-power-button.conf` + +## systemd (enabled) + +- `livekit-server.service` — **local TEST** LiveKit on :7880 (autostart for now) +- `livekit-cameras.service` — publishers. `Wants=` the test server, does not + `Require` it. Destination is `LIVEKIT_URL` in `.env` (currently + `ws://127.0.0.1:7880`). + +When the real/remote LiveKit exists, change `.env` `LIVEKIT_URL` and: + +```bash +systemctl disable --now livekit-server +systemctl restart livekit-cameras +``` ```bash systemctl status livekit-cameras livekit-server diff --git a/deploy/50-power-button.conf b/deploy/50-power-button.conf new file mode 100644 index 0000000..41668b8 --- /dev/null +++ b/deploy/50-power-button.conf @@ -0,0 +1,9 @@ +[Login] +HandlePowerKey=poweroff +HandlePowerKeyLongPress=poweroff +HandleSuspendKey=ignore +HandleHibernateKey=ignore +HandleLidSwitch=ignore +HandleLidSwitchExternalPower=ignore +HandleLidSwitchDocked=ignore +IdleAction=ignore diff --git a/deploy/livekit-cameras.service b/deploy/livekit-cameras.service index 8672a7d..60b0142 100644 --- a/deploy/livekit-cameras.service +++ b/deploy/livekit-cameras.service @@ -1,20 +1,28 @@ [Unit] Description=Publish C920 cameras (speechbrain audio) to LiveKit +Documentation=file:///home/fdenkena/livekit-cameras/README.md After=network-online.target sound.target livekit-server.service Wants=network-online.target -Requires=livekit-server.service +# Wants, not Requires: local livekit-server is a test stand-in. +# Point LIVEKIT_URL at a remote server later and you can disable this unit. +Wants=livekit-server.service [Service] Type=simple WorkingDirectory=/home/fdenkena/livekit-cameras +EnvironmentFile=-/home/fdenkena/livekit-cameras/.env +Environment=PYTHONUNBUFFERED=1 +Environment=ENHANCE_MODE=force +ExecStartPre=/home/fdenkena/livekit-cameras/deploy/wait-for-cameras.sh +ExecStartPre=/home/fdenkena/livekit-cameras/deploy/wait-for-livekit.sh ExecStart=/home/fdenkena/livekit-cameras/.venv/bin/python /home/fdenkena/livekit-cameras/run.py +ExecStop=/bin/kill -TERM $MAINPID Restart=always RestartSec=5 +TimeoutStartSec=90 TimeoutStopSec=30 KillMode=control-group -Environment=PYTHONUNBUFFERED=1 -Environment=LIVEKIT_URL=ws://127.0.0.1:7880 -Environment=ENHANCE_MODE=force +KillSignal=SIGTERM [Install] WantedBy=multi-user.target diff --git a/deploy/livekit-server.service b/deploy/livekit-server.service index e7eeaae..da80601 100644 --- a/deploy/livekit-server.service +++ b/deploy/livekit-server.service @@ -1,5 +1,6 @@ [Unit] -Description=LiveKit server for the C920 camera fleet +Description=LiveKit TEST server for the C920 camera fleet (local only) +Documentation=file:///home/fdenkena/livekit-cameras/README.md After=network-online.target Wants=network-online.target diff --git a/deploy/wait-for-cameras.sh b/deploy/wait-for-cameras.sh new file mode 100755 index 0000000..0ffa235 --- /dev/null +++ b/deploy/wait-for-cameras.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Wait for udev to pin C920 nodes after boot. Never fail the unit: +# discovery can still run with whatever is present. +set -u +udevadm settle -t 20 || true +udevadm trigger --subsystem-match=video4linux --action=add || true +udevadm trigger --subsystem-match=sound --action=add || true +for _ in $(seq 1 45); do + n=$(ls -1 /dev/cam[0-9][0-9] 2>/dev/null | wc -l) + if [ "$n" -ge 1 ]; then + echo "wait-for-cameras: $n pinned /dev/camNN nodes" + exit 0 + fi + sleep 1 +done +echo "wait-for-cameras: none yet; starting anyway" >&2 +exit 0 diff --git a/deploy/wait-for-livekit.sh b/deploy/wait-for-livekit.sh new file mode 100755 index 0000000..5ca1fcc --- /dev/null +++ b/deploy/wait-for-livekit.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Wait until LIVEKIT_URL (from .env) accepts TCP. Used so publishers do not +# race the local test livekit-server on boot. When LIVEKIT_URL points at a +# remote host this waits on that host instead. +set -u +ENV_FILE=/home/fdenkena/livekit-cameras/.env +if [ -f "$ENV_FILE" ]; then + set -a + # shellcheck disable=SC1090 + . "$ENV_FILE" + set +a +fi +URL="${LIVEKIT_URL:-ws://127.0.0.1:7880}" +rest="${URL#*://}" +hostport="${rest%%/*}" +host="${hostport%%:*}" +port="${hostport##*:}" +case "$hostport" in + *:*) ;; + *) + case "$URL" in + wss://*) port=443 ;; + *) port=80 ;; + esac + ;; +esac +if [ "$host" = "localhost" ]; then + host=127.0.0.1 +fi +echo "wait-for-livekit: $host:$port" +for _ in $(seq 1 30); do + if (echo >/dev/tcp/"$host"/"$port") 2>/dev/null; then + echo "wait-for-livekit: ready" + exit 0 + fi + sleep 1 +done +echo "wait-for-livekit: $host:$port not reachable after 30s" >&2 +exit 1