#!/bin/bash
# ==============================================================================================
# ============================= Conf Cache Sync ================================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Maintains a RAM-resident conf cache at /tmp/.vv/config/cached/.confs/.
# Credentials and partner keys live in RAM only — never on disk across hosts.
#
# On array start (default / --array-start):
#   1. Copy own conf to local cache
#   2. Pull each available partner's conf from their disk → local cache
#   3. Push own conf to each available partner's /tmp/.vv/ cache
#
# On conf save (--push-only):
#   Fast path — push updated own conf to all partners' /tmp/.vv/ cache only.
#   No pulls, no local cache rebuild.
#
# Cache is /tmp (tmpfs) — cleared every reboot, repopulated by this script
# on next array start. Scripts source from cache for partner vars; own vars
# always come from disk (load_config.sh skips cached copy of own conf).
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
#   conf_sync.sh                   Full sync: pull from all partners + push to all partners
#   conf_sync.sh --push-only       Push own conf to all partners (fast, for conf-save hook)
#   conf_sync.sh --pull-only       Pull partner confs into local cache only (for intermediate orch)
#   conf_sync.sh --dry-run         Show what would happen, no changes
#   conf_sync.sh --log             Verbose output
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../load_config.sh"

PUSH_ONLY=false
PULL_ONLY=false
FILTERED_ARGS=()
for arg in "$@"; do
    case "$arg" in
        --push-only) PUSH_ONLY=true ;;
        --pull-only) PULL_ONLY=true ;;
        *)           FILTERED_ARGS+=("$arg") ;;
    esac
done
parse_args "${FILTERED_ARGS[@]}"
detect_hosts

if [[ "${CONF_SYNC_ENABLED:-true}" == false ]]; then
    log "CONF_SYNC_ENABLED=false — skipping"
    exit 0
fi

CACHE_DIR="/tmp/.vv/config/cached/.confs"
MY_CONF="$SCRIPTS_ROOT/Configurations/${MY_ID,,}.conf"
SSH_TIMEOUT=10

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"

# ── Ensure cache dir exists ───────────────────────────────────────────────────
if [[ "$DRY_RUN" == false ]]; then
    mkdir -p "$CACHE_DIR"
fi

# ── Copy own conf into local cache ───────────────────────────────────────────
if [[ "$PUSH_ONLY" == false ]] && [[ "$PULL_ONLY" == false ]]; then
    if [[ -f "$MY_CONF" ]]; then
        if [[ "$DRY_RUN" == true ]]; then
            warn "DRY RUN — would copy $(basename "$MY_CONF") → $CACHE_DIR/"
        else
            cp "$MY_CONF" "$CACHE_DIR/${MY_ID,,}.conf" && \
                echo "Own conf cached ✅" || warn "Failed to cache own conf"
        fi
    else
        warn "Own conf not found: $MY_CONF"
    fi
fi

# ── Per-partner sync ──────────────────────────────────────────────────────────
PUSHED=0
PULLED=0
FAILED=0

for host_var in $(compgen -v | grep -E '^HOST[0-9]+$' | sort); do
    partner_host="${!host_var}"
    [[ -z "$partner_host" ]] && continue
    [[ "${host_var,,}" == "${MY_ID,,}" ]] && continue

    partner_slot="${host_var,,}"   # e.g. host2
    partner_ip=$(resolve_tailscale_ip "$partner_host" 2>/dev/null || true)

    if [[ -z "$partner_ip" ]]; then
        warn "$partner_host — cannot resolve Tailscale IP, skipping"
        (( FAILED++ ))
        continue
    fi

    # ── Pull: grab partner's conf from their disk → our local cache ──────────
    if [[ "$PUSH_ONLY" == false ]]; then
        remote_conf="${SCRIPTS_DIR}/Configurations/${partner_slot}.conf"
        if [[ "$DRY_RUN" == true ]]; then
            warn "DRY RUN — would pull $partner_host:$remote_conf → $CACHE_DIR/${partner_slot}.conf"
        elif timeout "$SSH_TIMEOUT" scp -i "$SSH_KEY" \
            -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes -o StrictHostKeyChecking=no \
            "root@${partner_ip}:${remote_conf}" \
            "$CACHE_DIR/${partner_slot}.conf" 2>/dev/null; then
            log "Pulled ${partner_slot}.conf from $partner_host ✅"
            (( PULLED++ ))
        else
            warn "Could not pull ${partner_slot}.conf from $partner_host"
            (( FAILED++ ))
        fi
    fi

    # ── Push: send own conf to partner's /tmp/.vv/ cache ────────────────────
    if [[ "$PULL_ONLY" == true ]]; then
        continue
    fi

    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would push ${MY_ID,,}.conf → $partner_host:/tmp/.vv/config/cached/.confs/"
        continue
    fi

    # Ensure partner's cache dir exists, then SCP own conf into it
    timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
        -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes -o StrictHostKeyChecking=no \
        "root@${partner_ip}" "mkdir -p '$CACHE_DIR'" 2>/dev/null

    if timeout "$SSH_TIMEOUT" scp -i "$SSH_KEY" \
        -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes -o StrictHostKeyChecking=no \
        "$MY_CONF" \
        "root@${partner_ip}:${CACHE_DIR}/${MY_ID,,}.conf" 2>/dev/null; then
        log "Pushed ${MY_ID,,}.conf to $partner_host ✅"
        (( PUSHED++ ))
    else
        warn "Could not push to $partner_host"
        (( FAILED++ ))
    fi
done

# ── Summary ───────────────────────────────────────────────────────────────────
if [[ "$PUSH_ONLY" == true ]]; then
    info "Conf push complete — pushed to $PUSHED host(s)${FAILED:+, $FAILED failed}"
elif [[ "$PULL_ONLY" == true ]]; then
    info "Conf pull complete — pulled $PULLED partner conf(s)${FAILED:+, $FAILED failed}"
else
    info "Conf sync complete — pulled $PULLED, pushed $PUSHED${FAILED:+, $FAILED failed}"
fi

if [[ "$FAILED" -gt 0 ]]; then
    notify "Conf sync on $LOCAL_SERVER_NAME ($MY_ID) — $FAILED partner(s) failed. Partner config cache may be stale." \
        "Conf Sync" "warning"
    exit 1
fi

-            log "Pulled ${partner_slot}.conf from $partner_host ✅"

+            echo "Pulled ${partner_slot}.conf from $partner_host ✅"


The file /boot/config/plugins/varaverk/System_Essentials/conf_sync.sh has been updated successfully. (file state is current in your context — no need to Read it back)

#!/bin/bash
# ==============================================================================================
# ============================= Conf Cache Sync ================================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Maintains a RAM-resident conf cache at /tmp/.vv/config/cached/.confs/.
# Credentials and partner keys live in RAM only — never on disk across hosts.
#
# On array start (default / --array-start):
#   1. Copy own conf to local cache
#   2. Pull each available partner's conf from their disk → local cache
#   3. Push own conf to each available partner's /tmp/.vv/ cache
#
# On conf save (--push-only):
#   Fast path — push updated own conf to all partners' /tmp/.vv/ cache only.
#   No pulls, no local cache rebuild.
#
# Cache is /tmp (tmpfs) — cleared every reboot, repopulated by this script
# on next array start. Scripts source from cache for partner vars; own vars
# always come from disk (load_config.sh skips cached copy of own conf).
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
#   conf_sync.sh                   Full sync: pull from all partners + push to all partners
#   conf_sync.sh --push-only       Push own conf to all partners (fast, for conf-save hook)
#   conf_sync.sh --pull-only       Pull partner confs into local cache only (for intermediate orch)
#   conf_sync.sh --dry-run         Show what would happen, no changes
#   conf_sync.sh --log             Verbose output
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../load_config.sh"

PUSH_ONLY=false
PULL_ONLY=false
FILTERED_ARGS=()
for arg in "$@"; do
    case "$arg" in
        --push-only) PUSH_ONLY=true ;;
        --pull-only) PULL_ONLY=true ;;
        *)           FILTERED_ARGS+=("$arg") ;;
    esac
done
parse_args "${FILTERED_ARGS[@]}"
detect_hosts

if [[ "${CONF_SYNC_ENABLED:-true}" == false ]]; then
    log "CONF_SYNC_ENABLED=false — skipping"
    exit 0
fi

CACHE_DIR="/tmp/.vv/config/cached/.confs"
MY_CONF="$SCRIPTS_ROOT/Configurations/${MY_ID,,}.conf"
SSH_TIMEOUT=10

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"

# ── Ensure cache dir exists ───────────────────────────────────────────────────
if [[ "$DRY_RUN" == false ]]; then
    mkdir -p "$CACHE_DIR"
fi

# ── Copy own conf into local cache ───────────────────────────────────────────
if [[ "$PUSH_ONLY" == false ]] && [[ "$PULL_ONLY" == false ]]; then
    if [[ -f "$MY_CONF" ]]; then
        if [[ "$DRY_RUN" == true ]]; then
            warn "DRY RUN — would copy $(basename "$MY_CONF") → $CACHE_DIR/"
        else
            cp "$MY_CONF" "$CACHE_DIR/${MY_ID,,}.conf" && \
                echo "Own conf cached ✅" || warn "Failed to cache own conf"
        fi
    else
        warn "Own conf not found: $MY_CONF"
    fi
fi

# ── Per-partner sync ──────────────────────────────────────────────────────────
PUSHED=0
PULLED=0
FAILED=0

for host_var in $(compgen -v | grep -E '^HOST[0-9]+$' | sort); do
    partner_host="${!host_var}"
    [[ -z "$partner_host" ]] && continue
    [[ "${host_var,,}" == "${MY_ID,,}" ]] && continue

    partner_slot="${host_var,,}"   # e.g. host2
    partner_ip=$(resolve_tailscale_ip "$partner_host" 2>/dev/null || true)

    if [[ -z "$partner_ip" ]]; then
        warn "$partner_host — cannot resolve Tailscale IP, skipping"
        (( FAILED++ ))
        continue
    fi

    # ── Pull: grab partner's conf from their disk → our local cache ──────────
    if [[ "$PUSH_ONLY" == false ]]; then
        remote_conf="${SCRIPTS_DIR}/Configurations/${partner_slot}.conf"
        if [[ "$DRY_RUN" == true ]]; then
            warn "DRY RUN — would pull $partner_host:$remote_conf → $CACHE_DIR/${partner_slot}.conf"
        elif timeout "$SSH_TIMEOUT" scp -i "$SSH_KEY" \
            -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes -o StrictHostKeyChecking=no \
            "root@${partner_ip}:${remote_conf}" \
            "$CACHE_DIR/${partner_slot}.conf" 2>/dev/null; then
            echo "Pulled ${partner_slot}.conf from $partner_host ✅"
            (( PULLED++ ))
        else
            warn "Could not pull ${partner_slot}.conf from $partner_host"
            (( FAILED++ ))
        fi
    fi

    # ── Push: send own conf to partner's /tmp/.vv/ cache ────────────────────
    if [[ "$PULL_ONLY" == true ]]; then
        continue
    fi

    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would push ${MY_ID,,}.conf → $partner_host:/tmp/.vv/config/cached/.confs/"
        continue
    fi

    # Ensure partner's cache dir exists, then SCP own conf into it
    timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
        -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes -o StrictHostKeyChecking=no \
        "root@${partner_ip}" "mkdir -p '$CACHE_DIR'" 2>/dev/null

    if timeout "$SSH_TIMEOUT" scp -i "$SSH_KEY" \
        -o ConnectTimeout="$SSH_TIMEOUT" -o BatchMode=yes -o StrictHostKeyChecking=no \
        "$MY_CONF" \
        "root@${partner_ip}:${CACHE_DIR}/${MY_ID,,}.conf" 2>/dev/null; then
        log "Pushed ${MY_ID,,}.conf to $partner_host ✅"
        (( PUSHED++ ))
    else
        warn "Could not push to $partner_host"
        (( FAILED++ ))
    fi
done

# ── Summary ───────────────────────────────────────────────────────────────────
if [[ "$PUSH_ONLY" == true ]]; then
    info "Conf push complete — pushed to $PUSHED host(s)${FAILED:+, $FAILED failed}"
elif [[ "$PULL_ONLY" == true ]]; then
    info "Conf pull complete — pulled $PULLED partner conf(s)${FAILED:+, $FAILED failed}"
else
    info "Conf sync complete — pulled $PULLED, pushed $PUSHED${FAILED:+, $FAILED failed}"
fi

if [[ "$FAILED" -gt 0 ]]; then
    notify "Conf sync on $LOCAL_SERVER_NAME ($MY_ID) — $FAILED partner(s) failed. Partner config cache may be stale." \
        "Conf Sync" "warning"
    exit 1
