Add auto-deployment infrastructure for all 4 LXCs

- Per-LXC deploy scripts (lxc151-hookshot, lxc106-cinny, lxc139-landing, lxc110-draupnir)
- Per-LXC webhook hook configs with unique HMAC-SHA256 secrets
- Livekit graceful restart script + systemd timer (waits for zero active calls)
- Fix hookshot/deploy.sh capitalization bug (Uptime-Kuma, Tinker-Tickets, etc.)

Each LXC independently clones repo and runs its own deploy.sh via adnanh/webhook on port 9000.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 11:41:32 -04:00
parent 0e275d725e
commit 5e936b2ca1
12 changed files with 293 additions and 2 deletions

18
deploy/hooks-lxc106.json Normal file
View File

@@ -0,0 +1,18 @@
[
{
"id": "matrix-deploy",
"execute-command": "/usr/local/bin/matrix-deploy.sh",
"command-working-directory": "/opt/matrix-config",
"response-message": "Deploying cinny config...",
"trigger-rule": {
"match": {
"type": "payload-hash-sha256",
"secret": "76dd5febd1cc3458545ce37537f4bfe26f241a9635b57a2cba183ebc9221230b",
"parameter": {
"source": "header",
"name": "X-Gitea-Signature"
}
}
}
}
]

18
deploy/hooks-lxc110.json Normal file
View File

@@ -0,0 +1,18 @@
[
{
"id": "matrix-deploy",
"execute-command": "/usr/local/bin/matrix-deploy.sh",
"command-working-directory": "/opt/matrix-config",
"response-message": "Deploying draupnir config...",
"trigger-rule": {
"match": {
"type": "payload-hash-sha256",
"secret": "0d23fab8743e9ee6b52cbd05a889b04c927ffa2b2b21fe50244f1a534d1a22d0",
"parameter": {
"source": "header",
"name": "X-Gitea-Signature"
}
}
}
}
]

18
deploy/hooks-lxc139.json Normal file
View File

@@ -0,0 +1,18 @@
[
{
"id": "matrix-deploy",
"execute-command": "/usr/local/bin/matrix-deploy.sh",
"command-working-directory": "/opt/matrix-config",
"response-message": "Deploying matrix landing page...",
"trigger-rule": {
"match": {
"type": "payload-hash-sha256",
"secret": "ddea576ef03bff35f0c9d138b626b273d9e9502434e0717899a87677cd5ac267",
"parameter": {
"source": "header",
"name": "X-Gitea-Signature"
}
}
}
}
]

18
deploy/hooks-lxc151.json Normal file
View File

@@ -0,0 +1,18 @@
[
{
"id": "matrix-deploy",
"execute-command": "/usr/local/bin/matrix-deploy.sh",
"command-working-directory": "/opt/matrix-config",
"response-message": "Deploying matrix hookshot transforms...",
"trigger-rule": {
"match": {
"type": "payload-hash-sha256",
"secret": "38ba0e66763da2096c47645cbf636ce3c2c51232e006b964e57d6bb94a32dcaa",
"parameter": {
"source": "header",
"name": "X-Gitea-Signature"
}
}
}
}
]

View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Checks every 5 minutes if a livekit restart is pending.
# Only restarts when there are no active WebSocket connections on port 7881
# (established connections = active call participants).
# Run by: livekit-graceful-restart.timer (systemd)
if [ ! -f /run/livekit-restart-pending ]; then
exit 0
fi
# Count established WebSocket signaling connections on livekit port 7881
ACTIVE=$(ss -tn state established '( dport = :7881 or sport = :7881 )' | grep -c ESTAB || true)
if [ "$ACTIVE" -gt 0 ]; then
echo "$(date): Livekit restart pending but $ACTIVE active connection(s) — waiting."
exit 0
fi
echo "$(date): No active calls — applying livekit-server restart."
systemctl restart livekit-server
rm -f /run/livekit-restart-pending
echo "$(date): livekit-server restarted successfully."

41
deploy/lxc106-cinny.sh Normal file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Auto-deploy script for LXC 106 (cinny)
# Handles: cinny/config.json, cinny/dev-update.sh
# 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"
exec >> "$LOG" 2>&1
echo "=== $(date) === LXC106 deploy triggered ==="
# Clone or pull
if [ ! -d "$REPO_DIR/.git" ]; then
git clone "$CLONE_URL" "$REPO_DIR"
CHANGED="cinny/config.json cinny/dev-update.sh"
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"
fi
if echo "$CHANGED" | grep -q '^cinny/config.json'; then
echo "Deploying cinny config.json..."
cp "$REPO_DIR/cinny/config.json" /var/www/html/config.json
echo "✓ config.json deployed"
fi
if echo "$CHANGED" | grep -q '^cinny/dev-update.sh'; then
echo "Deploying cinny dev-update.sh..."
cp "$REPO_DIR/cinny/dev-update.sh" /usr/local/bin/cinny-dev-update.sh
chmod +x /usr/local/bin/cinny-dev-update.sh
echo "✓ dev-update.sh deployed"
fi
echo "=== $(date) === LXC106 deploy complete ==="

