Files
compose-files/traefik-network-watcher/handler.sh
T
Arnout van Westen c136dafc5b feat(trn): replace TNC with shell traefik-network-watcher
Own watcher (docker:28-cli + handler.sh): attaches traefik to every docker
network labelled traefik.enable=true (daemon-side filter), 5-min periodic
resync + traefik-restart healing. dawarich/teslamate back on private
networks with the net label set.
2026-10-05 11:05:19 +02:00

129 lines
3.8 KiB
Bash

#!/bin/sh
#
# traefik-network-watcher
# Koppelt Traefik automatisch aan elke Docker-network die het label
# "traefik.enable=true" draagt. De filtering gebeurt daemon-side
# (docker network ls --filter label=...), dus zonder label gebeurt er
# niets: geen inspect-call, geen connect.
#
# Gedrag:
# - bij opstart: eenmalige scan over alle netwerken met het label
# - live: reactie op Docker-events (network create/connect)
# - herstelt zichzelf als de Traefik-container opnieuw opgestart wordt
# - optioneel: periodieke resync (SYNC_INTERVAL, standaard 300s)
# - optioneel: detachen van netwerken die het label verloren hebben
# (DETACH_UNLABELED=true, standaard uit)
#
set -u
TRAEFIK="${TRAEFIK_CONTAINER:-traefik}"
LABEL_SELECTOR="${TRAEFIK_NETWORK_LABEL:-label=traefik.enable=true}"
EXCLUDED="${EXCLUDED_NETWORKS:-bridge host none ingress docker_gwbridge}"
SYNC_INTERVAL="${SYNC_INTERVAL:-300}"
DETACH_UNLABELED="${DETACH_UNLABELED:-false}"
log() { echo "[watcher] $*"; }
warn() { echo "[watcher] $*" >&2; }
is_excluded() {
for n in $EXCLUDED; do
[ "$1" = "$n" ] && return 0
done
return 1
}
is_labeled() {
# True alleen als de daemon zelf dit netwerk met het label teruggeeft.
# Dit is de single source of truth; nergens anders wordt op het label
# gecontroleerd of geparst.
[ "$(docker network ls --filter "name=^$1\$" --filter "$LABEL_SELECTOR" --format '{{.Name}}')" = "$1" ]
}
networks_of() {
docker inspect "$TRAEFIK" --format \
'{{range $name, $_ := .NetworkSettings.Networks}}{{$name}}
{{end}}' 2>/dev/null
}
is_connected() {
networks_of | grep -qxF "$1"
}
attach() {
net="$1"
is_excluded "$net" && return 0
is_connected "$net" && return 0
log "attach: $TRAEFIK -> $net"
docker network connect "$net" "$TRAEFIK" >/dev/null 2>&1 || warn "attach $net mislukt"
}
detach() {
net="$1"
is_excluded "$net" && return 0
is_connected "$net" || return 0
log "detach: $TRAEFIK -/- $net"
docker network disconnect "$net" "$TRAEFIK" >/dev/null 2>&1 || warn "detach $net mislukt"
}
sync() {
# Alles met het label verbinden (idempotent, connect-check zit in attach)
docker network ls --filter "$LABEL_SELECTOR" --format '{{.Name}}' | while read -r net; do
attach "$net"
done
# Optioneel: alles zonder label weer loskoppelen
if [ "$DETACH_UNLABELED" = "true" ]; then
networks_of | while read -r net; do
is_excluded "$net" && continue
is_labeled "$net" || detach "$net"
done
fi
}
# --- opstart ------------------------------------------------------------
docker inspect "$TRAEFIK" >/dev/null 2>&1 || {
warn "container '$TRAEFIK' niet gevonden; restart:unless-stopped probeert het opnieuw";
exit 1;
}
log "traefik='$TRAEFIK' label-selector='$LABEL_SELECTOR' sync-interval=${SYNC_INTERVAL}s"
if [ "$SYNC_INTERVAL" != "0" ]; then
(
while :; do
sleep "$SYNC_INTERVAL"
sync
done
) &
SYNC_PID=$!
trap 'kill "$SYNC_PID" 2>/dev/null; exit 0' INT TERM
fi
# 1. bestaande netwerken nalopen
log "eerste scan..."
sync
# 2. live events volgen
# - network create/connect -> label-check op het betrokken netwerk,
# connect = connect (handmatige detaches worden ook gerepareerd)
# - container start (traefik zelf) -> volledige resync, zo herstellen
# de koppelingen na een 'docker compose up -d' van de traefik-stack,
# omdat die het container-object vernieuwt en alle endpoints wist
docker events \
--filter type=network \
--filter type=container \
--format '{{.Type}} {{.Action}} {{.Actor.Attributes.name}}' |
while read -r type action name; do
case "$type:$action" in
network:create | network:connect)
is_labeled "$name" && attach "$name"
;;
container:start)
if [ "$name" = "$TRAEFIK" ]; then
log "traefik opnieuw gestart, resync"
sync
fi
;;
esac
done