fi

-        log "Pushed ${MY_ID,,}.conf to $partner_host ✅"

+        echo "Pushed ${MY_ID,,}.conf to $partner_host ✅"


ok, thats what we were doing last week. we were going through all scripts correcting for verbose...... lets go through and check echo vs log... goal clean informative no verbose, and all details verbo…


Run docker network script during onboarding


The file /boot/config/plugins/varaverk/System_Essentials/server_reboot.sh has been updated successfully. (file state is current in your context — no need to Read it back)

warn "array_stopping.sh reported failures — proceeding with reboot"


warn "DRY RUN — would sync filesystem buffers"

+    echo "Filesystem buffers flushed ✅"


The file /boot/config/plugins/varaverk/System_Essentials/rsync_stop.sh has been updated successfully. (file state is current in your context — no need to Read it back)

warn "$REMOTE_SERVER_NAME — cannot resolve Tailscale IP, remote operations will be skipped"

elif timeout "$SSH_TIMEOUT" ping -c1 -W3 "$REMOTE_SERVER" &>/dev/null; then

-    log "$REMOTE_SERVER_NAME reachable ✅"

+    echo "$REMOTE_SERVER_NAME reachable ✅"

warn "$REMOTE_SERVER_NAME unreachable — remote operations will be skipped"


Now Plugin/unraid:


The file /boot/config/plugins/varaverk/Plugin/unraid/System_Essentials/php_fpm_max_children.sh has been updated successfully. (file state is current in your context — no need to Read it back)

#!/bin/bash
# ==============================================================================================
# ============================= PHP-FPM Max Children ===========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Raises PHP-FPM pm.max_children to prevent WebGUI slowdowns under load. Run
# once at array start via ARRAY_START_SCRIPTS. Idempotent — silent when the
# value is already correct, no restart on clean boot.
#
# unRAID's WebGUI runs through PHP-FPM. The default pm.max_children is very
# low (4–8). Under load — multiple users, Docker operations, heavy dashboard
# usage — all PHP workers saturate and new requests queue. The WebGUI becomes
# slow or unresponsive.
#
# PHP_MAX_CHILDREN=250 is appropriate for 128GB RAM: ~2MB per worker = ~500MB
# total. Too high wastes RAM; too low causes slowdowns.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Idempotent Content Check
#   Reads the current pm.max_children value before writing. If already at
#   target → silent exit, no PHP-FPM restart. Restarting PHP-FPM unnecessarily
#   disrupts active WebGUI sessions on every boot.
#
# Pattern Match Before Write
#   Verifies the sed pattern finds pm.max_children in the config before
#   applying any change. Prevents silent failures where sed succeeds but
#   writes nothing because the key was missing or commented out.
#
# Apply Sequence
#   1. Read current pm.max_children from PHP_CONF
#   2. If already at target → exit silently
#   3. Verify sed pattern matches before writing
#   4. Apply sed replacement
#   5. Restart PHP-FPM via rc.php-fpm
#   6. Verify PHP-FPM process running after restart
#   7. Read back config to confirm value applied
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
#   Writing to /etc/php83/ requires root.
#
# Single Instance Lock
#   acquire_lock prevents concurrent runs at array start.
#
# Process Verify
#   Confirms PHP-FPM running after restart — errors if it failed to start.
#
# Config Verify
#   Reads back config after restart to confirm the value was actually applied.
#
# Silent on Success
#   Runs every boot — no noise when already correct.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
#   PHP_MAX_CHILDREN
#       Target pm.max_children value. (default: 250)
#
#   PHP_CONF
#       Path to PHP-FPM www.conf. (default: /etc/php83/php-fpm.d/www.conf)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# php_fpm_max_children.sh
#     Read current value. Update and restart PHP-FPM only if changed. Silent if correct.
#
# php_fpm_max_children.sh --dry-run
#     Show current vs target value. No config write or restart.
#
# php_fpm_max_children.sh --status
#     Show current pm.max_children, target, and PHP-FPM process state.
#
# php_fpm_max_children.sh --log
#     Verbose output showing idempotent check, config write, and restart result.
#
# ==============================================================================================

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 — writing system config requires root"
    exit 1
fi

platform_require_cmd \
    "/usr/local/emhttp/plugins/dynamix/scripts/notify" \
    "" "" \
    "unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"

validate_int PHP_MAX_CHILDREN "$PHP_MAX_CHILDREN"
require_var PHP_CONF

acquire_lock

detect_hosts

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"

# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
    echo ""
    echo "━━━━━ $ICON_SUMMARY PHP-FPM STATUS ━━━━━"
    echo "$ICON_HOST   Identity:     $MY_ID ($LOCAL_SERVER_NAME)"
    echo "$ICON_GEAR   Config file:  $PHP_CONF"
    echo "$ICON_PHP    Target:       pm.max_children = $PHP_MAX_CHILDREN"
    echo ""

    if [[ -f "$PHP_CONF" ]]; then
        CURRENT_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | \
            awk '{print $NF}')
        if [[ "${CURRENT_VAL:-0}" -eq "$PHP_MAX_CHILDREN" ]]; then
            echo "  $ICON_SUCCESS Current: pm.max_children = $CURRENT_VAL (correct ✅)"
        else
            echo "  $ICON_WARN   Current: pm.max_children = ${CURRENT_VAL:-not set} (would update)"
        fi
    else
        echo "  $ICON_ERROR Config file not found: $PHP_CONF"
    fi

    echo ""
    if pgrep -f "php-fpm" >/dev/null 2>&1; then
        FPM_COUNT=$(pgrep -fc "php-fpm" 2>/dev/null || echo "?")
        echo "  $ICON_SUCCESS PHP-FPM: running ($FPM_COUNT worker(s))"
    else
        echo "  $ICON_ERROR  PHP-FPM: NOT running"
    fi
    echo "━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
fi

# ==============================================================================================
# ━━━ PHP-FPM Config ━━━
# ==============================================================================================
START=$(date +%s)

if [[ ! -f "$PHP_CONF" ]]; then
    error "PHP config file not found: $PHP_CONF"
    notify "PHP-FPM config not found on $(hostname) ($MY_ID) — $PHP_CONF missing" \
        "PHP-FPM" "warning"
    exit 1
fi

# ── Idempotent check ─────────────────────────────────────────────────────────────────────────
CURRENT_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | awk '{print $NF}')
if [[ "${CURRENT_VAL:-0}" -eq "$PHP_MAX_CHILDREN" ]]; then
    echo "pm.max_children already $PHP_MAX_CHILDREN ✅"
    exit 0
fi

warn "pm.max_children: ${CURRENT_VAL:-not set} → $PHP_MAX_CHILDREN"

# ── Verify pattern exists before writing ─────────────────────────────────────────────────────
if ! grep -qE "^pm\.max_children" "$PHP_CONF" 2>/dev/null; then
    error "pm.max_children not found in $PHP_CONF — cannot apply"
    error "Add 'pm.max_children = $PHP_MAX_CHILDREN' to $PHP_CONF manually"
    notify "PHP-FPM pm.max_children not found in config on $(hostname) ($MY_ID)" \
        "PHP-FPM" "warning"
    exit 1
fi

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would set pm.max_children = $PHP_MAX_CHILDREN in $PHP_CONF"
    warn "DRY RUN — would restart PHP-FPM"
    exit 0
fi

# ── Apply setting ─────────────────────────────────────────────────────────────────────────────
log "Applying pm.max_children = $PHP_MAX_CHILDREN..."
if ! sed -i "s/^pm\.max_children.*/pm.max_children = $PHP_MAX_CHILDREN/" "$PHP_CONF"; then
    error "Failed to update $PHP_CONF"
    notify "PHP-FPM config update failed on $(hostname) ($MY_ID)" \
        "PHP-FPM" "warning"
    exit 1
fi

log "Config updated"

# ── Restart PHP-FPM ──────────────────────────────────────────────────────────────────────────
log "Restarting PHP-FPM..."
if ! platform_restart_service php-fpm; then
    error "PHP-FPM restart command failed"
    notify "PHP-FPM restart failed on $(hostname) ($MY_ID)" \
        "PHP-FPM" "warning"
    exit 1
fi

sleep 3  # Allow PHP-FPM workers to initialise

# ── Verify process running ────────────────────────────────────────────────────────────────────
if ! pgrep -f "php-fpm" >/dev/null 2>&1; then
    error "PHP-FPM not running after restart — WebGUI may be broken"
    notify "PHP-FPM failed to start after config update on $(hostname) ($MY_ID)" \
        "PHP-FPM" "warning"
    exit 1
fi

# ── Verify config reflects target ────────────────────────────────────────────────────────────
APPLIED_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | awk '{print $NF}')
if [[ "${APPLIED_VAL:-0}" -ne "$PHP_MAX_CHILDREN" ]]; then
    warn "Config reads pm.max_children = ${APPLIED_VAL:-unknown} — expected $PHP_MAX_CHILDREN"
    warn "Check $PHP_CONF manually"
else
    log "Verified: pm.max_children = $APPLIED_VAL ✅"
fi

FPM_WORKERS=$(pgrep -fc "php-fpm" 2>/dev/null || echo "?")
log "$ICON_PHP Workers running: $FPM_WORKERS"

END=$(date +%s)

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY PHP-FPM SUMMARY ━━━━━"
echo "$ICON_HOST   Identity:     $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_GEAR   Config file:  $PHP_CONF"
echo "$ICON_PHP    Applied:      pm.max_children = $PHP_MAX_CHILDREN"
echo "$ICON_TIME   Duration:     $(format_duration $(( END - START )))"
echo ""
echo "$ICON_DONE Status: done ✅"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

exit 0


Edit

log "Verified: pm.max_children = $APPLIED_VAL ✅"

echo "Verified: pm.max_children = $APPLIED_VAL ✅"


#!/bin/bash
# ==============================================================================================
# ============================= PHP-FPM Max Children ===========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Raises PHP-FPM pm.max_children to prevent WebGUI slowdowns under load. Run
# once at array start via ARRAY_START_SCRIPTS. Idempotent — silent when the
# value is already correct, no restart on clean boot.
#
# unRAID's WebGUI runs through PHP-FPM. The default pm.max_children is very
# low (4–8). Under load — multiple users, Docker operations, heavy dashboard
# usage — all PHP workers saturate and new requests queue. The WebGUI becomes
# slow or unresponsive.
#
# PHP_MAX_CHILDREN=250 is appropriate for 128GB RAM: ~2MB per worker = ~500MB
# total. Too high wastes RAM; too low causes slowdowns.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Idempotent Content Check
#   Reads the current pm.max_children value before writing. If already at
#   target → silent exit, no PHP-FPM restart. Restarting PHP-FPM unnecessarily
#   disrupts active WebGUI sessions on every boot.
#
# Pattern Match Before Write
#   Verifies the sed pattern finds pm.max_children in the config before
#   applying any change. Prevents silent failures where sed succeeds but
#   writes nothing because the key was missing or commented out.
#
# Apply Sequence
#   1. Read current pm.max_children from PHP_CONF
#   2. If already at target → exit silently
#   3. Verify sed pattern matches before writing
#   4. Apply sed replacement
#   5. Restart PHP-FPM via rc.php-fpm
#   6. Verify PHP-FPM process running after restart
#   7. Read back config to confirm value applied
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Required
#   Writing to /etc/php83/ requires root.
#
# Single Instance Lock
#   acquire_lock prevents concurrent runs at array start.
#
# Process Verify
#   Confirms PHP-FPM running after restart — errors if it failed to start.
#
# Config Verify
#   Reads back config after restart to confirm the value was actually applied.
#
# Silent on Success
#   Runs every boot — no noise when already correct.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
#   PHP_MAX_CHILDREN
#       Target pm.max_children value. (default: 250)
#
#   PHP_CONF
#       Path to PHP-FPM www.conf. (default: /etc/php83/php-fpm.d/www.conf)
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# php_fpm_max_children.sh
#     Read current value. Update and restart PHP-FPM only if changed. Silent if correct.
#
# php_fpm_max_children.sh --dry-run
#     Show current vs target value. No config write or restart.
#
# php_fpm_max_children.sh --status
#     Show current pm.max_children, target, and PHP-FPM process state.
#
# php_fpm_max_children.sh --log
#     Verbose output showing idempotent check, config write, and restart result.
#
# ==============================================================================================

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 — writing system config requires root"
    exit 1
fi

platform_require_cmd \
    "/usr/local/emhttp/plugins/dynamix/scripts/notify" \
    "" "" \
    "unRAID notify script" || warn "unRAID notify script not found — native notifications disabled"

