From 23825a824dbf2c9f901de22b37dc37175b0e6b58 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sun, 16 Aug 2026 20:50:10 -0400 Subject: [PATCH] Create the networks our own pushed templates name, instead of trusting a fresh mirror to have uncommented them --- Partnership/partnership_onboard.sh | 15 +++++ Plugin/unraid/Partnership/containers.sh | 81 +++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/Partnership/partnership_onboard.sh b/Partnership/partnership_onboard.sh index ebf9e53..52b988a 100755 --- a/Partnership/partnership_onboard.sh +++ b/Partnership/partnership_onboard.sh @@ -639,6 +639,21 @@ fi echo "" echo "━━━ Step 1b — Docker Network (Mirror) ━━━" +# Two halves, and the first is the one that matters for a fresh mirror. +# +# ensure_stack_networks_on_remote reads the networks out of the XMLs this onboard is about to +# push and creates any that are missing on the mirror. It does not consult the mirror's conf, +# because on a fresh node that array is the template default — a single commented-out entry — +# and an empty list is indistinguishable from "no networks needed". The result was every +# container in both stacks created against a network that did not exist. +# +# docker_network_connect.sh still runs afterwards: it is what *connects* the mirror's own +# listed containers to its own listed networks, which is a different job and remains the +# mirror's to declare. +if ! ensure_stack_networks_on_remote "$MIRROR_IP" "$MIRROR_SSH_KEY"; then + warn "One or more stack networks could not be prepared on $MIRROR — deploys below may fail" +fi + _net_script="${SCRIPTS_ROOT}/Docker_Essentials/docker_network_connect.sh" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would run docker_network_connect.sh on $MIRROR" diff --git a/Plugin/unraid/Partnership/containers.sh b/Plugin/unraid/Partnership/containers.sh index f18a077..caac801 100755 --- a/Plugin/unraid/Partnership/containers.sh +++ b/Plugin/unraid/Partnership/containers.sh @@ -416,6 +416,87 @@ deploy_xml_stack() { done } +# ============================================================================================== +# ── Ensure the networks our pushed templates reference exist on a remote host ───────────────── +# +# The owner deploys the mirror's containers from the owner's own XMLs, and those XMLs name a +# network. `docker create` fails outright if that network is missing, so the network has to +# exist on the mirror before any stack is deployed. +# +# This used to be left entirely to docker_network_connect.sh running on the mirror, which +# iterates the *mirror's* NETWORK_CONNECT_NETWORKS. host.conf.template ships that array with its +# only entry commented out, so on a fresh node it is empty — nothing was created, and every +# container in both stacks was created against a network that did not exist and could never +# start. Twelve containers stuck in `Created`, reported as "0 deployed, 8 failed" and +# "0 deployed, 5 failed" as though each container had its own problem. +# +# The owner knows what it is about to push, so it derives the requirement from the templates +# rather than trusting the mirror's conf to have been filled in. +# +# Only bridge networks are created. br0 and friends are ipvlan/macvlan bound to real host +# hardware — the parent interface cannot be inferred from here, and guessing one would attach +# the mirror's containers to the wrong segment. +# +# Usage: ensure_stack_networks_on_remote "$MIRROR_IP" "$MIRROR_SSH_KEY" +# ============================================================================================== +ensure_stack_networks_on_remote() { + local remote_ip="$1" ssh_key="$2" + local -a xml_names=() nets=() + [[ ${#PARTNERSHIP_AUTH_STACK[@]} -gt 0 ]] && xml_names+=("${PARTNERSHIP_AUTH_STACK[@]}") + [[ ${#PARTNERSHIP_ARR_STACK[@]} -gt 0 ]] && xml_names+=("${PARTNERSHIP_ARR_STACK[@]}") + [[ ${#PARTNERSHIP_SERVICES_STACK[@]} -gt 0 ]] && xml_names+=("${PARTNERSHIP_SERVICES_STACK[@]}") + + local xml_name xml_file net n seen + for xml_name in "${xml_names[@]}"; do + [[ -z "$xml_name" ]] && continue + xml_file="${TEMPLATES_DIR}/${xml_name}" + [[ -f "$xml_file" ]] || continue + net=$(sed -n 's/.*\([^<]*\)<\/Network>.*/\1/p' "$xml_file" 2>/dev/null | head -1) + net="${net//[[:space:]]/}" + # Built-ins exist on every host; br* is host hardware, handled above. + case "$net" in ''|bridge|host|none|br[0-9]*) continue ;; esac + seen=false + for n in "${nets[@]}"; do [[ "$n" == "$net" ]] && { seen=true; break; }; done + [[ "$seen" == false ]] && nets+=("$net") + done + + if [[ ${#nets[@]} -eq 0 ]]; then + log "No custom networks referenced by the pushed templates" + return 0 + fi + + local rc=0 driver + for net in "${nets[@]}"; do + driver=$(timeout "${DOCKER_TIMEOUT:-30}" docker network inspect "$net" \ + --format '{{.Driver}}' 2>/dev/null) + if [[ -z "$driver" ]]; then + warn "$net is referenced by a pushed template but does not exist here either — skipping" + rc=1 + continue + fi + if [[ "$driver" != "bridge" ]]; then + warn "$net is $driver here, not bridge — create it on $MIRROR by hand, its parent interface is host-specific" + rc=1 + continue + fi + if [[ "$DRY_RUN" == true ]]; then + warn "DRY RUN — would ensure network '$net' (bridge) exists on $MIRROR" + continue + fi + if timeout "$SSH_TIMEOUT" ssh -i "$ssh_key" \ + -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes -o StrictHostKeyChecking=no \ + root@"$remote_ip" \ + "docker network inspect $(printf '%q' "$net") >/dev/null 2>&1 \ + || docker network create --driver bridge $(printf '%q' "$net") >/dev/null" 2>/dev/null; then + echo " network $net (bridge) ready on $MIRROR ✅" + else + warn " could not ensure network $net on $MIRROR — its containers will not start" + rc=1 + fi + done + return "$rc" +} + # ============================================================================================== # ── Remove owner-deployed containers from a remote host ────────────────────────────────────── #