File the owner's containers on the mirror during onboard, with the icon resolved where the Emby key is
This commit is contained in:
@@ -42,6 +42,8 @@
|
||||
# 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 "<OwnerShort>-Fallback",
|
||||
# icon resolved here and passed over: the mirror has no Emby key
|
||||
#
|
||||
# ==============================================================================================
|
||||
# DESIGN PRINCIPLES
|
||||
@@ -1047,6 +1049,56 @@ else
|
||||
unset _mirror_sd _pop_script
|
||||
fi
|
||||
|
||||
# ── Step 12: Group our containers on the mirror ───────────────────────────────────────────────
|
||||
# The mirror now runs a dozen containers that are ours, scattered among its own. This files them
|
||||
# under one folder named after us — "<OwnerShort>-Fallback" — matching the convention the owner
|
||||
# already keeps for the mirror's containers.
|
||||
#
|
||||
# The icon is resolved HERE and passed over, not looked up there. It comes from the closest Emby
|
||||
# user to our own name, and the mirror has neither our Emby key nor necessarily an Emby at all —
|
||||
# so a lookup on that side would find nothing and the folder would come up blank.
|
||||
#
|
||||
# Not fatal in any direction: folder.view3 absent on the mirror is a clean skip, and a folder
|
||||
# without a picture is still a folder.
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Step 12 — Container Grouping ($MIRROR) ━━━"
|
||||
|
||||
FOLDER_OK=false
|
||||
if [[ -z "${MIRROR_IP:-}" ]]; then
|
||||
warn "$MIRROR has no resolved IP — skipping container grouping"
|
||||
FOLDER_OK=skipped
|
||||
else
|
||||
mapfile -t _deployed < <(deployed_stack_container_names)
|
||||
_deployed_csv=$(IFS=,; echo "${_deployed[*]}")
|
||||
if [[ -z "$_deployed_csv" ]]; then
|
||||
log "No stack templates resolved to container names — nothing to group"
|
||||
FOLDER_OK=skipped
|
||||
elif [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would create ${MY_ID}-named fallback folder on $MIRROR with: $_deployed_csv"
|
||||
FOLDER_OK=true
|
||||
else
|
||||
_ff_local="$SCRIPTS_ROOT/Plugin/$PLATFORM/Tools/fallback_folder.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" \
|
||||
-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") \
|
||||
--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
|
||||
fi
|
||||
unset _deployed _deployed_csv
|
||||
fi
|
||||
|
||||
# ── Write Phase 2 completion state ────────────────────────────────────────────────────────────
|
||||
[[ "$ONBOARD_OK" == true && "$DRY_RUN" == false ]] && write_onboard_phase "$MIRROR_ID" 2
|
||||
|
||||
@@ -1080,6 +1132,7 @@ echo " Step 9d — Media seed: $( [[ "$SKIP_MEDIA_SEED" == true ]] && ech
|
||||
echo " Step 9e — Webhook listener: $(_skip "$SKIP_WEBHOOK_LISTENER" "$WEBHOOK_LISTENER_OK")"
|
||||
echo " Step 10 — Conf push: $( [[ "$ONBOARD_OK" == false ]] && echo "skipped" || echo "$(_ok "$MASTER_PUSH_OK")" )"
|
||||
echo " Step 11 — Discovery: $( [[ "$POPULATE_OK" == skipped ]] && echo "skipped (unreachable)" || _ok "$POPULATE_OK" )"
|
||||
echo " Step 12 — Grouping: $( [[ "$FOLDER_OK" == skipped ]] && echo "skipped" || _ok "$FOLDER_OK" )"
|
||||
echo ""
|
||||
|
||||
if [[ "$ONBOARD_OK" == true ]]; then
|
||||
|
||||
@@ -454,6 +454,31 @@ deploy_xml_stack() {
|
||||
#
|
||||
# Usage: ensure_stack_networks_on_remote "$MIRROR_IP" "$MIRROR_SSH_KEY"
|
||||
# ==============================================================================================
|
||||
# ==============================================================================================
|
||||
# ── Container names this onboard deploys, read from the templates it deploys them from ────────
|
||||
#
|
||||
# Echoes one name per line. The <Name> element is the same value deploy_xml_stack() passes to
|
||||
# `docker create --name`, so this is the deployed set by construction rather than by asking the
|
||||
# mirror what it ended up with — which would also pick up whatever the mirror already ran.
|
||||
#
|
||||
# Usage: mapfile -t names < <(deployed_stack_container_names)
|
||||
# ==============================================================================================
|
||||
deployed_stack_container_names() {
|
||||
local -a xml_names=()
|
||||
[[ ${#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 cname
|
||||
for xml_name in "${xml_names[@]}"; do
|
||||
[[ -z "$xml_name" ]] && continue
|
||||
xml_file="${TEMPLATES_DIR}/${xml_name}"
|
||||
[[ -f "$xml_file" ]] || continue
|
||||
cname=$(awk 'match($0,/<Name>([^<]+)<\/Name>/,a){print a[1];exit}' "$xml_file")
|
||||
[[ -n "$cname" ]] && echo "$cname"
|
||||
done
|
||||
}
|
||||
|
||||
ensure_stack_networks_on_remote() {
|
||||
local remote_ip="$1" ssh_key="$2"
|
||||
local -a xml_names=() nets=()
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
// Icon resolution failing never blocks the folder: no image is a cosmetic loss, no folder is not.
|
||||
//
|
||||
// REQUEST
|
||||
// fallback_folder.php --host=HOST2 [--containers=A,B,C] [--dry-run]
|
||||
// fallback_folder.php --host=HOST2 [--containers=A,B,C] [--icon=URL] [--dry-run]
|
||||
// fallback_folder.php --host=HOST2 --icon-only resolve and print the icon URL, write nothing
|
||||
//
|
||||
// CONFIGURATION
|
||||
// HOST1/HOST2… master.conf — the hostname the folder is named after
|
||||
@@ -57,9 +58,10 @@ require_once $pluginDir . '/include/config.php';
|
||||
define('FV3_JSON', '/boot/config/plugins/folder.view3/docker.json');
|
||||
|
||||
// ── Args ──────────────────────────────────────────────────────────────────────
|
||||
$opts = ['host' => '', 'containers' => '', 'dry-run' => false];
|
||||
$opts = ['host' => '', 'containers' => '', 'icon' => '', 'dry-run' => false, 'icon-only' => false];
|
||||
foreach (array_slice($argv, 1) as $a) {
|
||||
if ($a === '--dry-run') { $opts['dry-run'] = true; continue; }
|
||||
if ($a === '--dry-run') { $opts['dry-run'] = true; continue; }
|
||||
if ($a === '--icon-only'){ $opts['icon-only'] = true; continue; }
|
||||
if (preg_match('/^--([a-z-]+)=(.*)$/', $a, $m)) $opts[$m[1]] = $m[2];
|
||||
}
|
||||
$hostId = strtoupper(trim($opts['host']));
|
||||
@@ -72,6 +74,7 @@ $vars = vv_conf_vars();
|
||||
$hostname = trim($vars[$hostId] ?? '');
|
||||
if ($hostname === '') { fwrite(STDERR, "$hostId is not set in master.conf\n"); exit(1); }
|
||||
|
||||
|
||||
// derive_short_name() in common.sh: lowercase, strip a leading "unraid-", capitalise.
|
||||
$short = preg_replace('/^unraid-/i', '', $hostname);
|
||||
$short = ucfirst($short);
|
||||
@@ -124,9 +127,40 @@ function vv_closest_emby_icon(string $short, array $vars, string $meId): array {
|
||||
}
|
||||
|
||||
$meId = strtoupper(vv_detect_host());
|
||||
[$icon, $iconNote] = vv_closest_emby_icon($short, $vars, $meId);
|
||||
|
||||
// --icon= skips the lookup entirely. The mirror needs this: it is being given a folder named
|
||||
// after the OWNER, and the avatar lives in the owner's Emby — which the mirror has no key for and
|
||||
// may not run at all. So the owner resolves the URL with --icon-only and hands it over, rather
|
||||
// than the mirror guessing from a userbase it cannot see.
|
||||
if (trim($opts['icon']) !== '') {
|
||||
$icon = trim($opts['icon']);
|
||||
$iconNote = 'supplied by caller';
|
||||
} else {
|
||||
[$icon, $iconNote] = vv_closest_emby_icon($short, $vars, $meId);
|
||||
}
|
||||
|
||||
// --icon-only: resolve and print, touch nothing. Exits non-zero when there is no icon, so a
|
||||
// caller can tell "no image" from "empty string because something broke".
|
||||
if ($opts['icon-only']) {
|
||||
if ($icon === '') { fwrite(STDERR, "no icon: $iconNote\n"); exit(1); }
|
||||
echo $icon . "\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// ── Upsert the folder ─────────────────────────────────────────────────────────
|
||||
// A host does not keep a fallback folder for itself — the folder means "containers I run on
|
||||
// SOMEONE ELSE's behalf", so naming it after this machine is always wrong. Checked here rather
|
||||
// than at argument parsing, because --icon-only legitimately asks for THIS host's own avatar:
|
||||
// the owner resolves its own picture to hand to the mirror, which is the whole point of that mode.
|
||||
//
|
||||
// Refused rather than created, because the failure is otherwise silent — an empty folder named
|
||||
// after yourself looks plausible enough to survive a glance. One appeared on HOST1 exactly this
|
||||
// way, when an older copy of this script ignored an unrecognised flag and ran the upsert anyway.
|
||||
if (strcasecmp($hostId, strtoupper(vv_detect_host())) === 0) {
|
||||
fwrite(STDERR, "$hostId is this host — a fallback folder is named after the PARTNER, not self\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
if (!file_exists(FV3_JSON)) {
|
||||
echo "folder.view3 is not installed — nothing to do\n";
|
||||
exit(0);
|
||||
|
||||
Reference in New Issue
Block a user