validate_int PHP_MAX_CHILDREN "$PHP_MAX_CHILDREN"
require_var PHP_CONF

acquire_lock

detect_hosts

[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"

# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
    echo ""
    echo "━━━━━ $ICON_SUMMARY PHP-FPM STATUS ━━━━━"
    echo "$ICON_HOST   Identity:     $MY_ID ($LOCAL_SERVER_NAME)"
    echo "$ICON_GEAR   Config file:  $PHP_CONF"
    echo "$ICON_PHP    Target:       pm.max_children = $PHP_MAX_CHILDREN"
    echo ""

    if [[ -f "$PHP_CONF" ]]; then
        CURRENT_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | \
            awk '{print $NF}')
        if [[ "${CURRENT_VAL:-0}" -eq "$PHP_MAX_CHILDREN" ]]; then
            echo "  $ICON_SUCCESS Current: pm.max_children = $CURRENT_VAL (correct ✅)"
        else
            echo "  $ICON_WARN   Current: pm.max_children = ${CURRENT_VAL:-not set} (would update)"
        fi
    else
        echo "  $ICON_ERROR Config file not found: $PHP_CONF"
    fi

    echo ""
    if pgrep -f "php-fpm" >/dev/null 2>&1; then
        FPM_COUNT=$(pgrep -fc "php-fpm" 2>/dev/null || echo "?")
        echo "  $ICON_SUCCESS PHP-FPM: running ($FPM_COUNT worker(s))"
    else
        echo "  $ICON_ERROR  PHP-FPM: NOT running"
    fi
    echo "━━━━━━━━━━━━━━━━━━━━━━━"
    exit 0
fi

# ==============================================================================================
# ━━━ PHP-FPM Config ━━━
# ==============================================================================================
START=$(date +%s)

if [[ ! -f "$PHP_CONF" ]]; then
    error "PHP config file not found: $PHP_CONF"
    notify "PHP-FPM config not found on $(hostname) ($MY_ID) — $PHP_CONF missing" \
        "PHP-FPM" "warning"
    exit 1
fi

# ── Idempotent check ─────────────────────────────────────────────────────────────────────────
CURRENT_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | awk '{print $NF}')
if [[ "${CURRENT_VAL:-0}" -eq "$PHP_MAX_CHILDREN" ]]; then
    echo "pm.max_children already $PHP_MAX_CHILDREN ✅"
    exit 0
fi

warn "pm.max_children: ${CURRENT_VAL:-not set} → $PHP_MAX_CHILDREN"

# ── Verify pattern exists before writing ─────────────────────────────────────────────────────
if ! grep -qE "^pm\.max_children" "$PHP_CONF" 2>/dev/null; then
    error "pm.max_children not found in $PHP_CONF — cannot apply"
    error "Add 'pm.max_children = $PHP_MAX_CHILDREN' to $PHP_CONF manually"
    notify "PHP-FPM pm.max_children not found in config on $(hostname) ($MY_ID)" \
        "PHP-FPM" "warning"
    exit 1
fi

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would set pm.max_children = $PHP_MAX_CHILDREN in $PHP_CONF"
    warn "DRY RUN — would restart PHP-FPM"
    exit 0
fi

# ── Apply setting ─────────────────────────────────────────────────────────────────────────────
log "Applying pm.max_children = $PHP_MAX_CHILDREN..."
if ! sed -i "s/^pm\.max_children.*/pm.max_children = $PHP_MAX_CHILDREN/" "$PHP_CONF"; then
    error "Failed to update $PHP_CONF"
    notify "PHP-FPM config update failed on $(hostname) ($MY_ID)" \
        "PHP-FPM" "warning"
    exit 1
fi

echo "Config updated"

# ── Restart PHP-FPM ──────────────────────────────────────────────────────────────────────────
log "Restarting PHP-FPM..."
if ! platform_restart_service php-fpm; then
    error "PHP-FPM restart command failed"
    notify "PHP-FPM restart failed on $(hostname) ($MY_ID)" \
        "PHP-FPM" "warning"
    exit 1
fi

sleep 3  # Allow PHP-FPM workers to initialise

# ── Verify process running ────────────────────────────────────────────────────────────────────
if ! pgrep -f "php-fpm" >/dev/null 2>&1; then
    error "PHP-FPM not running after restart — WebGUI may be broken"
    notify "PHP-FPM failed to start after config update on $(hostname) ($MY_ID)" \
        "PHP-FPM" "warning"
    exit 1
fi

# ── Verify config reflects target ────────────────────────────────────────────────────────────
APPLIED_VAL=$(grep -E "^pm\.max_children" "$PHP_CONF" 2>/dev/null | awk '{print $NF}')
if [[ "${APPLIED_VAL:-0}" -ne "$PHP_MAX_CHILDREN" ]]; then
    warn "Config reads pm.max_children = ${APPLIED_VAL:-unknown} — expected $PHP_MAX_CHILDREN"
    warn "Check $PHP_CONF manually"
else
    log "Verified: pm.max_children = $APPLIED_VAL ✅"
fi

FPM_WORKERS=$(pgrep -fc "php-fpm" 2>/dev/null || echo "?")
log "$ICON_PHP Workers running: $FPM_WORKERS"

END=$(date +%s)

# ==============================================================================================
# ━━━ Summary ━━━
# ==============================================================================================
echo ""
echo "━━━━━ $ICON_SUMMARY PHP-FPM SUMMARY ━━━━━"
echo "$ICON_HOST   Identity:     $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_GEAR   Config file:  $PHP_CONF"
echo "$ICON_PHP    Applied:      pm.max_children = $PHP_MAX_CHILDREN"
echo "$ICON_TIME   Duration:     $(format_duration $(( END - START )))"
echo ""
echo "$ICON_DONE Status: done ✅"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

exit 0

-    log "Verified: pm.max_children = $APPLIED_VAL ✅"

+    echo "Verified: pm.max_children = $APPLIED_VAL ✅"


The file /boot/config/plugins/varaverk/Plugin/unraid/System_Essentials/unraid_api_key_renew.sh has been updated successfully. (file state is current in your context — no need to Read it back)

#!/bin/bash
# ==============================================================================================
# ================================= Unraid API Key Renewal ====================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Creates/overwrites the Varaverk API key in the unraid-api service registry at
# array start. The registry is ephemeral — OS updates and service restarts clear
# it. This script re-registers the key every boot so Varaverk's enhanced
# monitoring self-heals without manual intervention.
#
# Also updates HOST*_UNRAID_API_KEY in the local host conf so the partnership
# page always reflects the live key value.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# unraid_api_key_renew.sh
#     Renew the key. Silent on success.
#
# unraid_api_key_renew.sh --dry-run
#     Show what would happen — no changes made.
#
# unraid_api_key_renew.sh --log
#     Verbose output.
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "$SCRIPT_DIR/../../../load_config.sh"

parse_args "$@"
acquire_lock
detect_hosts

# ──────────────────────────────────────────────────────────────────────────────
CONF_FILE="$SCRIPT_DIR/../../../Configurations/${MY_ID,,}.conf"
VAR_NAME="${MY_ID}_UNRAID_API_KEY"

# Key name: "Varaverk <hostname>" stripping any unraid- prefix
# Space separator — unRAID API only allows letters, numbers, and spaces
HOSTNAME_SUFFIX=$(hostname -s 2>/dev/null | sed 's/^[Uu][Nn][Rr][Aa][Ii][Dd]-//' || hostname -s)
KEY_NAME="Varaverk ${HOSTNAME_SUFFIX}"

log "$ICON_GEAR Conf file: $CONF_FILE"
log "$ICON_GEAR Key var:   $VAR_NAME"
log "$ICON_GEAR Key name:  $KEY_NAME"

if [[ ! -f "$CONF_FILE" ]]; then
    error "Conf file not found: $CONF_FILE"
    exit 1
fi

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would check registry for $KEY_NAME, renew only if missing"
    exit 0
fi

# ──────────────────────────────────────────────────────────────────────────────
# Check if key already exists in the unraid-api registry before creating.
# --overwrite generates a new key value every time, invalidating the old one.
# Only renew if the registry has lost it.
log "Checking unraid-api registry for $KEY_NAME..."
EXISTING=$(timeout 5 /usr/local/sbin/unraid-api apikey --name "$KEY_NAME" --json </dev/null 2>/dev/null)
KEY=$(echo "$EXISTING" | jq -r '.key // empty' 2>/dev/null)

if [[ -n "$KEY" ]]; then
    PREVIEW="${KEY:0:8}...${KEY: -4}"
    # Always sync registry key → conf, even if the key was already there.
    # Conf gets wiped on git pull / conf regeneration without touching the registry.
    CONF_HAS_KEY=$(grep -oP "(?<=^\s*${VAR_NAME}=\")[^\"]*" "$CONF_FILE" 2>/dev/null || true)
    if [[ "$CONF_HAS_KEY" == "$KEY" ]]; then
        echo "API key valid ✅ — $VAR_NAME = $PREVIEW"
        log "Key in registry and conf — no action needed"
        exit 0
    fi
    log "Key in registry but conf is stale — syncing..."
    if grep -q "^\s*${VAR_NAME}\s*=" "$CONF_FILE"; then
        sed -i "s|^\(\s*${VAR_NAME}\s*=\s*\)\"[^\"]*\"|\1\"${KEY}\"|" "$CONF_FILE"
    else
        echo "    ${VAR_NAME}=\"${KEY}\"" >> "$CONF_FILE"
    fi
    echo "API key synced to conf ✅ — $VAR_NAME = $PREVIEW"
    exit 0
fi

log "Key not found in registry — creating new key..."

RAW=$(timeout 10 /usr/local/sbin/unraid-api apikey \
    --name "$KEY_NAME" --create --overwrite \
    --description "Varaverk plugin" --roles ADMIN --json </dev/null 2>&1)

if [[ -z "$RAW" ]]; then
    error "unraid-api returned no output"
    exit 1
fi

KEY=$(echo "$RAW" | jq -r '.key // empty' 2>/dev/null)
if [[ -z "$KEY" ]]; then
    error "No key in unraid-api response: ${RAW:0:200}"
    exit 1
fi

# ──────────────────────────────────────────────────────────────────────────────
if grep -q "^\s*${VAR_NAME}\s*=" "$CONF_FILE"; then
    sed -i "s|^\(\s*${VAR_NAME}\s*=\s*\)\"[^\"]*\"|\1\"${KEY}\"|" "$CONF_FILE"
else
    echo "    ${VAR_NAME}=\"${KEY}\"" >> "$CONF_FILE"
fi

PREVIEW="${KEY:0:8}...${KEY: -4}"
log "Writing new key to: $CONF_FILE"
warn "API key renewed ✅ — $VAR_NAME = $PREVIEW  (registry had lost it)"

# ── Push renewed key into each partner's OWN conf ─────────────────────────────
# Each host's conf is its complete keychest — no cross-host conf files needed.
# SSH_KEY is set by detect_hosts() — this server's outbound private key.
if [[ -z "$SSH_KEY" ]]; then
    log "No SSH key configured — skipping partner push"
    exit 0
fi

for host_var in $(compgen -v | grep -E '^HOST[0-9]+$'); do
    partner_host="${!host_var}"
    [[ -z "$partner_host" ]] && continue
    [[ "${host_var,,}" == "${MY_ID,,}" ]] && continue

    partner_slot="${host_var,,}"   # e.g. host2
    partner_ip=$(resolve_tailscale_ip "$partner_host" 2>/dev/null || true)
    [[ -z "$partner_ip" ]] && { log "Cannot resolve IP for $partner_host — skipping"; continue; }

    # Target is the partner's OWN conf on their machine
    partner_conf="/boot/config/plugins/varaverk/Configurations/${partner_slot}.conf"
    tmp=$(mktemp /tmp/vv_kp_XXXXXX.sh)
    remote="/tmp/vv_kp_${RANDOM}.sh"
    chmod 700 "$tmp"

    # Key stays in the temp file — never appears in SSH command args
    cat > "$tmp" <<PUSHSCRIPT
#!/bin/sh
target='${partner_conf}'
if grep -q "\b${VAR_NAME}\b" "\$target" 2>/dev/null; then
  sed -i 's|^\(\\s*${VAR_NAME}\\s*=\\s*\)"[^"]*"|\1"${KEY}"|' "\$target"
else
  printf '    ${VAR_NAME}="%s"\n' '${KEY}' >> "\$target"
