From c136dafc5b01d39565ecffb4616c790a89789258 Mon Sep 17 00:00:00 2001 From: Arnout van Westen Date: Mon, 5 Oct 2026 11:05:19 +0200 Subject: [PATCH] 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. --- dawarich/docker-compose.yml | 84 +++++++------- teslamate/compose.yaml | 35 +++--- traefik-network-watcher/docker-compose.yml | 20 ++++ traefik-network-watcher/handler.sh | 129 +++++++++++++++++++++ 4 files changed, 208 insertions(+), 60 deletions(-) create mode 100644 traefik-network-watcher/docker-compose.yml create mode 100644 traefik-network-watcher/handler.sh diff --git a/dawarich/docker-compose.yml b/dawarich/docker-compose.yml index 1a7f75d..5859bab 100644 --- a/dawarich/docker-compose.yml +++ b/dawarich/docker-compose.yml @@ -1,27 +1,49 @@ +# Based on upstream https://github.com/Freika/dawarich/blob/master/docker/docker-compose.yml +# Local deviations: +# - image tags pinned (repo convention; Renovate bumps) +# - host port 3007 -> 3000: pangolin (newt) and LAN clients target +# 192.168.10.144:3007, so it must stay published on the host +# - traefik labels on dawarich_app: Atlas traefik does NOT sit on this +# network; traefik-network-connector (own stack) attaches it on start +# - RAILS_ENV production (upstream default; install predates the switch) +# - BACKGROUND_PROCESSING_CONCURRENCY 10 (upstream default 3; tuned for +# large imports) +# - local photon geocoding (photon container currently disabled) +networks: + dawarich: + labels: + - traefik.enable=true + services: dawarich_redis: - image: redis:8.10-alpine + image: redis:7.4-alpine container_name: dawarich_redis - command: redis-server - network_mode: bridge + command: > + redis-server + --save 900 1 + --save 300 10 + --appendonly no + networks: + - dawarich volumes: - dawarich_shared:/data restart: always healthcheck: - test: [ "CMD-SHELL", "redis-cli --raw incr ping || exit 1" ] + test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] interval: 10s retries: 5 start_period: 30s timeout: 10s + dawarich_db: image: postgis/postgis:17-3.5-alpine shm_size: 1G container_name: dawarich_db - network_mode: bridge volumes: - dawarich_db_data:/var/lib/postgresql/data - dawarich_shared:/var/shared - # - ./postgresql.conf:/etc/postgresql/postgresql.conf # Optional, uncomment if you want to use a custom config + networks: + - dawarich environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password @@ -33,29 +55,29 @@ services: retries: 5 start_period: 30s timeout: 10s - # command: postgres -c config_file=/etc/postgresql/postgresql.conf # Use custom config, uncomment if you want to use a custom config + dawarich_app: - labels: - - traefik.constraint=proxy-public - - traefik.http.routers.nginx.rule=Host(`dawarich.arnoutvw.nl`) image: freikin/dawarich:1.15.3 container_name: dawarich_app - network_mode: bridge volumes: - dawarich_public:/var/app/public - dawarich_watched:/var/app/tmp/imports/watched - dawarich_storage:/var/app/storage - links: - - dawarich_db - - dawarich_redis + - dawarich_db_data:/dawarich_db_data + networks: + - dawarich + labels: + - traefik.enable=true + - traefik.http.routers.dawarich.rule=Host(`dawarich.arnoutvw.nl`) + - traefik.http.services.dawarich.loadbalancer.server.port=3000 ports: + # custom host port: pangolin/newt + LAN target 192.168.10.144:3007 - 3007:3000 - - 9397:9394 # Prometheus exporter, uncomment if needed stdin_open: true tty: true entrypoint: web-entrypoint.sh command: [ 'bin/rails', 'server', '-p', '3000', '-b', '::' ] - restart: always + restart: unless-stopped environment: RAILS_ENV: production SECRET_KEY_BASE: 2f5e59518ddeaeb978c73bc265eea20a91d5fe203d2a45280bd8fbf62b447b57 @@ -66,13 +88,6 @@ services: DATABASE_NAME: dawarich_development MIN_MINUTES_SPENT_IN_CITY: 60 APPLICATION_HOSTS: localhost,dawarich.arnoutvw.nl,192.168.10.144 - TIME_ZONE: Europe/London - APPLICATION_PROTOCOL: http - PROMETHEUS_EXPORTER_ENABLED: false - PROMETHEUS_EXPORTER_HOST: 0.0.0.0 - PROMETHEUS_EXPORTER_PORT: 9394 - SELF_HOSTED: "true" - STORE_GEODATA: "true" PHOTON_API_HOST: 192.168.10.144:2322 PHOTON_API_USE_HTTPS: false logging: @@ -96,8 +111,9 @@ services: deploy: resources: limits: - cpus: '0.50' # Limit CPU usage to 50% of one core - memory: '4G' # Limit memory usage to 4GB + cpus: '0.50' + memory: '4G' + dawarich_sidekiq: image: freikin/dawarich:1.15.3 container_name: dawarich_sidekiq @@ -105,15 +121,13 @@ services: - dawarich_public:/var/app/public - dawarich_watched:/var/app/tmp/imports/watched - dawarich_storage:/var/app/storage - network_mode: bridge - links: - - dawarich_db - - dawarich_redis + networks: + - dawarich stdin_open: true tty: true entrypoint: sidekiq-entrypoint.sh command: [ 'sidekiq' ] - restart: always + restart: unless-stopped environment: RAILS_ENV: production SECRET_KEY_BASE: 2f5e59518ddeaeb978c73bc265eea20a91d5fe203d2a45280bd8fbf62b447b57 @@ -122,17 +136,9 @@ services: DATABASE_USERNAME: postgres DATABASE_PASSWORD: password DATABASE_NAME: dawarich_development - APPLICATION_HOSTS: localhost BACKGROUND_PROCESSING_CONCURRENCY: 10 - APPLICATION_PROTOCOL: http - PROMETHEUS_EXPORTER_ENABLED: false - PROMETHEUS_EXPORTER_HOST: dawarich_app - PROMETHEUS_EXPORTER_PORT: 9394 - SELF_HOSTED: "true" - STORE_GEODATA: "true" PHOTON_API_HOST: 192.168.10.144:2322 PHOTON_API_USE_HTTPS: false - logging: driver: "json-file" options: @@ -160,4 +166,4 @@ volumes: dawarich_shared: dawarich_public: dawarich_watched: - dawarich_storage: + dawarich_storage: \ No newline at end of file diff --git a/teslamate/compose.yaml b/teslamate/compose.yaml index a119dcf..473fdc4 100644 --- a/teslamate/compose.yaml +++ b/teslamate/compose.yaml @@ -1,11 +1,15 @@ +networks: + teslamate: + labels: + - traefik.enable=true + services: teslamate: container_name: "teslamate" image: teslamate/teslamate:4.3.0 restart: always - network_mode: "bridge" - links: - - postgresql17 + networks: + - teslamate environment: - "DATABASE_USER=teslamate" - "DATABASE_NAME=teslamate" @@ -35,7 +39,8 @@ services: container_name: "postgresql17" image: postgres:17-alpine3.22 restart: always - network_mode: "bridge" + networks: + - teslamate environment: - "HOST_CONTAINERNAME=postgresql17" - "POSTGRES_USER=postgres" @@ -48,9 +53,8 @@ services: container_name: "teslamate-grafana" image: teslamate/grafana:4.3.0 restart: always - network_mode: "bridge" - links: - - postgresql17 + networks: + - teslamate environment: - "GF_AUTH_GENERIC_OAUTH_SCOPES=openid profile email" - "GF_AUTH_GENERIC_OAUTH_TOKEN_URL=https://pass.arnoutvw.nl/api/oidc/token" @@ -122,9 +126,7 @@ services: restart: "always" teslamateapi: container_name: "teslamateapi" - network_mode: "bridge" - links: - - postgresql17 + networks: - teslamate image: tobiasehlert/teslamateapi:1.25.0 restart: always @@ -162,15 +164,6 @@ services: - pangolin.proxy-resources.teslamateapi.targets[0].healthcheck.port=4001 - pangolin.proxy-resources.teslamateapi.targets[0].healthcheck.enabled=true - pangolin.proxy-resources.teslamateapi.auth.sso-enabled=false - - pangolin.proxy-resources.teslamateapi.rules[0].match=region + - pangolin.proxy-resources.teslamateapi.rules[0].match=ip - pangolin.proxy-resources.teslamateapi.rules[0].action=allow - - pangolin.proxy-resources.teslamateapi.rules[0].value=EU - - pangolin.proxy-resources.teslamateapi.rules[1].match=ip - - pangolin.proxy-resources.teslamateapi.rules[1].action=allow - - pangolin.proxy-resources.teslamateapi.rules[1].value=172.21.0.1 - - pangolin.proxy-resources.teslamateapi.rules[2].match=ip - - pangolin.proxy-resources.teslamateapi.rules[2].action=allow - - pangolin.proxy-resources.teslamateapi.rules[2].value=212.227.105.183 - - pangolin.proxy-resources.teslamateapi.rules[3].match=country - - pangolin.proxy-resources.teslamateapi.rules[3].action=deny - - pangolin.proxy-resources.teslamateapi.rules[3].value=ALL + - pangolin.proxy-resources.teslamateapi.rules[0].value=172.21.0.1 diff --git a/traefik-network-watcher/docker-compose.yml b/traefik-network-watcher/docker-compose.yml new file mode 100644 index 0000000..404c62c --- /dev/null +++ b/traefik-network-watcher/docker-compose.yml @@ -0,0 +1,20 @@ +# traefik-network-watcher — sh-script vervanging voor TNC +# (github.com/obeone/traefik_network_connector): koppelt traefik aan elk +# docker-netwerk met label traefik.enable=true, daemon-side filtering, +# periodieke resync (5 min) + traefik-restart herstel. +services: + traefik-network-watcher: + image: docker:28-cli + container_name: traefik-network-watcher + restart: unless-stopped + init: true + network_mode: bridge + entrypoint: ["/bin/sh", "/usr/local/bin/handler.sh"] + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./handler.sh:/usr/local/bin/handler.sh:ro + environment: + TZ: Europe/Amsterdam + TRAEFIK_CONTAINER: traefik + SYNC_INTERVAL: "300" + DETACH_UNLABELED: "false" \ No newline at end of file diff --git a/traefik-network-watcher/handler.sh b/traefik-network-watcher/handler.sh new file mode 100644 index 0000000..a1720f8 --- /dev/null +++ b/traefik-network-watcher/handler.sh @@ -0,0 +1,129 @@ +#!/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 \ No newline at end of file