87 lines
3.5 KiB
Bash
87 lines
3.5 KiB
Bash
#!/bin/sh
|
|
set -eu
|
|
|
|
SOURCE=${WG_CONFIG_SOURCE:-/run/secrets/fritz-athena.conf}
|
|
RUNTIME=/run/wireguard/wg0.conf
|
|
|
|
[ -s "$SOURCE" ] || { echo "WireGuard configuration is missing" >&2; exit 1; }
|
|
install -d -m 0700 /run/wireguard
|
|
|
|
# Fritzbox exports global DNS directives and wg-quick hooks. DNS is assigned
|
|
# per application container by Docker; executable hooks are deliberately not
|
|
# accepted from a secret file. All cryptographic values remain untouched.
|
|
awk '
|
|
/^[[:space:]]*(DNS|Table|PreUp|PostUp|PreDown|PostDown|SaveConfig)[[:space:]]*=/ { next }
|
|
/^\[Interface\][[:space:]]*$/ { print; print "Table = off"; next }
|
|
{ print }
|
|
' "$SOURCE" >"$RUNTIME"
|
|
chmod 0600 "$RUNTIME"
|
|
|
|
# Docker deliberately keeps /proc/sys read-only inside this narrowly
|
|
# privileged container. Table=off prevents wg-quick from trying to modify
|
|
# global policy-routing sysctls; the two required routes are installed below.
|
|
physical_default=$(ip -4 route show default | head -n 1)
|
|
physical_gateway=$(printf '%s\n' "$physical_default" | awk '{for (i=1; i<=NF; i++) if ($i == "via") print $(i+1)}')
|
|
physical_device=$(printf '%s\n' "$physical_default" | awk '{for (i=1; i<=NF; i++) if ($i == "dev") print $(i+1)}')
|
|
|
|
wg-quick up "$RUNTIME"
|
|
|
|
# Keep the encrypted peer itself reachable over Docker's physical network,
|
|
# then make the tunnel the namespace default. Connected Docker routes remain
|
|
# intact for the reverse proxies and internal service discovery.
|
|
endpoint=$(wg show wg0 endpoints | awk 'NR == 1 { print $2 }')
|
|
case "$endpoint" in
|
|
\[*\]:*) endpoint_ip=${endpoint#\[}; endpoint_ip=${endpoint_ip%%\]*} ;;
|
|
*:*) endpoint_ip=${endpoint%:*} ;;
|
|
*) endpoint_ip= ;;
|
|
esac
|
|
|
|
if [ -n "$endpoint_ip" ] && [ -n "$physical_gateway" ] && [ -n "$physical_device" ]; then
|
|
case "$endpoint_ip" in
|
|
*:*) : ;; # Docker gateway networks are intentionally IPv4-only.
|
|
*) ip -4 route replace "$endpoint_ip/32" via "$physical_gateway" dev "$physical_device" ;;
|
|
esac
|
|
fi
|
|
ip -4 route replace default dev wg0
|
|
ip -6 route replace default dev wg0 2>/dev/null || true
|
|
|
|
cleanup() {
|
|
[ -z "${proxy_pids:-}" ] || kill $proxy_pids 2>/dev/null || true
|
|
wg-quick down "$RUNTIME" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# Forward only explicitly selected Docker networks into the tunnel. The
|
|
# gateway itself is the only container that receives NET_ADMIN.
|
|
iptables -P FORWARD DROP
|
|
iptables -A FORWARD -o wg0 -j ACCEPT
|
|
iptables -A FORWARD -i wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
|
|
|
|
wg_ipv4=$(ip -4 -o address show dev wg0 | awk 'NR == 1 { split($4, address, "/"); print address[1] }')
|
|
[ -n "$wg_ipv4" ] || { echo "WireGuard IPv4 address is missing" >&2; exit 1; }
|
|
|
|
# The VPN is Athena's normal application network. Nothing below is published
|
|
# on the physical university interface: every listener is bound inside this
|
|
# namespace to the Fritzbox-assigned WireGuard address. Clients on the home
|
|
# VPN clients may use the router, speech services and Athena operator directly.
|
|
proxy_pids=""
|
|
start_proxy() {
|
|
listen_port=$1
|
|
target=$2
|
|
socat "TCP-LISTEN:${listen_port},bind=${wg_ipv4},reuseaddr,fork" "TCP:${target}" &
|
|
proxy_pids="$proxy_pids $!"
|
|
}
|
|
|
|
start_proxy 22 172.30.10.1:22
|
|
start_proxy 8081 router:8081
|
|
start_proxy 8085 tts-gateway:8085
|
|
start_proxy 8091 piper:8085
|
|
start_proxy 8099 llama-dashboard:8099
|
|
start_proxy 7861 music-ui:3000
|
|
start_proxy 7862 music-worker:7860
|
|
start_proxy 8202 mcp-athena-operator:8000
|
|
start_proxy 9443 portainer:9443
|
|
|
|
wait $(printf '%s\n' "$proxy_pids" | awk '{print $2}')
|