#!/bin/bash # Network-drift heal — the mutating half of the detector. Runs ONLY through the # task system (see cli_system_commands.sh `network heal`, which enqueues unless # LIBREPORTAL_TASK_EXEC=1), never a direct API. # # It (1) realigns CFG to docker's real subnet IN-PROCESS so re-IP draws from the # corrected /24, then (2) re-IPs each stranded app with ports PRESERVED # (reset_network="ip"), healing a gateway provider (gluetun) first so recreating # it doesn't orphan the apps routed through it. Re-IP runs sequentially, and a # fresh scan afterwards rewrites network_status.json so the WebUI badge clears # (or stays, if anything failed to heal). # # networkHealConflicts [app] # heal one app, or all detected conflicts networkHealConflicts() { local target_app="$1" isHeader "Healing network drift" # 1) Realign CFG to docker's real subnet in this process (ipFindAvailable # reads CFG_NETWORK_SUBNET, so this must happen before any re-IP). local docker_subnet docker_subnet=$(dockerCommandRun "docker network inspect $CFG_NETWORK_NAME --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'" 2>/dev/null | tr -d '[:space:]') if [[ -z "$docker_subnet" ]]; then isNotice "Network '$CFG_NETWORK_NAME' not present — (re)creating it." DOCKER_NETWORK_SETUP_NEEDED="true" declare -f installDockerNetwork >/dev/null 2>&1 && installDockerNetwork docker_subnet=$(dockerCommandRun "docker network inspect $CFG_NETWORK_NAME --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'" 2>/dev/null | tr -d '[:space:]') fi if [[ -n "$docker_subnet" && "$docker_subnet" != "$CFG_NETWORK_SUBNET" ]]; then declare -f adoptDockerSubnet >/dev/null 2>&1 && adoptDockerSubnet "$docker_subnet" fi # 2) Build the app set (unique). Either the requested app, or a fresh scan. local -a apps=() a if [[ -n "$target_app" ]]; then apps=("$target_app") else networkScanConflicts # populates NET_CONFLICTS (call direct, not $(...)) local row seen="" for row in "${NET_CONFLICTS[@]}"; do a="${row%%|*}" [[ -n "$a" ]] || continue # de-dupe app names (an app can have several drifted services) [[ " $seen " == *" $a "* ]] && continue seen+=" $a" apps+=("$a") done fi if [[ ${#apps[@]} -eq 0 ]]; then isSuccessful "No network conflicts to heal." declare -f webuiSystemNetworkCheck >/dev/null 2>&1 && webuiSystemNetworkCheck "force" >/dev/null 2>&1 return 0 fi # 3) Heal a gateway PROVIDER first — recreating gluetun re-attaches every app # routed through it, so it must settle before (or be reconciled after) them. local -a ordered=() for a in "${apps[@]}"; do [[ "$a" == "gluetun" ]] && ordered+=("$a"); done for a in "${apps[@]}"; do [[ "$a" != "gluetun" ]] && ordered+=("$a"); done # 4) Re-IP each (IP-only — ports preserved), sequentially. local attempted=0 for a in "${ordered[@]}"; do if [[ ! "$a" =~ ^[a-z0-9][a-z0-9_-]*$ ]]; then isError "Skipping invalid app slug: $a"; continue fi isNotice "Re-IPing '$a' into ${CFG_NETWORK_SUBNET} (ports preserved)…" dockerInstallApp "$a" "" ip ((attempted++)) done # 5) Reconcile gateway-routed apps onto the (possibly recreated) provider. declare -f appGluetunRecreateRouted >/dev/null 2>&1 && appGluetunRecreateRouted >/dev/null 2>&1 || true # 6) Fresh scan rewrites the status file; report what (if anything) remains. if declare -f webuiSystemNetworkCheck >/dev/null 2>&1; then webuiSystemNetworkCheck "force" >/dev/null 2>&1 fi networkScanConflicts local remaining=${#NET_CONFLICTS[@]} if (( remaining > 0 )); then isError "Network heal attempted ${attempted} app(s); ${remaining} conflict(s) still detected — re-run or inspect manually." return 1 fi isSuccessful "Network heal complete — re-IP'd ${attempted} app(s); no conflicts remain." }