fi
echo ok
PUSHSCRIPT

    if timeout 10 scp -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes \
        -o StrictHostKeyChecking=no "$tmp" "root@${partner_ip}:${remote}" 2>/dev/null; then
        if timeout 10 ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes \
            -o StrictHostKeyChecking=no "root@${partner_ip}" \
            "bash '${remote}'; rc=\$?; rm -f '${remote}'; exit \$rc" 2>/dev/null | grep -q ok; then
            log "Key pushed to $partner_host ✅"
        else
            warn "Key push to $partner_host failed — they can create their own copy"
        fi
    else
        warn "SCP to $partner_host failed — skipping"
    fi
    rm -f "$tmp"
done

-            log "Key pushed to $partner_host ✅"

+            echo "Key pushed to $partner_host ✅"


log "Array is started — /mnt/user is mounted ✅"

echo "Array is started — /mnt/user is mounted ✅"


The file /boot/config/plugins/varaverk/Plugin/unraid/Tools/recreate_shares.sh has been updated successfully. (file state is current in your context — no need to Read it back)

-log "Array is started — /mnt/user is mounted ✅"

+echo "Array is started — /mnt/user is mounted ✅"

# Check share cfg directory exists and has files


warn "DRY RUN — would create: $DISK_PATH"

-                log "Created: $DISK_PATH ✅"

+                echo "Created: $DISK_PATH ✅"


warn "DRY RUN — would place marker: $MARKER_PATH"

elif touch "$MARKER_PATH" 2>/dev/null; then

-            log "Marker placed: $MARKER_PATH ✅"

+            echo "Marker placed: $MARKER_PATH ✅"

warn "$SHARE_NAME — could not place .recovery marker"


Now the Watchdogs and Monitors (state transitions only):


log "NPM strikes cleared (was $NPM_STRIKES)"

echo "NPM strikes cleared (was $NPM_STRIKES)"


log "All pressure cleared — system at normal operation ✅"

echo "All pressure cleared — system at normal operation ✅"


The file /boot/config/plugins/varaverk/Watchdogs/System/network_watchdog.sh has been updated successfully. (file state is current in your context — no need to Read it back)

if curl -sf --max-time "${NETWORK_WATCHDOG_NPM_TIMEOUT:-10}" "$NPM_URL" >/dev/null 2>&1; then

log "$ICON_SUCCESS NPM proxy reachable — $NPM_URL"

-            log "NPM strikes cleared (was $NPM_STRIKES)"

+            echo "NPM strikes cleared (was $NPM_STRIKES)"

set_strikes "npm" 0 "${NETWORK_WATCHDOG_NPM_STATE_FILE}"


The file /boot/config/plugins/varaverk/Watchdogs/resource_watchdog.sh has been updated successfully. (file state is current in your context — no need to Read it back)

warn "De-escalated to level $NEW_LEVEL (${LEVEL_NAMES[$NEW_LEVEL]}) — ${RW_RECOVER_CYCLES:-3} more cycles to fully clear"

-                log "All pressure cleared — system at normal operation ✅"

+                echo "All pressure cleared — system at normal operation ✅"

notify "Resource Manager: pressure resolved on $(hostname) ($MY_ID) — system back to normal" \


Now Monitors:


log "Connectivity to $REMOTE_SERVER_NAME ✅"

echo "Connectivity to $REMOTE_SERVER_NAME ✅"


log "Version parity with $REMOTE_SERVER_NAME ✅"

echo "Version parity with $REMOTE_SERVER_NAME ✅"


log "Remote array mounted on $REMOTE_SERVER_NAME ✅"

echo "Remote array mounted on $REMOTE_SERVER_NAME ✅"


log "$SHARE_NAME — all $SHARE_MATCH files match ✅"

echo "$SHARE_NAME — all $SHARE_MATCH files match ✅"


log "$ICON_EMBY Connected to: $SERVER_NAME (v$SERVER_VERSION) ✅"

echo "$ICON_EMBY Connected to: $SERVER_NAME (v$SERVER_VERSION) ✅"


The file /boot/config/plugins/varaverk/Monitors/backup_verify.sh has been updated successfully. (file state is current in your context — no need to Read it back)

# Connectivity — no point making 100+ SSH calls if remote is unreachable

-log "Connectivity to $REMOTE_SERVER_NAME ✅"

+echo "Connectivity to $REMOTE_SERVER_NAME ✅"

# Version parity — mismatched unRAID could cause md5sum path differences


warn "Version parity check failed — proceeding with caution"

warn "Checksum results may be unreliable if md5sum path changed between versions"

-log "Version parity with $REMOTE_SERVER_NAME ✅"

+echo "Version parity with $REMOTE_SERVER_NAME ✅"

# Remote array — if array is down all files appear "missing" = false alarm


-log "Remote array mounted on $REMOTE_SERVER_NAME ✅"

+echo "Remote array mounted on $REMOTE_SERVER_NAME ✅"


warn "$SHARE_NAME — match: $SHARE_MATCH  missing: $SHARE_MISSING  mismatch: $SHARE_MISMATCH"

-        log "$SHARE_NAME — all $SHARE_MATCH files match ✅"

+        echo "$SHARE_NAME — all $SHARE_MATCH files match ✅"


The file /boot/config/plugins/varaverk/Monitors/emby_session_report.sh has been updated successfully. (file state is current in your context — no need to Read it back)

SERVER_NAME=$(echo "$SYSTEM_INFO" | jq -r '.ServerName // "Unknown"' 2>/dev/null)

SERVER_VERSION=$(echo "$SYSTEM_INFO" | jq -r '.Version // "Unknown"' 2>/dev/null)

-log "$ICON_EMBY Connected to: $SERVER_NAME (v$SERVER_VERSION) ✅"

+echo "$ICON_EMBY Connected to: $SERVER_NAME (v$SERVER_VERSION) ✅"

# ── Active Sessions ───────────────────────────────────────────────────────────────────────────

echo "━━━ $ICON_EMBY Active Sessions ━━━"


Now ramdisk_setup.sh (action outcomes only, not per-minute idempotent checks):


log "SSD fallback created: $TRANSCODE_SSD ✅"

echo "SSD fallback created: $TRANSCODE_SSD ✅"


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


[[ "$SETUP_SUCCESS" == true ]] && log "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"

[[ "$SETUP_SUCCESS" == true ]] && echo "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"


log "Created transcoding-temp on ramdisk ✅"

echo "Created transcoding-temp on ramdisk ✅"


220
221	if [[ "$DRY_RUN" == true ]]; then
222	    warn "DRY RUN — would create SSD fallback: $TRANSCODE_SSD"
223	else
224	    if [[ -d "$TRANSCODE_SSD" ]]; then
225	        log "SSD fallback already exists: $TRANSCODE_SSD"
226	    else
227	        log "Creating SSD fallback directory: $TRANSCODE_SSD"
228	        if mkdir -p "$TRANSCODE_SSD"; then
229	            log "SSD fallback created: $TRANSCODE_SSD ✅"
230	        else
231	            error "Failed to create SSD fallback: $TRANSCODE_SSD"
232	            SETUP_SUCCESS=false
233	        fi
234	    fi
235	fi
236
237	# ==============================================================================================
238	# ━━━ Symlink ━━━
239	# ==============================================================================================
240	echo ""
241	echo "━━━ $ICON_LINK Symlink ━━━"
242
243	if [[ "$DRY_RUN" == true ]]; then
244	    warn "DRY RUN — would set $TRANSCODE_LINK → $RAMDISK_PATH"
245	else
246	    if [[ -L "$TRANSCODE_LINK" ]]; then
247	        CURRENT_TARGET=$(readlink "$TRANSCODE_LINK")
248	        if [[ "$CURRENT_TARGET" == "$RAMDISK_PATH" ]]; then
249	            log "Symlink already points to ramdisk — no change needed ✅"
250	        else
251	            log "Updating symlink: $CURRENT_TARGET → $RAMDISK_PATH"
252	            ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
253	                error "Failed to update symlink"
254	                SETUP_SUCCESS=false
255	            }
256	        fi
257	    elif [[ -e "$TRANSCODE_LINK" ]]; then
258	        warn "$TRANSCODE_LINK exists but is not a symlink — removing and replacing"
259	        rm -rf "$TRANSCODE_LINK"
260	        ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
261	            error "Failed to create symlink"
262	            SETUP_SUCCESS=false
263	        }
264	    else
265	        log "Creating symlink: $TRANSCODE_LINK → $RAMDISK_PATH"
266	        mkdir -p "$(dirname "$TRANSCODE_LINK")"
267	        ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
268	            error "Failed to create symlink"
269	            SETUP_SUCCESS=false
270	        }
271	    fi
272
273	    [[ "$SETUP_SUCCESS" == true ]] && log "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"
274	fi
275
276	# ==============================================================================================
277	# ━━━ Transcoding-temp Directory ━━━
278	# ==============================================================================================
279	# Pre-created inside ramdisk so Emby always finds it there at session start.
280	# Without this Emby creates it at its own first-writable path — which may be
281	# SSD even when the symlink points at the ramdisk — locking all sessions onto SSD.
282	echo ""
283	echo "━━━ $ICON_GEAR Transcoding Temp Directory ━━━"
284
285	TRANSCODE_TEMP_DIR="${RAMDISK_PATH}/transcoding-temp"
286
287	if [[ "$DRY_RUN" == true ]]; then
288	    warn "DRY RUN — would create $TRANSCODE_TEMP_DIR"
289	else
290	    if [[ -d "$TRANSCODE_TEMP_DIR" ]]; then
291	        log "transcoding-temp already exists on ramdisk"
292	    else
293	        if mkdir -p "$TRANSCODE_TEMP_DIR"; then
294	            log "Created transcoding-temp on ramdisk ✅"
295	        else
296	            error "Failed to create transcoding-temp on ramdisk"
297	            SETUP_SUCCESS=false
298	        fi
299	    fi

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would create SSD fallback: $TRANSCODE_SSD"
else
    if [[ -d "$TRANSCODE_SSD" ]]; then
        log "SSD fallback already exists: $TRANSCODE_SSD"
    else
        log "Creating SSD fallback directory: $TRANSCODE_SSD"
        if mkdir -p "$TRANSCODE_SSD"; then
            log "SSD fallback created: $TRANSCODE_SSD ✅"
        else
            error "Failed to create SSD fallback: $TRANSCODE_SSD"
            SETUP_SUCCESS=false
        fi
    fi
fi

# ==============================================================================================
# ━━━ Symlink ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_LINK Symlink ━━━"

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would set $TRANSCODE_LINK → $RAMDISK_PATH"
else
    if [[ -L "$TRANSCODE_LINK" ]]; then
        CURRENT_TARGET=$(readlink "$TRANSCODE_LINK")
        if [[ "$CURRENT_TARGET" == "$RAMDISK_PATH" ]]; then
            log "Symlink already points to ramdisk — no change needed ✅"
        else
            log "Updating symlink: $CURRENT_TARGET → $RAMDISK_PATH"
            ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
                error "Failed to update symlink"
                SETUP_SUCCESS=false
            }
        fi
    elif [[ -e "$TRANSCODE_LINK" ]]; then
        warn "$TRANSCODE_LINK exists but is not a symlink — removing and replacing"
        rm -rf "$TRANSCODE_LINK"
        ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
            error "Failed to create symlink"
            SETUP_SUCCESS=false
        }
    else
        log "Creating symlink: $TRANSCODE_LINK → $RAMDISK_PATH"
        mkdir -p "$(dirname "$TRANSCODE_LINK")"
        ln -sfn "$RAMDISK_PATH" "$TRANSCODE_LINK" || {
            error "Failed to create symlink"
            SETUP_SUCCESS=false
        }
    fi

    [[ "$SETUP_SUCCESS" == true ]] && log "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"
fi

# ==============================================================================================
# ━━━ Transcoding-temp Directory ━━━
# ==============================================================================================
# Pre-created inside ramdisk so Emby always finds it there at session start.
# Without this Emby creates it at its own first-writable path — which may be
# SSD even when the symlink points at the ramdisk — locking all sessions onto SSD.
echo ""
echo "━━━ $ICON_GEAR Transcoding Temp Directory ━━━"

TRANSCODE_TEMP_DIR="${RAMDISK_PATH}/transcoding-temp"

if [[ "$DRY_RUN" == true ]]; then
    warn "DRY RUN — would create $TRANSCODE_TEMP_DIR"
else
    if [[ -d "$TRANSCODE_TEMP_DIR" ]]; then
        log "transcoding-temp already exists on ramdisk"
    else
        if mkdir -p "$TRANSCODE_TEMP_DIR"; then
            log "Created transcoding-temp on ramdisk ✅"
        else
            error "Failed to create transcoding-temp on ramdisk"
            SETUP_SUCCESS=false
        fi
    fi


