LibrePortal/scripts/network/traefik/traefik_port_subdomains.sh
librelad 4ee231ae9f refactor(de-sudo): wireguard -> runSystem, traefik -> runFileOp
Wireguard standalone touches /etc/wireguard + sysctl exclusively (genuine
root) -> runSystem for all its mkdir/chmod/sed/rm/grep/tee/qrencode. Traefik
dynamic configs live under containers/traefik (docker-install-owned) ->
runFileOp/runFileWrite (whitelist.yml, protectionauth.yml, the router-rewrite
awk|tee|mv in port_subdomains). sudo -u drops left.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-24 17:37:14 +01:00

97 lines
3.6 KiB
Bash

#!/bin/bash
# Per-port Traefik subdomain/host writer.
#
# Companion to traefik_port_middlewares.sh. Where that stamps a per-port
# MIDDLEWARE_TAG_<i>, this stamps a per-port DOMAINSUBNAME_TAG_<i> holding the
# fully-qualified host for that port's Traefik router, derived from the port's
# `subdomain` column (the 11th field of the 12-col PORT format).
#
# A single container can expose any number of hosts — one per Traefik-managed port.
#
# subdomain "" | "@" | "root" -> <domain> (apex / root of domain)
# subdomain "vault" -> vault.<domain> (single label)
# subdomain "admin.headscale" -> admin.headscale.<domain> (multi-level allowed)
#
# Index scheme matches traefik_port_middlewares.sh exactly: i walks every
# parsed port (0-based), idx = i+1 is the 1-based position used in the tag
# suffix, and only Traefik-managed ports (traefik=true) are stamped — so a
# port's MIDDLEWARE_TAG_<idx> and DOMAINSUBNAME_TAG_<idx> always line up on the
# same router.
tagsProcessorPortSubdomains()
{
local file="$1"
local app_name="$2"
if [[ -z "$file" || -z "$app_name" ]]; then
return 0
fi
local count=${#port_service_names[@]}
local i=0
while [[ $i -lt $count ]]; do
local idx=$((i + 1))
local p_traefik="${port_traefik_managed[$i]}"
local p_sub="${port_subdomains[$i]}"
if [[ "$p_traefik" == "true" ]]; then
local host
if [[ "$p_sub" == "@" || "$p_sub" == "root" ]]; then
host="${domain_full}" # apex / root of the domain (explicit only)
elif [[ -n "$p_sub" ]]; then
host="${p_sub}.${domain_full}" # subdomain (single or multi-level)
else
host="${app_name}.${domain_full}" # no subdomain set -> app-name default
fi
tagsManagerUpdateUniversalTag "$file" "DOMAINSUBNAME_TAG_${idx}" "$host"
fi
i=$((i + 1))
done
}
# Comments out the Traefik router block for any port that is NOT Traefik-managed
# (non-public, metrics-only, or a slot with no router at all), so an unfilled
# DOMAINSUBNAME_DATA_<n> placeholder can never reach a live compose. Each router
# is wrapped in # TRAEFIK_PORT_<n>_BEGIN ... # TRAEFIK_PORT_<n>_END markers;
# this mirrors the GLUETUN_OFF region-commenting in tags_processor_network_mode.sh.
tagsProcessorPortRouterBlocks()
{
local file="$1"
local app_name="$2"
if [[ -z "$file" || ! -f "$file" ]]; then
return 0
fi
# Space-padded list of 1-based indices of Traefik-managed ports, e.g. " 1 3 ".
local active=" "
local count=${#port_service_names[@]}
local i=0
while [[ $i -lt $count ]]; do
[[ "${port_traefik_managed[$i]}" == "true" ]] && active+="$((i + 1)) "
i=$((i + 1))
done
local tmp="${file}.routers.$$"
runFileOp awk -v active="$active" '
BEGIN { off = 0 }
/#[[:space:]]*TRAEFIK_PORT_[0-9]+_BEGIN/ {
match($0, /TRAEFIK_PORT_[0-9]+/); key = substr($0, RSTART, RLENGTH)
sub(/TRAEFIK_PORT_/, "", key)
off = (index(active, " " key " ") > 0) ? 0 : 1 # off=1 -> comment the block
print; next
}
/#[[:space:]]*TRAEFIK_PORT_[0-9]+_END/ { off = 0; print; next }
{
if (off == 1 && $0 !~ /^[[:space:]]*#/) {
match($0, /^[[:space:]]*/)
indent = substr($0, RSTART, RLENGTH)
rest = substr($0, RLENGTH + 1)
print indent "# " rest
next
}
print
}
' "$file" | runFileWrite "$tmp" >/dev/null
runFileOp mv "$tmp" "$file"
}