Initial.
This commit is contained in:
parent
33482f373e
commit
1473eed0e0
311
livekit-ip-watch.v1.sh
Normal file
311
livekit-ip-watch.v1.sh
Normal file
@ -0,0 +1,311 @@
|
||||
#!/usr/bin/env bash
|
||||
# Beschreibung: Aktualisiert livekit.yaml (rtc.ips.includes) bei IP-Wechsel (IPv4 ODER IPv6),
|
||||
# restarte den LiveKit-Dienst (Docker oder Docker Compose), wartet konfigurierbar und prüft Health.
|
||||
# Mit Backup + Rollback, optionalen ntfy-Notifications, YAML-Update via yq (Fallback eingebaut).
|
||||
# Synapse: https://git.techniverse.net/scriptos/livekit-ip-watch.git
|
||||
# Autor: Patrick Asmus
|
||||
# Web: https://www.cleveradmin.de
|
||||
# Version: 1.0
|
||||
# Datum: 27.10.2025
|
||||
# Modifikation: Initial
|
||||
#####################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
############################
|
||||
# Konfiguration (Variablen)
|
||||
############################
|
||||
|
||||
# Pfad zur LiveKit-Konfiguration
|
||||
CONFIG_FILE="/home/docker-container/matrix-rtc/data/matrix-element-call-livekit/config.yaml"
|
||||
|
||||
# Betriebsmodus: "compose" oder "docker"
|
||||
RUNTIME="compose"
|
||||
|
||||
# Compose: Kompletter Pfad zur Compose-Datei + Service-Name
|
||||
COMPOSE_FILE_PATH="/home/docker-container/matrix-rtc/docker-compose.yaml"
|
||||
COMPOSE_SERVICE="matrix-element-call-livekit"
|
||||
|
||||
# Plain-Docker: Container-Name (nur genutzt, wenn RUNTIME="docker")
|
||||
CONTAINER_NAME="matrix-element-call-livekit"
|
||||
|
||||
# IP-Variante: IPv4 oder IPv6
|
||||
ENABLE_IPV6=false
|
||||
|
||||
# Wartezeit nach Neustart (Sekunden), dann Healthcheck
|
||||
WAIT_AFTER_RESTART=20
|
||||
|
||||
# Healthcheck-Konfiguration: "http" oder "tcp"
|
||||
HEALTHCHECK_MODE="http"
|
||||
HEALTHCHECK_URL="https://rtc.matrix.techniverse.net"
|
||||
TCP_HOST="127.0.0.1"
|
||||
TCP_PORT="7880"
|
||||
HEALTHCHECK_TIMEOUT=3
|
||||
|
||||
# ntfy (optional)
|
||||
NTFY_URL=""
|
||||
NTFY_TOKEN=""
|
||||
|
||||
############################
|
||||
# Hauptscript
|
||||
############################
|
||||
|
||||
# Skriptverzeichnis, Lock- und State-Dateien
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
LOCKFILE="$SCRIPT_DIR/livekit-ip-watch.lock"
|
||||
STATE_FILE_V4="$SCRIPT_DIR/last_ip_v4"
|
||||
STATE_FILE_V6="$SCRIPT_DIR/last_ip_v6"
|
||||
BACKUP_DIR="$SCRIPT_DIR/backups"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
log() { printf '[%(%Y-%m-%d %H:%M:%S)T] %s\n' -1 "$*" >&2; }
|
||||
die() { log "ERROR: $*"; notify "error" "$*"; exit 1; }
|
||||
warn() { log "WARN: $*"; notify "warn" "$*"; }
|
||||
info() { log "INFO: $*"; }
|
||||
|
||||
# Ntfy
|
||||
notify() {
|
||||
local level="${1:-info}"
|
||||
shift || true
|
||||
local msg="${*:-}"
|
||||
[[ -z "$NTFY_URL" ]] && return 0
|
||||
local hdr=(-H "Title: livekit-ip-watch" -H "Priority: default" -H "Tags: $level")
|
||||
[[ -n "$NTFY_TOKEN" ]] && hdr+=(-H "Authorization: Bearer $NTFY_TOKEN")
|
||||
curl -fsS -X POST "${hdr[@]}" -d "$msg" "$NTFY_URL" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Lock
|
||||
acquire_lock() {
|
||||
if command -v flock >/dev/null 2>&1; then
|
||||
exec 9>"$LOCKFILE"
|
||||
if ! flock -n 9; then
|
||||
info "Läuft bereits (Lock vorhanden): $LOCKFILE"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
set -o noclobber
|
||||
if ! : > "$LOCKFILE"; then
|
||||
info "Läuft bereits (Lock vorhanden): $LOCKFILE"
|
||||
exit 0
|
||||
fi
|
||||
set +o noclobber
|
||||
fi
|
||||
trap 'release_lock' EXIT
|
||||
}
|
||||
|
||||
release_lock() {
|
||||
rm -f "$LOCKFILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# IP-Ermittlung
|
||||
get_ipv4() {
|
||||
local ip
|
||||
ip="$(dig +short myip.opendns.com @resolver1.opendns.com 2>/dev/null || true)"
|
||||
[[ -z "$ip" ]] && ip="$(curl -fsS https://api.ipify.org 2>/dev/null || true)"
|
||||
[[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || ip=""
|
||||
echo "$ip"
|
||||
}
|
||||
|
||||
get_ipv6() {
|
||||
local ip
|
||||
ip="$(curl -fsS -6 https://api6.ipify.org 2>/dev/null || true)"
|
||||
if [[ "$ip" =~ : ]]; then
|
||||
echo "$ip"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
have_yq() { command -v yq >/dev/null 2>&1; }
|
||||
|
||||
backup_config() {
|
||||
[[ -r "$CONFIG_FILE" ]] || die "Config nicht lesbar: $CONFIG_FILE"
|
||||
local ts backup
|
||||
ts="$(date +%Y%m%d%H%M%S)"
|
||||
backup="$BACKUP_DIR/$(basename "$CONFIG_FILE").$ts.bak"
|
||||
cp -a "$CONFIG_FILE" "$backup"
|
||||
echo "$backup"
|
||||
}
|
||||
|
||||
update_yaml_with_yq() {
|
||||
local cidr="$1"
|
||||
YQ_VAR_CIDR="$cidr" yq -i '
|
||||
.rtc |= (. // {}) |
|
||||
.rtc.ips |= (. // {}) |
|
||||
.rtc.ips.includes = [env(YQ_VAR_CIDR)]
|
||||
' "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
update_yaml_fallback() {
|
||||
local cidr="$1"
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
awk -v newcidr="$cidr" '
|
||||
BEGIN{ in_rtc=0; in_ips=0; in_includes=0; injected=0 }
|
||||
{
|
||||
line=$0
|
||||
if ($0 ~ /^[[:space:]]*rtc:[[:space:]]*$/) { in_rtc=1; in_ips=0; in_includes=0 }
|
||||
else if (in_rtc && $0 ~ /^[[:space:]]*[[:alnum:]_]+:[[:space:]]*$/ && $0 !~ /^[[:space:]]*ips:/) {
|
||||
if (!in_ips && !in_includes && !injected) {
|
||||
print " ips:"
|
||||
print " includes:"
|
||||
print " - " newcidr
|
||||
injected=1
|
||||
}
|
||||
}
|
||||
if (in_rtc && $0 ~ /^[[:space:]]*ips:[[:space:]]*$/) { in_ips=1; in_includes=0 }
|
||||
if (in_ips && $0 ~ /^[[:space:]]*includes:[[:space:]]*$/) { in_includes=1; next }
|
||||
if (in_includes) {
|
||||
if ($0 ~ /^[[:space:]]*-[[:space:]]*[^[:space:]]+/) {
|
||||
next
|
||||
} else {
|
||||
if (!injected) {
|
||||
print " - " newcidr
|
||||
injected=1
|
||||
}
|
||||
in_includes=0
|
||||
in_ips=($0 ~ /^[[:space:]]*ips:/)?1:0
|
||||
}
|
||||
}
|
||||
print line
|
||||
}
|
||||
END{
|
||||
if (in_rtc && !injected) {
|
||||
print " ips:"
|
||||
print " includes:"
|
||||
print " - " newcidr
|
||||
}
|
||||
}
|
||||
' "$CONFIG_FILE" > "$tmp"
|
||||
mv "$tmp" "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
detect_compose_cmd() {
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
echo "docker-compose"
|
||||
else
|
||||
echo "docker compose"
|
||||
fi
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
if [[ "$RUNTIME" == "compose" ]]; then
|
||||
local dir file
|
||||
dir="$(dirname "$COMPOSE_FILE_PATH")"
|
||||
file="$(basename "$COMPOSE_FILE_PATH")"
|
||||
local COMPOSE_CMD
|
||||
COMPOSE_CMD="$(detect_compose_cmd)"
|
||||
( cd "$dir" && "$COMPOSE_CMD" -f "$file" restart "$COMPOSE_SERVICE" )
|
||||
else
|
||||
docker restart "$CONTAINER_NAME"
|
||||
fi
|
||||
}
|
||||
|
||||
healthcheck_http() {
|
||||
curl -fsS --max-time "$HEALTHCHECK_TIMEOUT" "$HEALTHCHECK_URL" >/dev/null
|
||||
}
|
||||
|
||||
healthcheck_tcp() {
|
||||
if (exec 3<>/dev/tcp/"$TCP_HOST"/"$TCP_PORT") 2>/dev/null; then
|
||||
exec 3<&-
|
||||
exec 3>&-
|
||||
return 0
|
||||
elif command -v nc >/dev/null 2>&1; then
|
||||
nc -z -w "$HEALTHCHECK_TIMEOUT" "$TCP_HOST" "$TCP_PORT"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
do_healthcheck() {
|
||||
if [[ "$HEALTHCHECK_MODE" == "http" ]]; then
|
||||
healthcheck_http
|
||||
else
|
||||
healthcheck_tcp
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
acquire_lock
|
||||
|
||||
[[ -f "$CONFIG_FILE" ]] || die "Config-Datei nicht gefunden: $CONFIG_FILE"
|
||||
if [[ "$RUNTIME" == "compose" ]]; then
|
||||
[[ -f "$COMPOSE_FILE_PATH" ]] || die "Compose-Datei nicht gefunden: $COMPOSE_FILE_PATH"
|
||||
[[ -n "$COMPOSE_SERVICE" ]] || die "COMPOSE_SERVICE ist leer."
|
||||
else
|
||||
[[ -n "$CONTAINER_NAME" ]] || die "CONTAINER_NAME ist leer."
|
||||
fi
|
||||
|
||||
# Aktuelle IP ermitteln
|
||||
local current_ip cidr state_file
|
||||
if [[ "$ENABLE_IPV6" == true ]]; then
|
||||
current_ip="$(get_ipv6)"
|
||||
[[ -z "$current_ip" ]] && die "Konnte keine öffentliche IPv6 ermitteln."
|
||||
cidr="${current_ip}/128"
|
||||
state_file="$STATE_FILE_V6"
|
||||
else
|
||||
current_ip="$(get_ipv4)"
|
||||
[[ -z "$current_ip" ]] && die "Konnte keine öffentliche IPv4 ermitteln."
|
||||
cidr="${current_ip}/32"
|
||||
state_file="$STATE_FILE_V4"
|
||||
fi
|
||||
|
||||
local last_ip=""
|
||||
[[ -f "$state_file" ]] && last_ip="$(cat "$state_file" 2>/dev/null || true)"
|
||||
|
||||
if [[ "$current_ip" == "$last_ip" && -n "$last_ip" ]]; then
|
||||
info "IP unverändert: $current_ip"
|
||||
notify "info" "Keine Änderung. Öffentliche IP bleibt $current_ip."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
info "Neue IP erkannt: $current_ip (alt: ${last_ip:-none})"
|
||||
local backup
|
||||
backup="$(backup_config)"
|
||||
info "Backup erstellt: $backup"
|
||||
|
||||
# YAML aktualisieren
|
||||
if have_yq; then
|
||||
info "Aktualisiere YAML mit yq → .rtc.ips.includes = [ \"$cidr\" ]"
|
||||
update_yaml_with_yq "$cidr"
|
||||
else
|
||||
warn "yq nicht gefunden – Fallback-Editor wird verwendet."
|
||||
update_yaml_fallback "$cidr"
|
||||
fi
|
||||
|
||||
# Dienst neu starten
|
||||
info "Restart des Dienstes..."
|
||||
restart_service
|
||||
|
||||
info "Warte $WAIT_AFTER_RESTART Sekunden..."
|
||||
sleep "$WAIT_AFTER_RESTART"
|
||||
|
||||
# Healthcheck
|
||||
if do_healthcheck; then
|
||||
info "Healthcheck OK."
|
||||
echo "$current_ip" > "$state_file"
|
||||
notify "info" "IP geändert auf $current_ip, Dienst gesund."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Rollback bei Fehler
|
||||
warn "Healthcheck fehlgeschlagen – führe Rollback durch."
|
||||
cp -a "$backup" "$CONFIG_FILE"
|
||||
info "Rollback-Konfiguration wiederhergestellt: $backup"
|
||||
|
||||
info "Restart nach Rollback..."
|
||||
restart_service
|
||||
|
||||
info "Warte $WAIT_AFTER_RESTART Sekunden (Rollback)..."
|
||||
sleep "$WAIT_AFTER_RESTART"
|
||||
|
||||
if do_healthcheck; then
|
||||
warn "Rollback erfolgreich. System läuft wieder mit alter Konfiguration."
|
||||
notify "warn" "Rollback erfolgreich. Bitte prüfen. Alte IP bleibt ${last_ip:-unbekannt}."
|
||||
exit 10
|
||||
else
|
||||
die "Rollback fehlgeschlagen. Manuelles Eingreifen erforderlich."
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
x
Reference in New Issue
Block a user