diff --git a/Deployment/host.conf.template b/Deployment/host.conf.template index 6d5cbb4..af55b51 100644 --- a/Deployment/host.conf.template +++ b/Deployment/host.conf.template @@ -165,6 +165,13 @@ "/mnt/user/appdata-Fallback/Critical-Data" # critical-data profile — the auth stack ) +# Containers that belong in "-Fallback" on the mirror rather than in a mirrored +# copy of this host's folder layout — the ones that exist there only to cover this host going +# dark. Everything else the onboard deploys is filed onto the same shelf it occupies here +# (Arrs Stack, Networking, Databases…), because it runs on the mirror continuously. +# Empty is the normal state: leave it empty unless a container is genuinely failover-only. + HOSTN_PARTNERSHIP_FALLBACK_ONLY=() + # XML templates pushed to mirror during onboard — auth stack. # Dependencies (databases) must come before apps that depend on them. HOSTN_PARTNERSHIP_AUTH_STACK=( diff --git a/Partnership/partnership_onboard.sh b/Partnership/partnership_onboard.sh index d3675f4..63bc3e3 100755 --- a/Partnership/partnership_onboard.sh +++ b/Partnership/partnership_onboard.sh @@ -42,8 +42,11 @@ # Step 10: Conf push — push master.conf + setup state to all listed hosts # Step 11: Service discovery — conf_populate.sh on the mirror, last, once the stacks it # would discover are actually deployed there -# Step 12: Container grouping — file our containers on the mirror under "-Fallback", -# icon resolved here and passed over: the mirror has no Emby key +# Step 12: Container grouping — reproduce this host's folder layout on the mirror (Arrs +# Stack, Networking, Databases…) for the containers deployed +# there; only unfiled or PARTNERSHIP_FALLBACK_ONLY entries go +# to "-Fallback". Icon resolved here and passed +# over: the mirror has no Emby key # # Phase 3 (media seed) is NOT part of the above. Onboard ends at Phase 2 — connected, running, # and saying so. Seeding is a separate operator-triggered phase; see --phase3-only. With @@ -1268,23 +1271,63 @@ else FOLDER_OK=true else _ff_local="$SCRIPTS_ROOT/Plugin/$PLATFORM/Tools/fallback_folder.php" + _mf_local="$SCRIPTS_ROOT/Plugin/$PLATFORM/Tools/mirror_folders.php" _icon=$(php "$_ff_local" --host="$MY_ID" --icon-only 2>/dev/null || true) [[ -z "$_icon" ]] && log "No icon resolved for $MY_ID — folder will be created without one" _mirror_sd=$(resolve_remote_scripts_dir "$MIRROR_IP" "$MIRROR_SSH_KEY" "no") _ff_remote="${_mirror_sd}/Plugin/${PLATFORM}/Tools/fallback_folder.php" - if timeout 60 ssh -i "$MIRROR_SSH_KEY" \ + _mf_remote="${_mirror_sd}/Plugin/${PLATFORM}/Tools/mirror_folders.php" + + # ── 12a: reproduce our own folder layout on the mirror ──────────────────────────────── + # The deployed stacks run on the mirror continuously — they are not failover coverage — + # so they belong on the same shelves they occupy here: Sonarr in "Arrs Stack", NPM and + # Lldap in "Networking", the databases in "Databases". Filing all of them under + # "-Fallback", which is what this step used to do, records whose they are and + # nothing about what they do. + # + # The plan is computed here, where the owner's folder.view3 layout lives, and applied + # there. Whatever the layout does not account for comes back as "unfiled" and is what + # the fallback folder is actually for. + _unfiled="$_deployed_csv" + if [[ -f "$_mf_local" ]]; then + # Comma-joined: the flag takes a CSV, and "${arr[*]}" would join on spaces — which + # also happen to appear inside folder names, so the plan must never be reflowed. + _fbonly=$(IFS=,; printf '%s' "${PARTNERSHIP_FALLBACK_ONLY[*]:-}") + _plan=$(php "$_mf_local" --export --containers="$_deployed_csv" \ + --fallback-only="$_fbonly" 2>/dev/null) + if [[ -n "$_plan" ]]; then + if printf '%s' "$_plan" | timeout 60 ssh -i "$MIRROR_SSH_KEY" \ + -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes -o StrictHostKeyChecking=no \ + root@"$MIRROR_IP" \ + "[ -f '$_mf_remote' ] || exit 127; php '$_mf_remote' --import" 2>/dev/null; then + _unfiled=$(printf '%s' "$_plan" | php -r \ + 'echo implode(",", json_decode(stream_get_contents(STDIN),true)["unfiled"] ?? []);' 2>/dev/null) + else + warn "Could not mirror the folder layout to $MIRROR — falling back to one folder" + fi + fi + unset _plan _fbonly + fi + + # ── 12b: the fallback folder gets only what is genuinely fallback ───────────────────── + # Unfiled containers plus anything named in PARTNERSHIP_FALLBACK_ONLY. Empty is the + # normal, correct outcome when every deployed container has a home in the layout. + if [[ -z "$_unfiled" ]]; then + echo " Folder layout mirrored — nothing left for ${MY_ID}-Fallback ✅" + FOLDER_OK=true + elif timeout 60 ssh -i "$MIRROR_SSH_KEY" \ -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes -o StrictHostKeyChecking=no \ root@"$MIRROR_IP" \ "[ -f '$_ff_remote' ] || { echo missing; exit 127; } php '$_ff_remote' --host=$(printf '%q' "$MY_ID") \ - --containers=$(printf '%q' "$_deployed_csv") \ + --containers=$(printf '%q' "$_unfiled") \ --icon=$(printf '%q' "$_icon")" 2>/dev/null; then FOLDER_OK=true else warn "Could not group containers on $MIRROR — run $_ff_remote there by hand" fi - unset _ff_local _ff_remote _mirror_sd _icon + unset _ff_local _ff_remote _mf_local _mf_remote _mirror_sd _icon _unfiled fi unset _deployed _deployed_csv fi diff --git a/common.sh b/common.sh index 6621b34..70b5bfd 100755 --- a/common.sh +++ b/common.sh @@ -731,6 +731,7 @@ detect_hosts() { _alias_array "WEEKLY_SYNC_SHARES" _alias_array "CRITICAL_SYNC_SHARES" _alias_array "PARTNERSHIP_PROVISION_SHARES" + _alias_array "PARTNERSHIP_FALLBACK_ONLY" _alias_array "BACKUP_VERIFY_SHARES" _alias_array "DAILY_RESTART_CONTAINERS" _alias_array "WEEKLY_RESTART_CONTAINERS"