log "SSD fallback created: $TRANSCODE_SSD ✅"

echo "SSD fallback created: $TRANSCODE_SSD ✅"


The file /boot/config/plugins/varaverk/Transcodes/ramdisk_setup.sh has been updated successfully. (file state is current in your context — no need to Read it back)

log "Creating SSD fallback directory: $TRANSCODE_SSD"

-            log "SSD fallback created: $TRANSCODE_SSD ✅"

+            echo "SSD fallback created: $TRANSCODE_SSD ✅"

error "Failed to create SSD fallback: $TRANSCODE_SSD"


[[ "$SETUP_SUCCESS" == true ]] && log "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"

[[ "$SETUP_SUCCESS" == true ]] && echo "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"


-    [[ "$SETUP_SUCCESS" == true ]] && log "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"

+    [[ "$SETUP_SUCCESS" == true ]] && echo "Symlink: $TRANSCODE_LINK → $RAMDISK_PATH ✅"


log "Created transcoding-temp on ramdisk ✅"

echo "Created transcoding-temp on ramdisk ✅"


log "transcoding-temp already exists on ramdisk"

-            log "Created transcoding-temp on ramdisk ✅"

+            echo "Created transcoding-temp on ramdisk ✅"

error "Failed to create transcoding-temp on ramdisk"


Now let me check what's left — Rsync, Fallback, and Partnership:


Bash

grep -rn 'log ".*✅\|log ".*done\|log ".*nothing\|log ".*removed\|log ".*created\|log ".*restarted\|log ".*started\|log ".*stopped\|log ".*mounted\|log ".*applied\|log ".*updated\|log ".*pushed\|log ".*pulled\|log ".*verified\|log ".*flushed\|log ".*placed\|log ".*complete\|log ".*established\|log ".*cleared\|log ".*resolved' \
  /boot/config/plugins/varaverk/Rsync/ \
  /boot/config/plugins/varaverk/Fallback/ \
  /boot/config/plugins/varaverk/Partnership/ 2>/dev/null | grep -v "^Binary"

Find remaining log→echo candidates in Rsync, Fallback, Partnership


/boot/config/plugins/varaverk/Rsync/rsync.sh:334:        log "$ICON_DONE Rsync complete — $BYTES_TRANSFERRED bytes transferred"
/boot/config/plugins/varaverk/Rsync/rsync.sh:405:            log "$ICON_STARTED $container restarted on $REMOTE_SERVER_NAME ✅" || \
/boot/config/plugins/varaverk/Partnership/partnership_onboard.sh:281:            log "  $container removed ✅" || \
/boot/config/plugins/varaverk/Partnership/partnership_onboard.sh:298:        log "SSH key ready ✅"
/boot/config/plugins/varaverk/Partnership/partnership_onboard.sh:326:            log "Phase 2 triggered on $OWNER ✅"
/boot/config/plugins/varaverk/Partnership/partnership_onboard.sh:390:        log "SSH to $MIRROR already works ✅ — skipping key install"
/boot/config/plugins/varaverk/Partnership/partnership_onboard.sh:395:            log "SSH keys ready ✅"
/boot/config/plugins/varaverk/Partnership/partnership_onboard.sh:426:    log "SSH keys ready ✅"
/boot/config/plugins/varaverk/Partnership/partnership_onboard.sh:475:            log "Conf push complete ✅"
/boot/config/plugins/varaverk/Partnership/partnership_onboard.sh:517:    log "Docker network ready on $MIRROR ✅"
/boot/config/plugins/varaverk/Fallback/fallback.sh:398:            log "$ICON_STARTED $container started and running ✅"
/boot/config/plugins/varaverk/Fallback/fallback.sh:422:        log "$container already stopped locally"
/boot/config/plugins/varaverk/Fallback/fallback.sh:431:        log "$ICON_STOPPED $container stopped" || \
/boot/config/plugins/varaverk/Fallback/fallback.sh:463:            log "$ICON_STARTED $container started on $REMOTE_SERVER_NAME ✅"
/boot/config/plugins/varaverk/Fallback/fallback.sh:489:        log "$container already stopped on $REMOTE_SERVER_NAME"
/boot/config/plugins/varaverk/Fallback/fallback.sh:500:        log "$ICON_STOPPED $container stopped on $REMOTE_SERVER_NAME" || \
/boot/config/plugins/varaverk/Fallback/fallback.sh:1045:            log "$ICON_FALLBACK FALLBACK — ${ELAPSED_MIN}min — T2:$(state_get tier2_started) T3:$(state_get tier3_started) T4:$(state_get tier4_started)"
/boot/config/plugins/varaverk/Partnership/ssh_setup.sh:144:            log "${KEY_CONF_VAR} updated in $(basename "$HOST_CONF") ✅" || \
/boot/config/plugins/varaverk/Partnership/ssh_setup.sh:240:            log "SSH validate — auth restored to $REMOTE_SERVER_NAME ✅ (strikes reset)"
/boot/config/plugins/varaverk/Partnership/ssh_setup.sh:242:            log "SSH validate — $REMOTE_SERVER_NAME SSH auth OK ✅"
/boot/config/plugins/varaverk/Partnership/ssh_setup.sh:304:            log "Keypair generated ✅" || {
/boot/config/plugins/varaverk/Partnership/partnership_transfer.sh:299:        log "master.conf pushed to $NEW_OWNER ✅" || \
/boot/config/plugins/varaverk/Partnership/onboard_cancel.sh:76:        log "No local public key — nothing to remove from $MIRROR"
/boot/config/plugins/varaverk/Partnership/onboard_cancel.sh:93:                log "HOST1 key removed from $MIRROR authorized_keys ✅"
/boot/config/plugins/varaverk/Partnership/onboard_cancel.sh:105:        rm -f "$SSH_KEY" "$SSH_KEY_PUB" && log "Local key pair deleted ✅" || \
/boot/config/plugins/varaverk/Partnership/onboard_cancel.sh:113:            log "Phase flags cleared from local setup.db ✅"
/boot/config/plugins/varaverk/Partnership/onboard_cancel.sh:126:        log "No authorized_keys on HOST1 — nothing to remove"
/boot/config/plugins/varaverk/Partnership/onboard_cancel.sh:129:        log "$MIRROR key not found in HOST1 authorized_keys (already removed or never added)"
/boot/config/plugins/varaverk/Partnership/onboard_cancel.sh:136:            log "$MIRROR key removed from HOST1 authorized_keys ✅"
/boot/config/plugins/varaverk/Partnership/gitea_ssh_setup.sh:208:    log "API token valid ✅"
/boot/config/plugins/varaverk/Partnership/gitea_ssh_setup.sh:439:    log "SSH config entry added: Host gitea-${MY_ID,,} ✅"
/boot/config/plugins/varaverk/Partnership/partnership_offboard.sh:208:        log "Emby admin '$username' removed ✅"
/boot/config/plugins/varaverk/Partnership/partnership_offboard.sh:246:        log "Rsync stopped ✅"
/boot/config/plugins/varaverk/Partnership/partnership_offboard.sh:382:    log "Rsync stopped ✅"
/boot/config/plugins/varaverk/Partnership/partnership_offboard.sh:477:    log "Local state: INACTIVE ✅"
/boot/config/plugins/varaverk/Fallback/fallback_test.sh:337:            log "State changed to FALLBACK — outage detected correctly ✅"
/boot/config/plugins/varaverk/Fallback/fallback_test.sh:367:            log "$ICON_RUNNING $container is running locally ✅"
/boot/config/plugins/varaverk/Fallback/fallback_test.sh:397:        log "$REMOTE_SERVER_NAME is reachable again ✅"
/boot/config/plugins/varaverk/Fallback/fallback_test.sh:423:            log "State returned to NORMAL — handback completed ✅"
/boot/config/plugins/varaverk/Fallback/fallback_test.sh:453:            log "$ICON_NOT_RUNNING $container stopped locally — handed back ✅"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:360:        log "State file pushed to remote ✅" || \
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:436:        log "Blocklist empty — nothing to remove"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:469:                log "Our pubkey revoked from $REMOTE_SERVER_NAME ✅"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:494:                log "$REMOTE_SERVER_NAME pubkey revoked locally ✅"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:500:            log "$REMOTE_SERVER_NAME pubkey not found in local authorized_keys — already removed"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:504:        log "/root/.ssh/authorized_keys not found — nothing to remove locally"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:571:            log "$container started ✅"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:613:                log "$container removed ✅" || warn "$container rm failed"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:626:        rm -rf "$path" && log "  Appdata removed: $path ✅" || warn "  Failed to remove: $path"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:670:            log "$container removed from $MIRROR ✅" || \
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:679:                log "  Appdata removed on $MIRROR: $path ✅" || \
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:713:            log "$container started on $MIRROR ✅" || \
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:804:        log "Emby admin '$username' created (id: $user_id) ✅"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:815:        log "$username granted admin policy ✅" || \
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:870:        log "Emby admin '$username' removed ✅"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:925:        log "master.conf updated: ${key}=${value}"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:1171:            log "PARTNERSHIP_ENABLED=true in master.conf ✅"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:1172:            platform_push_conf | while IFS= read -r line; do log "$line"; done
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:1187:            log "${MY_ID}_LOCAL_DONE=true written to setup.db ✅"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:1262:                log "$container reachable at http://${OWNER_IP}:${port}/ ✅"
/boot/config/plugins/varaverk/Partnership/partnership_manager.sh:1441:            log "Remote master.conf updated ✅" || \


Let me read the rsync.sh and fallback.sh sections before editing:


328	    if [[ "$RSYNC_EXIT" -eq 0 ]]; then
329	        # Parse bytes transferred from --stats output
330	        BYTES_TRANSFERRED=$(echo "$RSYNC_OUTPUT" | \
331	            awk '/Total transferred file size:/{gsub(/,/,"",$NF); gsub(/[^0-9]/,"",$NF); print $NF+0}')
332	        BYTES_TRANSFERRED="${BYTES_TRANSFERRED:-0}"
333
334	        log "$ICON_DONE Rsync complete — $BYTES_TRANSFERRED bytes transferred"
335	        RSYNC_SUCCESS=true
336	        break
337	    else
338	        warn "$ICON_RETRY Rsync failed (attempt $ATTEMPT/$RETRY_COUNT)"
339	        log "Exit code: $RSYNC_EXIT"
340	        if [[ "$ATTEMPT" -lt "$RETRY_COUNT" ]]; then
341	            log "Retrying in ${SLEEP}s..."
342	            sleep "$SLEEP"
343	        fi
344	    fi
345	done
346
347	# ==============================================================================================

    if [[ "$RSYNC_EXIT" -eq 0 ]]; then
        # Parse bytes transferred from --stats output
        BYTES_TRANSFERRED=$(echo "$RSYNC_OUTPUT" | \
            awk '/Total transferred file size:/{gsub(/,/,"",$NF); gsub(/[^0-9]/,"",$NF); print $NF+0}')
        BYTES_TRANSFERRED="${BYTES_TRANSFERRED:-0}"

        log "$ICON_DONE Rsync complete — $BYTES_TRANSFERRED bytes transferred"
        RSYNC_SUCCESS=true
        break
    else
        warn "$ICON_RETRY Rsync failed (attempt $ATTEMPT/$RETRY_COUNT)"
        log "Exit code: $RSYNC_EXIT"
        if [[ "$ATTEMPT" -lt "$RETRY_COUNT" ]]; then
            log "Retrying in ${SLEEP}s..."
            sleep "$SLEEP"
        fi
    fi
done

# ==============================================================================================


398	        if [[ "$DRY_RUN" == true ]]; then
399	            warn "DRY RUN — would restart $container on $REMOTE_SERVER_NAME"
400	            continue
401	        fi
402
403	        timeout 15 ssh -i "$SSH_KEY" -o ConnectTimeout=10 root@"$REMOTE_SERVER" \
404	            "docker restart $container" >/dev/null 2>&1 && \
405	            log "$ICON_STARTED $container restarted on $REMOTE_SERVER_NAME ✅" || \
406	            warn "Failed to restart $container on $REMOTE_SERVER_NAME"
407	    done
408	fi
409
410	END=$(date +%s)
411	DURATION=$(( END - START ))
412

        if [[ "$DRY_RUN" == true ]]; then
            warn "DRY RUN — would restart $container on $REMOTE_SERVER_NAME"
            continue
        fi

        timeout 15 ssh -i "$SSH_KEY" -o ConnectTimeout=10 root@"$REMOTE_SERVER" \
            "docker restart $container" >/dev/null 2>&1 && \
            log "$ICON_STARTED $container restarted on $REMOTE_SERVER_NAME ✅" || \
            warn "Failed to restart $container on $REMOTE_SERVER_NAME"
    done
fi

END=$(date +%s)
DURATION=$(( END - START ))


390	        return 0
391	    fi
392	    if timeout "$DOCKER_TIMEOUT" docker start "$container" >/dev/null 2>&1; then
393	        sleep "$CONTAINER_VERIFY_WAIT"
394	        local post_status
395	        post_status=$(timeout "$DOCKER_TIMEOUT" docker inspect -f '{{.State.Running}}' \
396	            "$container" 2>/dev/null)
397	        if [[ "$post_status" == "true" ]]; then
398	            log "$ICON_STARTED $container started and running ✅"
399	            return 0
400	        else
401	            error "$container started but crashed immediately"
402	            notify "$container failed to stay running during fallback on $(hostname)" \
403	                "Fallback" "warning"
404	            return 1
405	        fi
406	    else
407	        error "Failed to start $container locally"
408	        notify "Failed to start $container locally during fallback on $(hostname)" \
409	            "Fallback" "warning"
410	        return 1
411	    fi
412	}
413
414	# Stop a container locally
415	local_stop() {
416	    local container="$1"
417	    [[ -z "$container" ]] && return
418	    local status
419	    status=$(timeout "$DOCKER_TIMEOUT" docker inspect -f '{{.State.Running}}' \
420	        "$container" 2>/dev/null)
421	    if [[ "$status" != "true" ]]; then
422	        log "$container already stopped locally"
423	        return 0
424	    fi
425	    log "$ICON_STOP Stopping $container locally..."
426	    if [[ "$DRY_RUN" == true ]]; then
427	        warn "DRY RUN — would stop $container locally"
428	        return 0
429	    fi
430	    timeout "$DOCKER_TIMEOUT" docker stop "$container" >/dev/null 2>&1 && \
431	        log "$ICON_STOPPED $container stopped" || \
432	        error "Failed to stop $container locally"
433	}
434
435	# Start a container on remote via SSH and verify it came up
436	remote_start() {
437	    local container="$1"
438	    [[ -z "$container" ]] && return
439	    local status
440	    status=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" -o ConnectTimeout="$SSH_TIMEOUT" \
441	        root@"$REMOTE_SERVER" \
442	        "timeout $DOCKER_TIMEOUT docker inspect -f '{{.State.Running}}' \
443	        $container 2>/dev/null" 2>/dev/null)
444	    if [[ "$status" == "true" ]]; then
445	        log "$container already running on $REMOTE_SERVER_NAME"
446	        return 0
447	    fi
448	    log "$ICON_START Starting $container on $REMOTE_SERVER_NAME..."
449	    if [[ "$DRY_RUN" == true ]]; then
450	        warn "DRY RUN — would start $container on $REMOTE_SERVER_NAME"
451	        return 0
452	    fi
453	    if timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" -o ConnectTimeout="$SSH_TIMEOUT" \
454	        root@"$REMOTE_SERVER" \
455	        "timeout $DOCKER_TIMEOUT docker start $container" >/dev/null 2>&1; then
456	        sleep "$CONTAINER_VERIFY_WAIT"
457	        local post_status
458	        post_status=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
459	            -o ConnectTimeout="$SSH_TIMEOUT" root@"$REMOTE_SERVER" \
460	            "timeout $DOCKER_TIMEOUT docker inspect -f '{{.State.Running}}' \
461	            $container 2>/dev/null" 2>/dev/null)
462	        if [[ "$post_status" == "true" ]]; then
463	            log "$ICON_STARTED $container started on $REMOTE_SERVER_NAME ✅"
464	            return 0
465	        else
466	            error "$container started on $REMOTE_SERVER_NAME but crashed immediately"
467	            notify "$container failed after start on $REMOTE_SERVER_NAME during handback on $(hostname)" \
468	                "Fallback" "warning"
469	            return 1
470	        fi
471	    else
472	        error "Failed to start $container on $REMOTE_SERVER_NAME"
473	        notify "Failed to start $container on $REMOTE_SERVER_NAME during handback on $(hostname)" \
474	            "Fallback" "warning"
475	        return 1
476	    fi
477	}
478
479	# Stop a container on remote via SSH
480	remote_stop() {
481	    local container="$1"
482	    [[ -z "$container" ]] && return
483	    local status
484	    status=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" -o ConnectTimeout="$SSH_TIMEOUT" \
485	        root@"$REMOTE_SERVER" \
486	        "timeout $DOCKER_TIMEOUT docker inspect -f '{{.State.Running}}' \
487	        $container 2>/dev/null" 2>/dev/null)
488	    if [[ "$status" != "true" ]]; then
489	        log "$container already stopped on $REMOTE_SERVER_NAME"
490	        return 0
491	    fi
492	    log "$ICON_STOP Stopping $container on $REMOTE_SERVER_NAME..."
493	    if [[ "$DRY_RUN" == true ]]; then
494	        warn "DRY RUN — would stop $container on $REMOTE_SERVER_NAME"
495	        return 0
496	    fi
497	    timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" -o ConnectTimeout="$SSH_TIMEOUT" \
498	        root@"$REMOTE_SERVER" \
499	        "timeout $DOCKER_TIMEOUT docker stop $container" >/dev/null 2>&1 && \
500	        log "$ICON_STOPPED $container stopped on $REMOTE_SERVER_NAME" || \
501	        error "Failed to stop $container on $REMOTE_SERVER_NAME"
502	}
503
504	# ==============================================================================================
505	# ── DDNS HELPERS ──────────────────────────────────────────────────────────────────────────────
506	# ==============================================================================================
507	# DDNS managed exclusively by this script — never by network state alone.
508	# Stopping DDNS on internet loss prevents split-brain DNS updates.
509

        return 0
    fi
    if timeout "$DOCKER_TIMEOUT" docker start "$container" >/dev/null 2>&1; then
        sleep "$CONTAINER_VERIFY_WAIT"
        local post_status
        post_status=$(timeout "$DOCKER_TIMEOUT" docker inspect -f '{{.State.Running}}' \
            "$container" 2>/dev/null)
        if [[ "$post_status" == "true" ]]; then
            log "$ICON_STARTED $container started and running ✅"
            return 0
        else
            error "$container started but crashed immediately"
            notify "$container failed to stay running during fallback on $(hostname)" \
                "Fallback" "warning"
            return 1
        fi
    else
        error "Failed to start $container locally"
        notify "Failed to start $container locally during fallback on $(hostname)" \
            "Fallback" "warning"
        return 1
    fi
}

