Auth stack certs tab, arrs db fallbacks, cert monitor cache, conf parser fix
- Auth stack: fold cert monitor into Auth Stack page as fourth tab (Certs); remove standalone cert page and top-level tab - cert_monitor.sh: write JSON status cache to State_Files/cert_status.json after each run; expose per-domain days/expiry via _CERT_DAYS/_CERT_EXPIRY globals - api/cert.php: new — serves cached cert status; falls back to configured domains as UNKN when no cache exists; POST action=run triggers live check - arrs db fallbacks: vv_arr_cleanup_stats/discovery_stats/recovery_stats now read from data/*.db files when log JSON files don't yet exist - config.php vv_conf_vars(): unescape bash \$ → $ so passwords with dollar signs read correctly from conf files - host1.conf: fill in HOST1_NPM_USER/PASS and HOST1_LLDAP_USER/PASS - Partnership adapter pattern: Unraid-specific container logic extracted to Plugin/unraid/Partnership/; platform-agnostic structure stays in Partnership/ - First-run wizard: uniform multi-step flow for all hosts; HOST2 pull moved to checklist; auto SSH keygen and API key creation on save - api/checklist.php: live setup checklist with pull_master action - Fullscreen toggle: hide Unraid header/menu; state persists via localStorage
This commit is contained in:
@@ -274,8 +274,8 @@ MIRROR_STATE_FILE="${STATE_DIR:-/boot/config}/partnership_${MIRROR}.db"
|
||||
OFFLINE_COUNTER="${STATE_DIR:-/boot/config}/partnership_offline_days.db"
|
||||
|
||||
# ── Exit Trap — restart locally stopped containers if script crashes mid-cleanup ──────────────
|
||||
# Used by folderview3_remove_partner_folder() and cleanup_partner_containers() — also shared
|
||||
# with partnership_offboard.sh which sources this file and registers the same trap.
|
||||
# Used by cleanup_partner_containers() — also shared with partnership_offboard.sh which
|
||||
# sources this file and registers the same trap.
|
||||
declare -a _PM_TRAP_STOPPED=()
|
||||
_pm_trap_restart_stopped() {
|
||||
[[ ${#_PM_TRAP_STOPPED[@]} -eq 0 ]] && return
|
||||
@@ -573,166 +573,6 @@ do_ssh_key_revocation() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ── FolderView3 integration ───────────────────────────────────────────────────────────────────
|
||||
FOLDERVIEW3_DIR="/usr/local/emhttp/plugins/folder.view3"
|
||||
FOLDERVIEW3_JSON="/boot/config/plugins/folder.view3/docker.json"
|
||||
|
||||
# Derive short partner name: strip unraid- prefix (case-insensitive) if present
|
||||
derive_partner_folder_name() {
|
||||
local hostname="$1"
|
||||
local short="${hostname,,}"
|
||||
[[ "$short" == unraid-* ]] && short="${short:7}"
|
||||
# Capitalise first char for readability: jayred365 → Jayred365-fallback
|
||||
echo "${short^}-Fallback"
|
||||
}
|
||||
|
||||
folderview3_ensure_plugin() {
|
||||
if [[ -d "$FOLDERVIEW3_DIR" ]]; then
|
||||
log "FolderView3 plugin present ✅"
|
||||
return 0
|
||||
fi
|
||||
if [[ -z "${PARTNERSHIP_FOLDERVIEW3_URL:-}" ]]; then
|
||||
warn "FolderView3 plugin not installed and PARTNERSHIP_FOLDERVIEW3_URL is empty"
|
||||
warn "Install manually from Community Applications or set PARTNERSHIP_FOLDERVIEW3_URL in master.conf"
|
||||
return 1
|
||||
fi
|
||||
warn "FolderView3 not found — installing from CA..."
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would run: plugin install $PARTNERSHIP_FOLDERVIEW3_URL"
|
||||
return 0
|
||||
fi
|
||||
plugin install "$PARTNERSHIP_FOLDERVIEW3_URL" 2>/dev/null && \
|
||||
log "FolderView3 installed ✅" || {
|
||||
warn "FolderView3 install failed — folder will not be created"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
folderview3_create_partner_folder() {
|
||||
local partner_name="$1"
|
||||
shift
|
||||
local containers=("$@")
|
||||
|
||||
log "FolderView3: creating folder '$partner_name' with ${#containers[@]} container(s)..."
|
||||
|
||||
folderview3_ensure_plugin || return 1
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would create FolderView3 folder: $partner_name"
|
||||
for c in "${containers[@]}"; do
|
||||
[[ -n "$c" ]] && warn " DRY RUN — container: $c"
|
||||
done
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Initialise JSON if missing
|
||||
if [[ ! -f "$FOLDERVIEW3_JSON" ]]; then
|
||||
mkdir -p "$(dirname "$FOLDERVIEW3_JSON")"
|
||||
echo '{}' > "$FOLDERVIEW3_JSON"
|
||||
fi
|
||||
|
||||
# Check folder doesn't already exist
|
||||
local existing
|
||||
existing=$(jq -r --arg name "$partner_name" \
|
||||
'to_entries[] | select(.value.name == $name) | .key' \
|
||||
"$FOLDERVIEW3_JSON" 2>/dev/null)
|
||||
if [[ -n "$existing" ]]; then
|
||||
log "FolderView3: folder '$partner_name' already exists (id: $existing) — skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Build containers JSON array
|
||||
local containers_json
|
||||
containers_json=$(printf '%s\n' "${containers[@]}" | \
|
||||
grep -v '^$' | jq -R . | jq -s .)
|
||||
|
||||
# Generate a stable random ID from timestamp+name
|
||||
local folder_id
|
||||
folder_id=$(echo "${partner_name}$(date +%s%N)" | md5sum | head -c 12)
|
||||
|
||||
jq --arg id "$folder_id" --arg name "$partner_name" \
|
||||
--argjson containers "$containers_json" \
|
||||
'.[$id] = {"name": $name, "containers": $containers, "containerImages": {}}' \
|
||||
"$FOLDERVIEW3_JSON" > "${FOLDERVIEW3_JSON}.tmp" && \
|
||||
mv "${FOLDERVIEW3_JSON}.tmp" "$FOLDERVIEW3_JSON" && \
|
||||
log "FolderView3: folder '$partner_name' created with ${#containers[@]} container(s) ✅" || {
|
||||
warn "FolderView3: failed to write JSON — check $FOLDERVIEW3_JSON"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
folderview3_remove_partner_folder() {
|
||||
local partner_name="$1"
|
||||
|
||||
log "FolderView3: removing folder '$partner_name' and stopping its containers..."
|
||||
|
||||
if [[ ! -f "$FOLDERVIEW3_JSON" ]]; then
|
||||
log "FolderView3: JSON not found — nothing to remove"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
warn "jq not found — cannot manage FolderView3 JSON"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get containers from this folder
|
||||
local containers_json
|
||||
containers_json=$(jq -r --arg name "$partner_name" \
|
||||
'to_entries[] | select(.value.name == $name) | .value.containers[]' \
|
||||
"$FOLDERVIEW3_JSON" 2>/dev/null)
|
||||
|
||||
if [[ -z "$containers_json" ]]; then
|
||||
log "FolderView3: folder '$partner_name' not found — nothing to remove"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Stop and remove each container in the folder
|
||||
local stopped=0 removed=0 failed=0
|
||||
while IFS= read -r container; do
|
||||
[[ -z "$container" ]] && continue
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would stop + rm: $container"
|
||||
continue
|
||||
fi
|
||||
# Stop if running
|
||||
if timeout "${DOCKER_TIMEOUT:-30}" docker inspect "$container" >/dev/null 2>&1; then
|
||||
if timeout "${DOCKER_TIMEOUT:-30}" docker stop "$container" >/dev/null 2>&1; then
|
||||
log "$container stopped ✅"
|
||||
_PM_TRAP_STOPPED+=("$container")
|
||||
(( stopped++ ))
|
||||
else
|
||||
warn "$container stop failed"
|
||||
(( failed++ ))
|
||||
fi
|
||||
if timeout "${DOCKER_TIMEOUT:-30}" docker rm "$container" >/dev/null 2>&1; then
|
||||
log "$container removed ✅"
|
||||
(( removed++ ))
|
||||
else
|
||||
warn "$container rm failed (may not exist)"
|
||||
fi
|
||||
else
|
||||
log "$container not found locally — skipping"
|
||||
fi
|
||||
done <<< "$containers_json"
|
||||
|
||||
# Remove folder entry from JSON
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
jq --arg name "$partner_name" \
|
||||
'with_entries(select(.value.name != $name))' \
|
||||
"$FOLDERVIEW3_JSON" > "${FOLDERVIEW3_JSON}.tmp" && \
|
||||
mv "${FOLDERVIEW3_JSON}.tmp" "$FOLDERVIEW3_JSON" && \
|
||||
log "FolderView3: folder '$partner_name' removed ✅" || \
|
||||
warn "FolderView3: failed to remove folder from JSON"
|
||||
else
|
||||
warn "DRY RUN — would remove folder '$partner_name' from $FOLDERVIEW3_JSON"
|
||||
fi
|
||||
|
||||
[[ "$DRY_RUN" == false ]] && \
|
||||
log "FolderView3 cleanup: $stopped stopped, $removed removed, $failed failed"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Gather all partner fallback containers for this server (all tiers)
|
||||
gather_partner_fallback_containers() {
|
||||
local out_var="$1"
|
||||
@@ -803,19 +643,14 @@ start_own_stack() {
|
||||
}
|
||||
|
||||
# Remove partnership containers on this server + their appdata bind-mount paths.
|
||||
# Uses FolderView3 folder if enabled (precise list), else falls back to FALLBACK_*_COVERS_* config.
|
||||
# Appdata paths collected via docker inspect BEFORE removal — inspect fails on removed containers.
|
||||
# Safety gate: only paths matching /mnt/*/appdata* are deleted.
|
||||
cleanup_partner_containers() {
|
||||
local folder_name="$1"
|
||||
declare -a containers=()
|
||||
gather_partner_fallback_containers containers
|
||||
|
||||
if [[ ${#containers[@]} -eq 0 ]]; then
|
||||
log "No partner containers found to remove"
|
||||
if [[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]]; then
|
||||
folderview3_remove_partner_folder "$folder_name"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -832,25 +667,21 @@ cleanup_partner_containers() {
|
||||
done
|
||||
|
||||
# Remove containers
|
||||
if [[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]]; then
|
||||
folderview3_remove_partner_folder "$folder_name"
|
||||
else
|
||||
for container in "${containers[@]}"; do
|
||||
[[ -z "$container" ]] && continue
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would stop + rm: $container"
|
||||
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
|
||||
log "$container not found — skipping"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
for container in "${containers[@]}"; do
|
||||
[[ -z "$container" ]] && continue
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
warn "DRY RUN — would stop + rm: $container"
|
||||
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
|
||||
log "$container not found — skipping"
|
||||
fi
|
||||
done
|
||||
|
||||
# Delete appdata after containers are gone
|
||||
while IFS= read -r path; do
|
||||
@@ -1271,27 +1102,6 @@ if [[ "$MODE" == "status" ]]; then
|
||||
echo " ${entry%%|*} → port ${entry##*|}"
|
||||
done
|
||||
|
||||
if [[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]]; then
|
||||
echo ""
|
||||
FOLDER_STATUS_NAME=$(derive_partner_folder_name "$REMOTE_SERVER_NAME")
|
||||
if [[ -f "$FOLDERVIEW3_JSON" ]]; then
|
||||
FOLDER_EXISTS=$(jq -r --arg name "$FOLDER_STATUS_NAME" \
|
||||
'to_entries[] | select(.value.name == $name) | .key' \
|
||||
"$FOLDERVIEW3_JSON" 2>/dev/null)
|
||||
if [[ -n "$FOLDER_EXISTS" ]]; then
|
||||
FOLDER_CONTAINERS=$(jq -r --arg name "$FOLDER_STATUS_NAME" \
|
||||
'[to_entries[] | select(.value.name == $name) | .value.containers[]] | join(", ")' \
|
||||
"$FOLDERVIEW3_JSON" 2>/dev/null)
|
||||
echo " FolderView3: $FOLDER_STATUS_NAME ✅"
|
||||
echo " Containers: $FOLDER_CONTAINERS"
|
||||
else
|
||||
echo " FolderView3: $FOLDER_STATUS_NAME — not found in JSON"
|
||||
fi
|
||||
else
|
||||
echo " FolderView3: config not found ($FOLDERVIEW3_JSON)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$OFFLINE_COUNTER" ]]; then
|
||||
OFFLINE_DAYS=$(cat "$OFFLINE_COUNTER" 2>/dev/null || echo 0)
|
||||
[[ "$OFFLINE_DAYS" -gt 0 ]] && \
|
||||
@@ -1414,26 +1224,12 @@ if [[ "$MODE" == "onboard" ]]; then
|
||||
# ── LOCAL-ONLY PATH ──────────────────────────────────────────────────────────
|
||||
# HOST1-local setup steps that don't need HOST2 present. Called from
|
||||
# partnership_onboard.sh --phase1-only so HOST1 can complete its own side
|
||||
# (FolderView3, setup.db flag) while waiting for HOST2 to install and onboard.
|
||||
# (PARTNERSHIP_ENABLED flag, setup.db) while waiting for HOST2 to install and onboard.
|
||||
if [[ "$LOCAL_ONLY" == true ]]; then
|
||||
log "Mode: local-only — skipping remote pre-flight and WebUI steps"
|
||||
log "Owner: $OWNER_ID ($OWNER) · Mirror: $MIRROR_ID ($MIRROR)"
|
||||
echo ""
|
||||
|
||||
if [[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]]; then
|
||||
echo "FolderView3 Integration"
|
||||
PARTNER_FOLDER_NAME=$(derive_partner_folder_name "$MIRROR")
|
||||
declare -a PARTNER_CONTAINERS=()
|
||||
gather_partner_fallback_containers PARTNER_CONTAINERS
|
||||
if [[ ${#PARTNER_CONTAINERS[@]} -gt 0 ]]; then
|
||||
folderview3_create_partner_folder "$PARTNER_FOLDER_NAME" "${PARTNER_CONTAINERS[@]}"
|
||||
else
|
||||
log "No FALLBACK_${MY_ID}_COVERS_${MIRROR_ID}_TIER* containers — skipping folder"
|
||||
fi
|
||||
else
|
||||
log "FolderView3 not configured — skipping"
|
||||
fi
|
||||
|
||||
# Enable partnership in master.conf + push to all hosts
|
||||
echo ""
|
||||
echo "Enabling partnership in master.conf..."
|
||||
@@ -1570,20 +1366,6 @@ if (vv_write_conf_raw('master.conf', \$master)) {
|
||||
warn "DRY RUN — would write ACTIVE state and push to remote"
|
||||
fi
|
||||
|
||||
# FolderView3 — create partner folder with this server's fallback containers for remote
|
||||
if [[ "${PARTNERSHIP_FOLDERVIEW3:-false}" == true ]]; then
|
||||
echo ""
|
||||
echo "━━━ $ICON_CONTAINERS FolderView3 Integration ━━━"
|
||||
PARTNER_FOLDER_NAME=$(derive_partner_folder_name "$MIRROR")
|
||||
declare -a PARTNER_CONTAINERS=()
|
||||
gather_partner_fallback_containers PARTNER_CONTAINERS
|
||||
if [[ ${#PARTNER_CONTAINERS[@]} -gt 0 ]]; then
|
||||
folderview3_create_partner_folder "$PARTNER_FOLDER_NAME" "${PARTNER_CONTAINERS[@]}"
|
||||
else
|
||||
log "No FALLBACK_${MY_ID}_COVERS_${REMOTE_ID}_TIER* containers configured — skipping folder creation"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Emby admin provisioning — runs after container deployment (deploy step not yet built)
|
||||
provision_emby_admin "$MIRROR_IP"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user