52
deploy/lxc110-draupnir.sh Normal file
View File

@@ -0,0 +1,52 @@
#!/bin/bash
# Auto-deploy script for LXC 110 (draupnir)
# Handles: draupnir/production.yaml — restarts draupnir after deploy
# NOTE: access token in production.yaml is redacted in git.
# The real token lives only at /opt/draupnir/config/production.yaml on this LXC.
# This script merges the git version (structure/settings) with the live token.
# Triggered by: Gitea webhook on push to main
set -euo pipefail
REPO_DIR="/opt/matrix-config"
LIVE_CONFIG="/opt/draupnir/config/production.yaml"
LOG="/var/log/matrix-deploy.log"
CLONE_URL="https://code.lotusguild.org/LotusGuild/matrix.git"
exec >> "$LOG" 2>&1
echo "=== $(date) === LXC110 deploy triggered ==="
# Clone or pull
if [ ! -d "$REPO_DIR/.git" ]; then
git clone "$CLONE_URL" "$REPO_DIR"
CHANGED="draupnir/production.yaml"
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"
fi
if echo "$CHANGED" | grep -q '^draupnir/production.yaml'; then
echo "Deploying draupnir config..."
# Extract live access token (never stored in git)
LIVE_TOKEN=$(grep '^accessToken:' "$LIVE_CONFIG" | awk '{print $2}' | tr -d '"')
if [ -z "$LIVE_TOKEN" ]; then
echo "ERROR: Could not extract live accessToken from $LIVE_CONFIG — aborting." >&2
exit 1
fi
# Copy repo version and restore the live token
cp "$REPO_DIR/draupnir/production.yaml" "$LIVE_CONFIG"
sed -i "s|accessToken: \"REDACTED\"|accessToken: \"$LIVE_TOKEN\"|" "$LIVE_CONFIG"
echo "Restarting draupnir..."
systemctl restart draupnir
sleep 3
systemctl is-active draupnir && echo "✓ draupnir restarted successfully" || echo "✗ draupnir failed to start"
fi
echo "=== $(date) === LXC110 deploy complete ==="

36
deploy/lxc139-landing.sh Normal file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Auto-deploy script for LXC 139 (nginx proxy manager)
# Handles: landing/index.html
# 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"
exec >> "$LOG" 2>&1
echo "=== $(date) === LXC139 deploy triggered ==="
# Clone or pull
if [ ! -d "$REPO_DIR/.git" ]; then
git clone "$CLONE_URL" "$REPO_DIR"
CHANGED="landing/index.html"
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"
fi
if echo "$CHANGED" | grep -q '^landing/index.html'; then
echo "Deploying landing page..."
mkdir -p /var/www/matrix-landing
cp "$REPO_DIR/landing/index.html" /var/www/matrix-landing/index.html
nginx -s reload
echo "✓ landing page deployed and nginx reloaded"
fi
echo "=== $(date) === LXC139 deploy complete ==="

49
deploy/lxc151-hookshot.sh Normal file
View File

@@ -0,0 +1,49 @@
#!/bin/bash
# Auto-deploy script for LXC 151 (matrix homeserver)
# Handles: hookshot transformation functions, livekit service file (graceful)
# 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
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
fi
echo "=== $(date) === LXC151 deploy complete ==="

View File

@@ -24,8 +24,9 @@ DIR="$(cd "$(dirname "$0")" && pwd)"
deploy_hook() {
local file="$1"
local filename="$(basename "$file" .js)"
# Capitalize first letter to match state_key (e.g. proxmox -> Proxmox)
local state_key="$(echo "$filename" | sed 's/\b\(.\)/\u\1/g' | sed 's/-\(.\)/\u\1/g')"
# Capitalize first letter of each hyphen-separated word, preserving hyphens
# e.g. proxmox -> Proxmox, uptime-kuma -> Uptime-Kuma, tinker-tickets -> Tinker-Tickets
local state_key="$(python3 -c "import sys; s='$filename'; print('-'.join(w.capitalize() for w in s.split('-')))")"
local fn="$(cat "$file")"
local encoded_room="$(python3 -c "import urllib.parse; print(urllib.parse.quote('$MATRIX_ROOM'))")"
local encoded_key="$(python3 -c "import urllib.parse; print(urllib.parse.quote('$state_key'))")"

View File

@@ -0,0 +1,9 @@
[Unit]
Description=LiveKit graceful restart when no active calls
After=livekit-server.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/livekit-graceful-restart.sh
StandardOutput=journal
StandardError=journal

View File

@@ -0,0 +1,9 @@
[Unit]
Description=Check every 5 minutes if a pending livekit restart can be applied
[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target