# Stop a container locally
local_stop() {
    local container="$1"
    [[ -z "$container" ]] && return
    local status
    status=$(timeout "$DOCKER_TIMEOUT" docker inspect -f '{{.State.Running}}' \
        "$container" 2>/dev/null)
    if [[ "$status" != "true" ]]; then
        log "$container already stopped locally"
        return 0
    fi
    log "$ICON_STOP Stopping $container locally..."
    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would stop $container locally"
        return 0
    fi
    timeout "$DOCKER_TIMEOUT" docker stop "$container" >/dev/null 2>&1 && \
        log "$ICON_STOPPED $container stopped" || \
        error "Failed to stop $container locally"
}

# Start a container on remote via SSH and verify it came up
remote_start() {
    local container="$1"
    [[ -z "$container" ]] && return
    local status
    status=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" -o ConnectTimeout="$SSH_TIMEOUT" \
        root@"$REMOTE_SERVER" \
        "timeout $DOCKER_TIMEOUT docker inspect -f '{{.State.Running}}' \
        $container 2>/dev/null" 2>/dev/null)
    if [[ "$status" == "true" ]]; then
        log "$container already running on $REMOTE_SERVER_NAME"
        return 0
    fi
    log "$ICON_START Starting $container on $REMOTE_SERVER_NAME..."
    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would start $container on $REMOTE_SERVER_NAME"
        return 0
    fi
    if timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" -o ConnectTimeout="$SSH_TIMEOUT" \
        root@"$REMOTE_SERVER" \
        "timeout $DOCKER_TIMEOUT docker start $container" >/dev/null 2>&1; then
        sleep "$CONTAINER_VERIFY_WAIT"
        local post_status
        post_status=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
            -o ConnectTimeout="$SSH_TIMEOUT" root@"$REMOTE_SERVER" \
            "timeout $DOCKER_TIMEOUT docker inspect -f '{{.State.Running}}' \
            $container 2>/dev/null" 2>/dev/null)
        if [[ "$post_status" == "true" ]]; then
            log "$ICON_STARTED $container started on $REMOTE_SERVER_NAME ✅"
            return 0
        else
            error "$container started on $REMOTE_SERVER_NAME but crashed immediately"
            notify "$container failed after start on $REMOTE_SERVER_NAME during handback on $(hostname)" \
                "Fallback" "warning"
            return 1
        fi
    else
        error "Failed to start $container on $REMOTE_SERVER_NAME"
        notify "Failed to start $container on $REMOTE_SERVER_NAME during handback on $(hostname)" \
            "Fallback" "warning"
        return 1
    fi
}

# Stop a container on remote via SSH
remote_stop() {
    local container="$1"
    [[ -z "$container" ]] && return
    local status
    status=$(timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" -o ConnectTimeout="$SSH_TIMEOUT" \
        root@"$REMOTE_SERVER" \
        "timeout $DOCKER_TIMEOUT docker inspect -f '{{.State.Running}}' \
        $container 2>/dev/null" 2>/dev/null)
    if [[ "$status" != "true" ]]; then
        log "$container already stopped on $REMOTE_SERVER_NAME"
        return 0
    fi
    log "$ICON_STOP Stopping $container on $REMOTE_SERVER_NAME..."
    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would stop $container on $REMOTE_SERVER_NAME"
        return 0
    fi
    timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" -o ConnectTimeout="$SSH_TIMEOUT" \
        root@"$REMOTE_SERVER" \
        "timeout $DOCKER_TIMEOUT docker stop $container" >/dev/null 2>&1 && \
        log "$ICON_STOPPED $container stopped on $REMOTE_SERVER_NAME" || \
        error "Failed to stop $container on $REMOTE_SERVER_NAME"
}

# ==============================================================================================
# ── DDNS HELPERS ──────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# DDNS managed exclusively by this script — never by network state alone.
# Stopping DDNS on internet loss prevents split-brain DNS updates.


Edit

log "$ICON_DONE Rsync complete — $BYTES_TRANSFERRED bytes transferred"

echo "$ICON_DONE Rsync complete — $BYTES_TRANSFERRED bytes transferred"


log "$ICON_STARTED $container restarted on $REMOTE_SERVER_NAME ✅" || \

echo "$ICON_STARTED $container restarted on $REMOTE_SERVER_NAME ✅" || \


The file /boot/config/plugins/varaverk/Rsync/rsync.sh has been updated successfully. (file state is current in your context — no need to Read it back)

awk '/Total transferred file size:/{gsub(/,/,"",$NF); gsub(/[^0-9]/,"",$NF); print $NF+0}')

-        log "$ICON_DONE Rsync complete — $BYTES_TRANSFERRED bytes transferred"

+        echo "$ICON_DONE Rsync complete — $BYTES_TRANSFERRED bytes transferred"


timeout 15 ssh -i "$SSH_KEY" -o ConnectTimeout=10 root@"$REMOTE_SERVER" \

"docker restart $container" >/dev/null 2>&1 && \

-            log "$ICON_STARTED $container restarted on $REMOTE_SERVER_NAME ✅" || \

+            echo "$ICON_STARTED $container restarted on $REMOTE_SERVER_NAME ✅" || \

warn "Failed to restart $container on $REMOTE_SERVER_NAME"


log "$ICON_STARTED $container started and running ✅"

echo "$ICON_STARTED $container started and running ✅"


log "$ICON_STOPPED $container stopped" || \

echo "$ICON_STOPPED $container stopped" || \


log "$ICON_STARTED $container started on $REMOTE_SERVER_NAME ✅"

echo "$ICON_STARTED $container started on $REMOTE_SERVER_NAME ✅"


log "$ICON_STOPPED $container stopped on $REMOTE_SERVER_NAME" || \

echo "$ICON_STOPPED $container stopped on $REMOTE_SERVER_NAME" || \


The file /boot/config/plugins/varaverk/Fallback/fallback.sh has been updated successfully. (file state is current in your context — no need to Read it back)

