#!/bin/bash # ============================================================================================== # ================================= Docker Network Connect ===================================== # ============================================================================================== # Ensures custom Docker networks exist then connects specified containers to them. # Run once at array start via ARRAY_START_SCRIPTS — idempotent, safe to re-run anytime. # # ── WHAT IT DOES ────────────────────────────────────────────────────────────────────────────── # For each network in NETWORK_CONNECT_NETWORKS: # 1. Check if network exists # → missing → create it (bridge driver, Docker assigns subnet automatically) # notifies on creation — unexpected, usually means unRAID wiped networks # → exists → skip creation silently # 2. Connect each container in NETWORK_CONNECT_CONTAINERS to the network # → already connected → skip cleanly # → not connected → connect it # → container not found → warn and skip (not an error — may not be running yet) # # ── USE CASE ────────────────────────────────────────────────────────────────────────────────── # high-availability is the main custom network — shared by most containers. # After a unRAID update wipes custom networks → recreated automatically at next array start. # Containers on their own networks (NextCloud AIO etc.) can be added to # NETWORK_CONNECT_CONTAINERS so they also join high-availability without touching their # primary network configuration. # # ── SAFEGUARDS ──────────────────────────────────────────────────────────────────────────────── # Docker daemon check — verifies daemon is responsive before any network operations # Command validation — validates unRAID notify script before use # Timeout protection — all docker commands wrapped in timeout — daemon hangs cannot stall # Empty array guards — warns and exits cleanly if arrays are unconfigured # Silent by default — only warnings and errors produce output (v3.4 standard) # network creation always warns — unexpected, means networks were wiped # Idempotent — safe to run multiple times, skips what is already correct # # ── CONFIGURATION (master_host*.conf) ───────────────────────────────────────────────────────── # HOST*_NETWORK_CONNECT_NETWORKS — networks to ensure exist # HOST*_NETWORK_CONNECT_CONTAINERS — containers to connect to each network # Aliased by detect_hosts() — script uses unprefixed names # # ── USAGE ───────────────────────────────────────────────────────────────────────────────────── # docker_network_connect.sh — normal run # docker_network_connect.sh --dry-run — preview without making changes # docker_network_connect.sh --log — verbose output # docker_network_connect.sh --status — show current network state and exit # ============================================================================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../load_config.sh" parse_args "$@" # ============================================================================================== # ━━━ Setup ━━━ # ============================================================================================== if [[ "$EUID" -ne 0 ]]; then error "Must be run as root" exit 1 fi acquire_lock if ! command -v docker &>/dev/null; then error "Docker not found — cannot manage networks" exit 1 fi # detect_hosts() sets MY_ID and aliases HOST*_NETWORK_CONNECT_* arrays detect_hosts # Validate unRAID notify script — used for network creation alerts validate_unraid_cmd "/usr/local/emhttp/plugins/dynamix/scripts/notify" "" "" "unRAID notify script" || warn "unRAID notify script not found — native notifications disabled" # Docker daemon check — network operations are useless if daemon is hung DOCKER_TIMEOUT=15 if ! timeout "$DOCKER_TIMEOUT" docker info >/dev/null 2>&1; then error "Docker daemon not responding — cannot manage networks" notify "docker_network_connect failed on $(hostname) — Docker daemon not responding" "Network Connect" "warning" exit 1 fi # Empty array guards if [[ ${#NETWORK_CONNECT_NETWORKS[@]} -eq 0 ]]; then warn "NETWORK_CONNECT_NETWORKS is empty for $MY_ID — nothing to do" warn "Check HOST*_NETWORK_CONNECT_NETWORKS in master_host*.conf" exit 0 fi if [[ ${#NETWORK_CONNECT_CONTAINERS[@]} -eq 0 ]]; then warn "NETWORK_CONNECT_CONTAINERS is empty for $MY_ID — no containers to connect" warn "Check HOST*_NETWORK_CONNECT_CONTAINERS in master_host*.conf" exit 0 fi # ============================================================================================== # ━━━ Status ━━━ # ============================================================================================== if [[ "$SHOW_STATUS" == true ]]; then echo "" echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_DOCKER_NET Networks: ${NETWORK_CONNECT_NETWORKS[*]}" echo "$ICON_CONTAINERS Containers: ${NETWORK_CONNECT_CONTAINERS[*]}" echo "" echo "━━━ Network State ━━━" for network in "${NETWORK_CONNECT_NETWORKS[@]}"; do [[ -z "$network" ]] && continue if timeout "$DOCKER_TIMEOUT" docker network inspect "$network" &>/dev/null; then echo " $ICON_SUCCESS $network — exists" timeout "$DOCKER_TIMEOUT" docker network inspect "$network" \ --format ' Subnet: {{range .IPAM.Config}}{{.Subnet}}{{end}}' 2>/dev/null else echo " $ICON_ERROR $network — missing (will be created on next run)" fi done echo "" echo "━━━ Container Connections ━━━" for container in "${NETWORK_CONNECT_CONTAINERS[@]}"; do [[ -z "$container" ]] && continue echo "$ICON_CONTAINERS $container:" timeout "$DOCKER_TIMEOUT" docker inspect "$container" \ --format '{{range $k, $v := .NetworkSettings.Networks}} {{$k}}{{"\\n"}}{{end}}' \ 2>/dev/null || echo " not found" done echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi [[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made" # ============================================================================================== # ━━━ Network Ensure + Connect ━━━ # ============================================================================================== log "Networks: ${NETWORK_CONNECT_NETWORKS[*]}" log "Containers: ${NETWORK_CONNECT_CONTAINERS[*]}" START=$(date +%s) NETWORKS_CREATED=() CONNECTED=() SKIPPED=() FAILED=() for network in "${NETWORK_CONNECT_NETWORKS[@]}"; do [[ -z "$network" ]] && continue log "Processing network: $network" # ── Step 1 — ensure network exists ─────────────────────────────────────────────────────── if timeout "$DOCKER_TIMEOUT" docker network inspect "$network" &>/dev/null; then log "$network exists ✅" else warn "$ICON_DOCKER_NET $network not found — creating (unRAID update may have wiped networks)" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would create: $network (bridge, auto subnet)" else if timeout "$DOCKER_TIMEOUT" docker network create \ --driver bridge "$network" >/dev/null 2>&1; then warn "$ICON_DOCKER_NET $network created" NETWORKS_CREATED+=("$network") # Network creation is unexpected — notify so user is aware notify "Docker network created on $(hostname) — $network (unRAID update likely wiped it)" \ "Network Connect" "normal" else error "$network — failed to create" FAILED+=("$network:create") continue fi fi fi # ── Step 2 — connect containers to this network ────────────────────────────────────────── for container in "${NETWORK_CONNECT_CONTAINERS[@]}"; do [[ -z "$container" ]] && continue # Container not found — warn and skip, not an error if ! timeout "$DOCKER_TIMEOUT" docker inspect "$container" &>/dev/null; then warn "$container not found — skipping (may not be running yet)" continue fi # Already connected — skip cleanly and silently if timeout "$DOCKER_TIMEOUT" docker network inspect "$network" \ --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null \ | grep -qw "$container"; then log "$container already on $network — skipping" SKIPPED+=("$container→$network") continue fi # Connect if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would connect $container to $network" continue fi log "Connecting $container to $network..." if timeout "$DOCKER_TIMEOUT" docker network connect "$network" "$container" 2>/dev/null; then log "$container connected to $network" CONNECTED+=("$container→$network") else error "Failed to connect $container to $network" FAILED+=("$container:$network") fi done done END=$(date +%s) # ============================================================================================== # ━━━ Summary ━━━ # ============================================================================================== # Always show summary — this runs at array start and output is useful for diagnostics echo "" echo "━━━━━ $ICON_SUMMARY NETWORK CONNECT SUMMARY ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_TIME Duration: $(format_duration $((END - START)))" [[ ${#NETWORKS_CREATED[@]} -gt 0 ]] && warn "$ICON_DOCKER_NET Created: ${NETWORKS_CREATED[*]} (networks were missing)" [[ ${#CONNECTED[@]} -gt 0 ]] && log "Connected: ${CONNECTED[*]}" [[ ${#SKIPPED[@]} -gt 0 ]] && log "Already connected: ${#SKIPPED[@]} skipped" [[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${FAILED[*]}" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — no changes made" elif [[ ${#FAILED[@]} -gt 0 ]]; then echo "$ICON_ERROR Status: SOME OPERATIONS FAILED" notify "Docker network connect failed on $(hostname) — ${FAILED[*]}" "Network Connect" "warning" elif [[ ${#NETWORKS_CREATED[@]} -gt 0 ]]; then warn "Networks recreated — ${NETWORKS_CREATED[*]} — unRAID update likely wiped them" else log "All networks healthy — no action needed" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" [[ ${#FAILED[@]} -gt 0 ]] && exit 1 exit 0