Files
matrix/deploy/lxc151-hookshot.sh
T
jared a06f2c662a feat(livekit-guard): enforce per-room call permissions (screenshare/camera)
Extend voice-limit-guard to enforce a per-room publish-source policy
(io.lotus.room_quality allow_screenshare/allow_camera) for ALL Matrix clients,
alongside the existing participant limit.

- At token issue, re-sign the LiveKit JWT's canPublishSources to drop forbidden
  sources (microphone always kept). Verifies our own secret signed the token
  first and fails open on mismatch, so a secret drift can never mint a token the
  SFU rejects. Limit check and source policy are independent (one's outage can't
  skip the other).
- Live (mid-call) enforcement: a background reconcile loop calls LiveKit
  UpdateParticipant to revoke a forbidden source from participants who joined
  before the policy changed -- which unpublishes their in-progress
  screenshare/camera server-side within ~3s and blocks re-publish. Only removes
  sources (never grants), preserves other permission flags, fails open, and runs
  as a daemon thread that cannot crash or block token issuance.
- Endpoint-specific room-id extraction (/get_token->room_id, /sfu/get->room) so
  a client sending both keys can't get a different room's policy applied.
- Auto-deploy the guard on LXC 151 (py_compile-gated, backup + rollback).
- Unit tests: JWT re-sign/verify + tamper, secret-mismatch, source narrowing,
  reconcile (never-grant / preserve-flags / disable-on-empty), fail-open.

Numeric bitrate/fps caps are NOT server-enforceable on an SFU (LiveKit forwards,
never transcodes) and remain a Lotus-client-cooperative setting; the
screenshare/camera permission is the hard cross-client lever.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:34:34 -04:00

113 lines
4.8 KiB
Bash

#!/bin/bash
# Auto-deploy script for LXC 151 (matrix homeserver)
# Handles: hookshot transformation functions, livekit service file (graceful),
# voice-limit-guard (livekit policy sidecar), matrixbot
# Triggered by: Gitea webhook on push to main
set -euo pipefail
REPO_DIR="/opt/matrix-config"
LOG="/var/log/matrix-deploy.log"
CLONE_URL="https://code.lotusguild.org/LotusGuild/matrix.git"
ENV_FILE="/etc/matrix-deploy.env"
exec >> "$LOG" 2>&1
echo "=== $(date) === LXC151 deploy triggered ==="
# Load env (MATRIX_TOKEN, MATRIX_SERVER, MATRIX_ROOM)
if [ -f "$ENV_FILE" ]; then
# shellcheck source=/dev/null
source "$ENV_FILE"
fi
# Clone or pull
if [ ! -d "$REPO_DIR/.git" ]; then
git clone "$CLONE_URL" "$REPO_DIR"
else
cd "$REPO_DIR"
git fetch --all
PREV=$(git rev-parse HEAD)
git reset --hard origin/main
NEW=$(git rev-parse HEAD)
CHANGED=$(git diff --name-only "$PREV" "$NEW")
echo "Changed files: $CHANGED"
# Hookshot transforms
if echo "$CHANGED" | grep -q '^hookshot/'; then
echo "Deploying hookshot transforms..."
export MATRIX_TOKEN MATRIX_SERVER MATRIX_ROOM
bash "$REPO_DIR/hookshot/deploy.sh"
fi
# Livekit service file — graceful restart (never kill active calls)
if echo "$CHANGED" | grep -q '^systemd/livekit-server.service'; then
echo "livekit-server.service changed — staging graceful restart..."
cp "$REPO_DIR/systemd/livekit-server.service" /etc/systemd/system/livekit-server.service
systemctl daemon-reload
touch /run/livekit-restart-pending
echo "Restart pending — will apply when no active calls."
fi
# Voice-limit / quality guard (fronts lk-jwt-service on :8070)
if echo "$CHANGED" | grep -qE '^livekit/voice-limit-guard\.py|^systemd/voice-limit-guard\.service'; then
echo "Deploying voice-limit-guard..."
# Validate syntax BEFORE swapping so a broken edit can never wedge token
# issuance (a syntax error would crash the guard and block all joins).
if python3 -m py_compile "$REPO_DIR/livekit/voice-limit-guard.py"; then
GUARD_DST="/opt/voice-limit-guard/voice-limit-guard.py"
GUARD_BAK="/opt/voice-limit-guard/voice-limit-guard.py.bak"
# Back up the last-known-good guard so a runtime-broken (but
# syntactically valid) deploy can self-heal — a dead guard breaks
# ALL new joins, so we must never leave it down.
[ -f "$GUARD_DST" ] && cp "$GUARD_DST" "$GUARD_BAK"
install -D -m644 "$REPO_DIR/livekit/voice-limit-guard.py" "$GUARD_DST"
if echo "$CHANGED" | grep -q '^systemd/voice-limit-guard\.service'; then
install -m644 "$REPO_DIR/systemd/voice-limit-guard.service" /etc/systemd/system/voice-limit-guard.service
systemctl daemon-reload
fi
# Restarting the guard only affects joins in a ~1s window (established
# calls talk directly to livekit-server); it does not drop calls.
# `|| true` so a non-zero restart can't abort the deploy under set -e.
systemctl restart voice-limit-guard || true
sleep 1
if systemctl is-active --quiet voice-limit-guard; then
echo "voice-limit-guard restarted successfully."
elif [ -f "$GUARD_BAK" ]; then
echo "ERROR: new voice-limit-guard failed to start — rolling back to last-known-good."
cp "$GUARD_BAK" "$GUARD_DST"
systemctl restart voice-limit-guard || true
sleep 1
if systemctl is-active --quiet voice-limit-guard; then
echo "voice-limit-guard rolled back and running."
else
echo "CRITICAL: voice-limit-guard down after rollback — manual intervention needed."
fi
else
echo "CRITICAL: voice-limit-guard failed to start and no backup to roll back to."
fi
else
echo "ERROR: py_compile failed on voice-limit-guard.py — skipping deploy."
fi
fi
# Matrixbot source files
if echo "$CHANGED" | grep -q '^matrixbot/'; then
echo "Deploying matrixbot changes..."
BOT_DIR="/opt/matrixbot"
BOT_FILES="bot.py callbacks.py commands.py config.py utils.py welcome.py wordle.py wordlist_answers.py wordlist_valid.py requirements.txt"
for f in $BOT_FILES; do
if [ -f "$REPO_DIR/matrixbot/$f" ]; then
cp "$REPO_DIR/matrixbot/$f" "$BOT_DIR/$f"
fi
done
systemctl restart matrixbot
sleep 2
if systemctl is-active --quiet matrixbot; then
echo "matrixbot restarted successfully."
else
echo "ERROR: matrixbot failed to restart."
fi
fi
fi
echo "=== $(date) === LXC151 deploy complete ==="