post_status=$(timeout "$DOCKER_TIMEOUT" docker inspect -f '{{.State.Running}}' \

-            log "$ICON_STARTED $container started and running ✅"

+            echo "$ICON_STARTED $container started and running ✅"

error "$container started but crashed immediately"


timeout "$DOCKER_TIMEOUT" docker stop "$container" >/dev/null 2>&1 && \

-        log "$ICON_STOPPED $container stopped" || \

+        echo "$ICON_STOPPED $container stopped" || \

error "Failed to stop $container locally"


"timeout $DOCKER_TIMEOUT docker inspect -f '{{.State.Running}}' \

-            log "$ICON_STARTED $container started on $REMOTE_SERVER_NAME ✅"

+            echo "$ICON_STARTED $container started on $REMOTE_SERVER_NAME ✅"

error "$container started on $REMOTE_SERVER_NAME but crashed immediately"


timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" -o ConnectTimeout="$SSH_TIMEOUT" \

"timeout $DOCKER_TIMEOUT docker stop $container" >/dev/null 2>&1 && \

-        log "$ICON_STOPPED $container stopped on $REMOTE_SERVER_NAME" || \

+        echo "$ICON_STOPPED $container stopped on $REMOTE_SERVER_NAME" || \

error "Failed to stop $container on $REMOTE_SERVER_NAME"


Now Partnership — reading key sections to confirm context before editing:


Bash

grep -n 'log ".*✅\|log ".*nothing\|log ".*removed\|log ".*created\|log ".*restarted\|log ".*started\|log ".*stopped\|log ".*mounted\|log ".*applied\|log ".*updated\|log ".*pushed\|log ".*pulled\|log ".*verified\|log ".*flushed\|log ".*placed\|log ".*complete\|log ".*established\|log ".*cleared\|log ".*resolved\|log ".*revoked\|log ".*deleted\|log ".*ready\|log ".*triggered\|log ".*added\|log ".*valid\|log ".*generated\|log ".*written\|log ".*INACTIVE\|log ".*restored\|log ".*reachable' \
  /boot/config/plugins/varaverk/Partnership/partnership_manager.sh | grep -v "^Binary"

Find log→echo candidates in partnership_manager.sh


360:        log "State file pushed to remote ✅" || \
429:        log "$hostname already on blocklist"
436:        log "Blocklist empty — nothing to remove"
469:                log "Our pubkey revoked from $REMOTE_SERVER_NAME ✅"
494:                log "$REMOTE_SERVER_NAME pubkey revoked locally ✅"
500:            log "$REMOTE_SERVER_NAME pubkey not found in local authorized_keys — already removed"
504:        log "/root/.ssh/authorized_keys not found — nothing to remove locally"
571:            log "$container started ✅"
613:                log "$container removed ✅" || warn "$container rm failed"
626:        rm -rf "$path" && log "  Appdata removed: $path ✅" || warn "  Failed to remove: $path"
670:            log "$container removed from $MIRROR ✅" || \
679:                log "  Appdata removed on $MIRROR: $path ✅" || \
713:            log "$container started on $MIRROR ✅" || \
804:        log "Emby admin '$username' created (id: $user_id) ✅"
815:        log "$username granted admin policy ✅" || \
870:        log "Emby admin '$username' removed ✅"
925:        log "master.conf updated: ${key}=${value}"
1093:        log "Partnership check — remote unreachable, skipping state check"
1171:            log "PARTNERSHIP_ENABLED=true in master.conf ✅"
1187:            log "${MY_ID}_LOCAL_DONE=true written to setup.db ✅"
1262:                log "$container reachable at http://${OWNER_IP}:${port}/ ✅"
1441:            log "Remote master.conf updated ✅" || \


355	        warn "DRY RUN — would push state file to remote"
356	        return 0
357	    fi
358	    timeout "$SSH_TIMEOUT" scp -i "$ssh_key" -o ConnectTimeout="$SSH_TIMEOUT" \
359	        "$local_file" "root@${remote_ip}:${local_file}" 2>/dev/null && \
360	        log "State file pushed to remote ✅" || \
361	        warn "Could not push state file to remote — will propagate on next sync"
362	}

        warn "DRY RUN — would push state file to remote"
        return 0
    fi
    timeout "$SSH_TIMEOUT" scp -i "$ssh_key" -o ConnectTimeout="$SSH_TIMEOUT" \
        "$local_file" "root@${remote_ip}:${local_file}" 2>/dev/null && \
        log "State file pushed to remote ✅" || \
        warn "Could not push state file to remote — will propagate on next sync"
}


607	            continue
608	        fi
609	        if timeout "${DOCKER_TIMEOUT:-30}" docker inspect "$container" >/dev/null 2>&1; then
610	            timeout "${DOCKER_TIMEOUT:-30}" docker stop "$container" >/dev/null 2>&1 || true
611	            _PM_TRAP_STOPPED+=("$container")
612	            timeout "${DOCKER_TIMEOUT:-30}" docker rm   "$container" >/dev/null 2>&1 && \
613	                log "$container removed ✅" || warn "$container rm failed"
614	        else

            continue
        fi
        if timeout "${DOCKER_TIMEOUT:-30}" docker inspect "$container" >/dev/null 2>&1; then
            timeout "${DOCKER_TIMEOUT:-30}" docker stop "$container" >/dev/null 2>&1 || true
            _PM_TRAP_STOPPED+=("$container")
            timeout "${DOCKER_TIMEOUT:-30}" docker rm   "$container" >/dev/null 2>&1 && \
                log "$container removed ✅" || warn "$container rm failed"
        else


622	        if [[ "$DRY_RUN" == true ]]; then
623	            warn "  DRY RUN — would rm -rf $path"
624	            continue
625	        fi
626	        rm -rf "$path" && log "  Appdata removed: $path ✅" || warn "  Failed to remove: $path"
627	    done <<< "$all_appdata_paths"

        if [[ "$DRY_RUN" == true ]]; then
            warn "  DRY RUN — would rm -rf $path"
            continue
        fi
        rm -rf "$path" && log "  Appdata removed: $path ✅" || warn "  Failed to remove: $path"
    done <<< "$all_appdata_paths"


665	        timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
666	            -o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
667	            "docker stop '$container' >/dev/null 2>&1
668	             docker rm   '$container' >/dev/null 2>&1 && echo removed" 2>/dev/null | \
669	            grep -q removed && \
670	            log "$container removed from $MIRROR ✅" || \
671	            warn "Failed to remove $container from $MIRROR"
672
673	        # Delete appdata on remote after container removal
674	        while IFS= read -r path; do
675	            [[ -z "$path" ]] && continue
676	            timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
677	                -o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
678	                "rm -rf '$path' && echo removed" 2>/dev/null | grep -q removed && \
679	                log "  Appdata removed on $MIRROR: $path ✅" || \
680	                warn "  Failed to remove appdata on $MIRROR: $path"
681	        done <<< "$appdata_paths"
682	    done <<< "$container_list"
683	}
684

        timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
            -o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
            "docker stop '$container' >/dev/null 2>&1
             docker rm   '$container' >/dev/null 2>&1 && echo removed" 2>/dev/null | \
            grep -q removed && \
            log "$container removed from $MIRROR ✅" || \
            warn "Failed to remove $container from $MIRROR"

        # Delete appdata on remote after container removal
        while IFS= read -r path; do
            [[ -z "$path" ]] && continue
            timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
                -o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
                "rm -rf '$path' && echo removed" 2>/dev/null | grep -q removed && \
                log "  Appdata removed on $MIRROR: $path ✅" || \
                warn "  Failed to remove appdata on $MIRROR: $path"
        done <<< "$appdata_paths"
    done <<< "$container_list"
}


708	        fi
709	        timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
710	            -o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
711	            "docker start '$container' >/dev/null 2>&1 && echo started" 2>/dev/null | \
712	            grep -q started && \
713	            log "$container started on $MIRROR ✅" || \
714	            warn "$container failed to start on $MIRROR — check manually"
715	    done
716	}
717
718	# Create the mirror's Emby admin account on the owner's deployed Emby.
719	# Credentials come from the MIRROR's conf (HOST*_PARTNERSHIP_EMBY_ADMIN_USER/PASS).

        fi
        timeout "$SSH_TIMEOUT" ssh -i "$SSH_KEY" \
            -o ConnectTimeout="$SSH_TIMEOUT" root@"$mirror_ip" \
            "docker start '$container' >/dev/null 2>&1 && echo started" 2>/dev/null | \
            grep -q started && \
            log "$container started on $MIRROR ✅" || \
            warn "$container failed to start on $MIRROR — check manually"
    done
}

# Create the mirror's Emby admin account on the owner's deployed Emby.
# Credentials come from the MIRROR's conf (HOST*_PARTNERSHIP_EMBY_ADMIN_USER/PASS).


810	        -X POST \
811	        -H "X-Emby-Authorization: MediaBrowser Token=\"$EMBY_API_KEY\"" \
812	        -H "Content-Type: application/json" \
813	        -d '{"IsAdministrator": true, "IsDisabled": false}' \
814	        "${emby_url}/Users/${user_id}/Policy" 2>/dev/null && \
815	        log "$username granted admin policy ✅" || \
816	        warn "Could not set admin policy — grant manually in Emby dashboard"
817	}
818
819	# Delete the mirror's Emby admin account from the owner's deployed Emby.
820	revoke_emby_admin() {
821	    local mirror_ip="$1"

        -X POST \
        -H "X-Emby-Authorization: MediaBrowser Token=\"$EMBY_API_KEY\"" \
        -H "Content-Type: application/json" \
        -d '{"IsAdministrator": true, "IsDisabled": false}' \
        "${emby_url}/Users/${user_id}/Policy" 2>/dev/null && \
        log "$username granted admin policy ✅" || \
        warn "Could not set admin policy — grant manually in Emby dashboard"
}

