From 2cbfb7fab65736c0ace57599604579e66f908ecc Mon Sep 17 00:00:00 2001 From: Stanislav Ilnytskyi Date: Wed, 29 Jul 2026 10:43:54 +0200 Subject: [PATCH] Refresh Traefik static config instead of seeding it once ${WARDEN_HOME_DIR}/etc/traefik/traefik.yml is generated configuration rather than user state, but it was only copied into place when absent. After a Traefik major upgrade some settings, such as the router rule syntax, have to be adjusted globally for backward compatibility, and installs that already had the file never received them. Refresh it whenever it drifts from the shipped version, keeping the previous copy as traefik.yml.bak, and restart Traefik afterwards since static configuration is never hot reloaded. Co-Authored-By: Claude Opus 5 (1M context) --- commands/doctor.cmd | 16 +++++++++++++ commands/env.cmd | 6 +++++ commands/svc.cmd | 10 ++++---- utils/svc.sh | 58 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/commands/doctor.cmd b/commands/doctor.cmd index 7d3f9f52..132592fd 100644 --- a/commands/doctor.cmd +++ b/commands/doctor.cmd @@ -43,6 +43,22 @@ echo -e "\033[32mWarden global .env:\033[0m" cat ${WARDEN_HOME_DIR}/.env echo +echo -e "\033[32mTraefik version in use:\033[0m" +docker container inspect traefik --format '{{ .Config.Image }} ({{ .State.Status }})' 2>/dev/null \ + || echo "traefik container is not present" +echo + +echo -e "\033[32mTraefik static configuration:\033[0m" +if [[ ! -f "${WARDEN_HOME_DIR}/etc/traefik/traefik.yml" ]]; then + echo "not present; run 'warden svc up'" +elif cmp -s "${WARDEN_DIR}/config/traefik/traefik.yml" "${WARDEN_HOME_DIR}/etc/traefik/traefik.yml"; then + echo "in sync with ${WARDEN_DIR}/config/traefik/traefik.yml" +else + echo "differs from ${WARDEN_DIR}/config/traefik/traefik.yml (shipped < | > in use):" + diff "${WARDEN_DIR}/config/traefik/traefik.yml" "${WARDEN_HOME_DIR}/etc/traefik/traefik.yml" +fi +echo + echo -e "\033[32mWarden service override via Docker compose file:\033[0m" if [[ -f ${WARDEN_HOME_DIR}/docker-compose.yml ]]; then echo -e "\033[33mWarden services have additional service configuration added or overridden via ${WARDEN_HOME_DIR}/docker-compose.yml file.\033[0m" diff --git a/commands/env.cmd b/commands/env.cmd index 9083a02f..1dd924c7 100644 --- a/commands/env.cmd +++ b/commands/env.cmd @@ -10,6 +10,12 @@ if [[ "${WARDEN_PARAMS[0]}" == "up" ]]; then assertSvcRunning fi +## keep Traefik's static configuration current on environment lifecycle commands, since +## these are reached far more often than 'warden svc up' after an upgrade +if containsElement "${WARDEN_PARAMS[0]}" up start restart; then + assertTraefikStaticConfig +fi + HOST_UID=$(id -u) HOST_GID=$(id -g) diff --git a/commands/svc.cmd b/commands/svc.cmd index 29af3e82..c1aaaf26 100644 --- a/commands/svc.cmd +++ b/commands/svc.cmd @@ -67,6 +67,11 @@ if [[ -f "${WARDEN_HOME_DIR}/docker-compose.yml" ]]; then DOCKER_COMPOSE_ARGS+=("${WARDEN_HOME_DIR}/docker-compose.yml") fi +## keep Traefik's static configuration current whenever global services are (re)started +if containsElement "${WARDEN_PARAMS[0]}" up start restart; then + assertTraefikStaticConfig +fi + ## special handling when 'svc up' is run if [[ "${WARDEN_PARAMS[0]}" == "up" ]]; then @@ -84,11 +89,6 @@ if [[ "${WARDEN_PARAMS[0]}" == "up" ]]; then mkdir -p "${WARDEN_HOME_DIR}/etc/traefik" fi - ## copy configuration files into location where they'll be mounted into containers from - if [[ ! -f "${WARDEN_HOME_DIR}/etc/traefik/traefik.yml" ]]; then - cp "${WARDEN_DIR}/config/traefik/traefik.yml" "${WARDEN_HOME_DIR}/etc/traefik/traefik.yml" - fi - ## generate dynamic traefik ssl termination configuration cat > "${WARDEN_HOME_DIR}/etc/traefik/dynamic.yml" <<-EOT tls: diff --git a/utils/svc.sh b/utils/svc.sh index 17305ece..7c6a9fb9 100644 --- a/utils/svc.sh +++ b/utils/svc.sh @@ -1,6 +1,64 @@ #!/usr/bin/env bash [[ ! ${WARDEN_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1 +## Warden owns ${WARDEN_HOME_DIR}/etc/traefik/traefik.yml; it is generated configuration, +## not user state. After a Traefik major upgrade some settings, such as the router rule +## syntax, have to be adjusted globally for backward compatibility, and seeding the file +## once meant existing installs never received them. +## +## Override the service's volumes via ${WARDEN_HOME_DIR}/docker-compose.yml if you need +## Traefik to run against a hand-written static config. +## +## Returns 0 when the file was replaced, otherwise 1. +function refreshTraefikStaticConfig() { + local source="${WARDEN_DIR}/config/traefik/traefik.yml" + local target="${WARDEN_HOME_DIR}/etc/traefik/traefik.yml" + + if cmp -s "${source}" "${target}"; then + return 1 + fi + + mkdir -p "$(dirname "${target}")" + + if [[ -f "${target}" ]]; then + ## timestamped so a later refresh cannot clobber an earlier backup + local backup + backup="${target}.$(date +%Y%m%d-%H%M%S).bak" + cp "${target}" "${backup}" + echo "==> Refreshing ${target} from ${source}" + echo " Previous version saved as ${backup}" + fi + + cp "${source}" "${target}" + + return 0 +} + +## Resolve the container id of Warden's own running Traefik. Matching on the Compose +## project labels keeps this scoped to the services 'warden svc' orchestrates, so a +## Traefik belonging to something else on the same daemon is never restarted. +function wardenTraefikContainerId() { + docker container ls -q \ + --filter label=com.docker.compose.project=warden \ + --filter label=com.docker.compose.service=traefik \ + --filter status=running 2>/dev/null || true +} + +## Refresh Traefik's static configuration, restarting Traefik when it changed. Traefik +## never reloads static configuration, and Compose will not recreate the container just +## because a bind mounted file changed, so the restart has to be explicit. +function assertTraefikStaticConfig() { + refreshTraefikStaticConfig || return 0 + + local traefikId + traefikId="$(wardenTraefikContainerId)" + + if [[ -n "${traefikId}" ]]; then + echo "==> Restarting traefik to apply the updated static configuration" + docker restart "${traefikId}" >/dev/null + fi +} + function assertSvcRunning() { ## test for global services running wardenNetworkName=$(cat ${WARDEN_DIR}/docker/docker-compose.yml | grep -A3 'networks:' | tail -n1 | sed -e 's/[[:blank:]]*name:[[:blank:]]*//g')