# Delete the mirror's Emby admin account from the owner's deployed Emby.
revoke_emby_admin() {
    local mirror_ip="$1"


1165	            local _master_conf="${SCRIPTS_ROOT:-$(dirname "$SCRIPT_DIR")}/master.conf"
1166	            if grep -q "^[[:space:]]*PARTNERSHIP_ENABLED=" "$_master_conf" 2>/dev/null; then
1167	                sed -i "s|^[[:space:]]*PARTNERSHIP_ENABLED=.*|PARTNERSHIP_ENABLED=true|" "$_master_conf"
1168	            else
1169	                echo "PARTNERSHIP_ENABLED=true" >> "$_master_conf"
1170	            fi
1171	            log "PARTNERSHIP_ENABLED=true in master.conf ✅"
1172	            platform_push_conf | while IFS= read -r line; do log "$line"; done
1173	        else
1174	            warn "DRY RUN — would set PARTNERSHIP_ENABLED=true in master.conf and push"
1175	        fi
1176
1177	        # Write HOST1_LOCAL_DONE flag to setup.db
1178	        local_state_file="$(platform_setup_db_path)"
1179	        if [[ "$DRY_RUN" == false ]]; then
1180	            flag_key="${MY_ID}_LOCAL_DONE"
1181	            if grep -q "^${flag_key}=" "$local_state_file" 2>/dev/null; then
1182	                sed -i "s|^${flag_key}=.*|${flag_key}=true|" "$local_state_file"
1183	            else
1184	                echo "${flag_key}=true" >> "$local_state_file"
1185	            fi
1186	            platform_push_setup_state
1187	            log "${MY_ID}_LOCAL_DONE=true written to setup.db ✅"
1188	        else
1189	            warn "DRY RUN — would write ${MY_ID}_LOCAL_DONE=true"
1190	        fi
1191
1192	        echo ""
1193	        echo "HOST1 local setup complete. Waiting for $MIRROR_ID ($MIRROR) to onboard."
1194	        exit 0
1195	    fi
1196
1197	    # ── FULL ONBOARD PATH (requires HOST2) ────────────────────────────────────
1198
1199	    # Check already onboarded

            local _master_conf="${SCRIPTS_ROOT:-$(dirname "$SCRIPT_DIR")}/master.conf"
            if grep -q "^[[:space:]]*PARTNERSHIP_ENABLED=" "$_master_conf" 2>/dev/null; then
                sed -i "s|^[[:space:]]*PARTNERSHIP_ENABLED=.*|PARTNERSHIP_ENABLED=true|" "$_master_conf"
            else
                echo "PARTNERSHIP_ENABLED=true" >> "$_master_conf"
            fi
            log "PARTNERSHIP_ENABLED=true in master.conf ✅"
            platform_push_conf | while IFS= read -r line; do log "$line"; done
        else
            warn "DRY RUN — would set PARTNERSHIP_ENABLED=true in master.conf and push"
        fi

        # Write HOST1_LOCAL_DONE flag to setup.db
        local_state_file="$(platform_setup_db_path)"
        if [[ "$DRY_RUN" == false ]]; then
            flag_key="${MY_ID}_LOCAL_DONE"
            if grep -q "^${flag_key}=" "$local_state_file" 2>/dev/null; then
                sed -i "s|^${flag_key}=.*|${flag_key}=true|" "$local_state_file"
            else
                echo "${flag_key}=true" >> "$local_state_file"
            fi
            platform_push_setup_state
            log "${MY_ID}_LOCAL_DONE=true written to setup.db ✅"
        else
            warn "DRY RUN — would write ${MY_ID}_LOCAL_DONE=true"
        fi

        echo ""
        echo "HOST1 local setup complete. Waiting for $MIRROR_ID ($MIRROR) to onboard."
        exit 0
    fi

    # ── FULL ONBOARD PATH (requires HOST2) ────────────────────────────────────

    # Check already onboarded


1436	        # Update remote master.conf
1437	        timeout "$SSH_TIMEOUT" ssh -i "$NEW_MIRROR_SSH_KEY" \
1438	            -o ConnectTimeout="$SSH_TIMEOUT" root@"$NEW_MIRROR_IP" \
1439	            "sed -i 's|^[[:space:]]*PARTNERSHIP_OWNER_HOST=.*|    PARTNERSHIP_OWNER_HOST=\"$NEW_OWNER_ID\"|' \
1440	            '$SCRIPT_DIR/../master.conf'" 2>/dev/null && \
1441	            log "Remote master.conf updated ✅" || \
1442	            error "Failed to update remote master.conf — update manually"
1443	    else
1444	        warn "DRY RUN — would set PARTNERSHIP_OWNER_HOST=$NEW_OWNER_ID on both servers"
1445	    fi
1446
1447	    # Write state files

        # Update remote master.conf
        timeout "$SSH_TIMEOUT" ssh -i "$NEW_MIRROR_SSH_KEY" \
            -o ConnectTimeout="$SSH_TIMEOUT" root@"$NEW_MIRROR_IP" \
            "sed -i 's|^[[:space:]]*PARTNERSHIP_OWNER_HOST=.*|    PARTNERSHIP_OWNER_HOST=\"$NEW_OWNER_ID\"|' \
            '$SCRIPT_DIR/../master.conf'" 2>/dev/null && \
            log "Remote master.conf updated ✅" || \
            error "Failed to update remote master.conf — update manually"
    else
        warn "DRY RUN — would set PARTNERSHIP_OWNER_HOST=$NEW_OWNER_ID on both servers"
    fi

    # Write state files


Now editing partnership_manager.sh in one go with perl for unicode safety:


perl -i -pe '
  s/        log "State file pushed to remote ✅"/        echo "State file pushed to remote ✅"/;
  s/                log "Our pubkey revoked from \$REMOTE_SERVER_NAME ✅"/                echo "Our pubkey revoked from \$REMOTE_SERVER_NAME ✅"/;
  s/                log "\$REMOTE_SERVER_NAME pubkey revoked locally ✅"/                echo "\$REMOTE_SERVER_NAME pubkey revoked locally ✅"/;
  s/                log "\$container removed ✅" \|\| warn "\$container rm failed"/                echo "\$container removed ✅" || warn "\$container rm failed"/;
  s/        rm -rf "\$path" && log "  Appdata removed: \$path ✅"/        rm -rf "\$path" \&\& echo "  Appdata removed: \$path ✅"/;
  s/            log "\$container removed from \$MIRROR ✅" \|\| \\/            echo "\$container removed from \$MIRROR ✅" || \\/;
  s/                log "  Appdata removed on \$MIRROR: \$path ✅" \|\| \\/                echo "  Appdata removed on \$MIRROR: \$path ✅" || \\/;
  s/            log "\$container started on \$MIRROR ✅" \|\| \\/            echo "\$container started on \$MIRROR ✅" || \\/;
  s/            log "\$container started ✅"/            echo "\$container started ✅"/;
  s/        log "Emby admin '"'"'\$username'"'"' created \(id: \$user_id\) ✅"/        echo "Emby admin '"'"'\$username'"'"' created (id: \$user_id) ✅"/;
  s/        log "\$username granted admin policy ✅" \|\| \\/        echo "\$username granted admin policy ✅" || \\/;
  s/        log "Emby admin '"'"'\$username'"'"' removed ✅"/        echo "Emby admin '"'"'\$username'"'"' removed ✅"/;
  s/            log "master\.conf updated: \${key}=\${value}"/            echo "master.conf updated: \${key}=\${value}"/;
  s/            log "PARTNERSHIP_ENABLED=true in master\.conf ✅"/            echo "PARTNERSHIP_ENABLED=true in master.conf ✅"/;
  s/            log "\${MY_ID}_LOCAL_DONE=true written to setup\.db ✅"/            echo "\${MY_ID}_LOCAL_DONE=true written to setup.db ✅"/;
  s/                log "\$container reachable at http:\/\/\${OWNER_IP}:\${port}\/ ✅"/                echo "\$container reachable at http:\/\/\${OWNER_IP}:\${port}\/ ✅"/;
  s/            log "Remote master\.conf updated ✅" \|\| \\/            echo "Remote master.conf updated ✅" || \\/;
' /boot/config/plugins/varaverk/Partnership/partnership_manager.sh

Convert action-outcome log to echo in partnership_manager.sh


(Bash completed with no output)


grep -n 'echo "State file\|echo "Our pubkey\|echo ".*pubkey revoked\|echo ".*removed ✅\|echo "  Appdata\|echo ".*started on.*MIRROR\|echo ".*started ✅\|echo "Emby admin.*created\|echo ".*granted admin\|echo "Emby admin.*removed\|echo "master.conf updated\|echo "PARTNERSHIP_ENABLED=true\|echo ".*LOCAL_DONE.*written\|echo ".*reachable at\|echo "Remote master.conf updated' \
  /boot/config/plugins/varaverk/Partnership/partnership_manager.sh

Verify partnership_manager.sh echo conversions


360:        echo "State file pushed to remote ✅" || \
469:                echo "Our pubkey revoked from $REMOTE_SERVER_NAME ✅"
494:                echo "$REMOTE_SERVER_NAME pubkey revoked locally ✅"
571:            echo "$container started ✅"
613:                echo "$container removed ✅" || warn "$container rm failed"
626:        rm -rf "$path" && echo "  Appdata removed: $path ✅" || warn "  Failed to remove: $path"
679:                echo "  Appdata removed on $MIRROR: $path ✅" || \
713:            echo "$container started on $MIRROR ✅" || \
804:        echo "Emby admin '$username' created (id: $user_id) ✅"
815:        echo "$username granted admin policy ✅" || \
870:        echo "Emby admin '$username' removed ✅"
1169:                echo "PARTNERSHIP_ENABLED=true" >> "$_master_conf"
1171:            echo "PARTNERSHIP_ENABLED=true in master.conf ✅"
1187:            echo "${MY_ID}_LOCAL_DONE=true written to setup.db ✅"
1262:                echo "$container reachable at http://${OWNER_IP}:${port}/ ✅"
1441:            echo "Remote master.conf updated ✅" || \


Line 670 (`$container removed from $MIRROR`) wasn't changed. Let me check and fix:


668	             docker rm   '$container' >/dev/null 2>&1 && echo removed" 2>/dev/null | \
669	            grep -q removed && \
670	            echo "$container removed from $MIRROR ✅" || \
671	            warn "Failed to remove $container from $MIRROR"
672

             docker rm   '$container' >/dev/null 2>&1 && echo removed" 2>/dev/null | \
            grep -q removed && \
            echo "$container removed from $MIRROR ✅" || \
            warn "Failed to remove $container from $MIRROR"


920	    if [[ ! -f "$conf" ]]; then
921	        error "master.conf not found at $conf"
922	        return 1
923	    fi
924	    if sed -i "s|^[[:space:]]*${key}=.*|    ${key}=${value}|" "$conf" 2>/dev/null; then
925	        log "master.conf updated: ${key}=${value}"
926	        return 0
927	    else

    if [[ ! -f "$conf" ]]; then
        error "master.conf not found at $conf"
        return 1
    fi
    if sed -i "s|^[[:space:]]*${key}=.*|    ${key}=${value}|" "$conf" 2>/dev/null; then
        log "master.conf updated: ${key}=${value}"
        return 0
    else


Line 670 is already `echo` (the perl got it). Line 925 still needs fixing:


Edit

log "master.conf updated: ${key}=${value}"

echo "master.conf updated: ${key}=${value}"


The file /boot/config/plugins/varaverk/Partnership/partnership_manager.sh has been updated successfully. (file state is current in your context — no need to Read it back)

if sed -i "s|^[[:space:]]*${key}=.*|    ${key}=${value}|" "$conf" 2>/dev/null; then

-        log "master.conf updated: ${key}=${value}"

+        echo "master.conf updated: ${key}=${value}"

error "Failed to update master.conf: ${key}=${value}"


Now the other Partnership files:


Bash

perl -i -pe '
  s/            log "  \$container removed ✅" \|\| \\/            echo "  \$container removed ✅" || \\/;
  s/        log "SSH key ready ✅"/        echo "SSH key ready ✅"/;
  s/            log "Phase 2 triggered on \$OWNER ✅"/            echo "Phase 2 triggered on \$OWNER ✅"/;
  s/        log "SSH to \$MIRROR already works ✅ — skipping key install"/        echo "SSH to \$MIRROR already works ✅ — skipping key install"/;
  s/            log "SSH keys ready ✅"/            echo "SSH keys ready ✅"/;
  s/        log "SSH keys ready ✅"/        echo "SSH keys ready ✅"/;
  s/            log "Conf push complete ✅"/            echo "Conf push complete ✅"/;
  s/    log "Docker network ready on \$MIRROR ✅"/    echo "Docker network ready on \$MIRROR ✅"/;
' /boot/config/plugins/varaverk/Partnership/partnership_onboard.sh

Convert log to echo in partnership_onboard.sh


perl -i -pe '
  s/            log "\${KEY_CONF_VAR} updated in \$\(basename "\$HOST_CONF"\) ✅" \|\| \\/            echo "\${KEY_CONF_VAR} updated in \$(basename "\$HOST_CONF") ✅" || \\/;
  s/            log "SSH validate — auth restored to \$REMOTE_SERVER_NAME ✅ \(strikes reset\)"/            echo "SSH validate — auth restored to \$REMOTE_SERVER_NAME ✅ (strikes reset)"/;
  s/            log "SSH validate — \$REMOTE_SERVER_NAME SSH auth OK ✅"/            echo "SSH validate — \$REMOTE_SERVER_NAME SSH auth OK ✅"/;
  s/            log "Keypair generated ✅" \|\| \{/            echo "Keypair generated ✅" || {/;
' /boot/config/plugins/varaverk/Partnership/ssh_setup.sh

Convert log to echo in ssh_setup.sh


perl -i -pe '
  s/        log "master\.conf pushed to \$NEW_OWNER ✅" \|\| \\/        echo "master.conf pushed to \$NEW_OWNER ✅" || \\/;
' /boot/config/plugins/varaverk/Partnership/partnership_transfer.sh

perl -i -pe '
  s/                log "HOST1 key removed from \$MIRROR authorized_keys ✅"/                echo "HOST1 key removed from \$MIRROR authorized_keys ✅"/;
  s/        rm -f "\$SSH_KEY" "\$SSH_KEY_PUB" && log "Local key pair deleted ✅"/        rm -f "\$SSH_KEY" "\$SSH_KEY_PUB" \&\& echo "Local key pair deleted ✅"/;
  s/            log "Phase flags cleared from local setup\.db ✅"/            echo "Phase flags cleared from local setup.db ✅"/;
  s/            log "\$MIRROR key removed from HOST1 authorized_keys ✅"/            echo "\$MIRROR key removed from HOST1 authorized_keys ✅"/;
' /boot/config/plugins/varaverk/Partnership/onboard_cancel.sh

perl -i -pe '
  s/    log "API token valid ✅"/    echo "API token valid ✅"/;
  s/    log "SSH config entry added: Host gitea-\${MY_ID,,} ✅"/    echo "SSH config entry added: Host gitea-\${MY_ID,,} ✅"/;
' /boot/config/plugins/varaverk/Partnership/gitea_ssh_setup.sh

perl -i -pe '
  s/        log "Emby admin '"'"'\$username'"'"' removed ✅"/        echo "Emby admin '"'"'\$username'"'"' removed ✅"/;
  s/        log "Rsync stopped ✅"/        echo "Rsync stopped ✅"/g;
  s/    log "Local state: INACTIVE ✅"/    echo "Local state: INACTIVE ✅"/;
' /boot/config/plugins/varaverk/Partnership/partnership_offboard.sh

Convert log to echo in remaining Partnership files