From 5a7d5b50052d535f1f033ff968c4fb16796cdd7a Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Sun, 14 Jun 2026 14:53:51 -0400 Subject: [PATCH] =?UTF-8?q?Add=20share=5Fsetup.sh=20=E2=80=94=20create=20m?= =?UTF-8?q?issing=20mirror=20shares=20during=20onboarding=20(pool-aware,?= =?UTF-8?q?=20idempotent)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Partnership/partnership_onboard.sh | 25 +++- Partnership/share_setup.sh | 177 +++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 Partnership/share_setup.sh diff --git a/Partnership/partnership_onboard.sh b/Partnership/partnership_onboard.sh index 859e811..1cca94b 100755 --- a/Partnership/partnership_onboard.sh +++ b/Partnership/partnership_onboard.sh @@ -18,8 +18,10 @@ # Step 1: SSH key setup — generate keypair, copy to owner, update conf # Owner completes the rest remotely. Mirror is done. # -# OWNER PATH (10 steps) +# OWNER PATH (11 steps) # Step 1: SSH key setup — generate keypair, install on mirror, update conf +# Step 1b: Docker network — ensure varaverk docker network exists on mirror +# Step 1c: Share setup — create missing Unraid shares on mirror (pool-aware, idempotent) # Step 2: Stop mirror auth — stop mirror's existing auth containers before replacing # Step 3: Deploy auth stack — push XMLs, pull images, create + start on mirror # Mariadb/Redis health-checked before Authelia deploys @@ -126,6 +128,9 @@ # Partnership/partnership_onboard.sh --skip-ssh # Skip SSH key setup (key already in place) # +# Partnership/partnership_onboard.sh --skip-share-setup +# Skip share creation on mirror (shares already exist) +# # Partnership/partnership_onboard.sh --skip-auth-stack # Skip auth stack stop + deploy (Steps 3-4) # @@ -158,6 +163,7 @@ source "$SCRIPTS_ROOT/Plugin/$PLATFORM/Partnership/containers.sh" # ── Parse flags ─────────────────────────────────────────────────────────────────────────────── SKIP_SSH=false +SKIP_SHARE_SETUP=false SKIP_AUTH_STACK=false SKIP_ARR_STACK=false SKIP_SERVICES_STACK=false @@ -168,8 +174,9 @@ FILTERED_ARGS=() for arg in "$@"; do case "$arg" in - --skip-ssh) SKIP_SSH=true ;; - --skip-auth-stack) SKIP_AUTH_STACK=true ;; + --skip-ssh) SKIP_SSH=true ;; + --skip-share-setup) SKIP_SHARE_SETUP=true ;; + --skip-auth-stack) SKIP_AUTH_STACK=true ;; --skip-arr-stack) SKIP_ARR_STACK=true ;; --skip-services-stack) SKIP_SERVICES_STACK=true ;; --skip-arr-sync) SKIP_ARR_SYNC=true ;; @@ -521,6 +528,18 @@ else warn "Check ${_net_script} on $MIRROR and re-run with --skip-ssh if needed" fi +# ── Step 1c: Share setup ───────────────────────────────────────────────────────────────────── +echo "" +echo "━━━ Step 1c — Share Setup (Mirror) ━━━" + +if [[ "$SKIP_SHARE_SETUP" == true ]]; then + warn "Skipping (--skip-share-setup)" +elif [[ "$DRY_RUN" == true ]]; then + bash "$SCRIPT_DIR/share_setup.sh" --dry-run +else + bash "$SCRIPT_DIR/share_setup.sh" +fi + # ── Step 2: Stop mirror's existing auth stack ───────────────────────────────────────────────── echo "" echo "━━━ Step 2 — Stop Mirror Auth Stack ━━━" diff --git a/Partnership/share_setup.sh b/Partnership/share_setup.sh new file mode 100644 index 0000000..9b67642 --- /dev/null +++ b/Partnership/share_setup.sh @@ -0,0 +1,177 @@ +#!/bin/bash +# ============================================================================================== +# ============================= share_setup.sh ================================================= +# ============================================================================================== +# +# PURPOSE +# ───────────────────────────────────────────────────────────────────────────── +# Creates missing Unraid shares on the mirror during partnership onboarding. +# Safe to re-run — existing shares are never modified, only missing ones created. +# +# For each path in the owner's sync share lists (daily, weekly, critical, +# intermediate): +# - Extracts the top-level Unraid share name +# - Skips if the remote already has a .cfg for it +# - Uses the local share .cfg as a template: substitutes the remote's detected +# appdata pool, clears disk include/exclude (disk layouts differ per server) +# - Writes the .cfg to remote /boot/config/shares/ and mkdir -p the directory +# - For sub-paths (e.g. appdata-Fallback/Critical-Data), ensures the subdir +# exists after the top-level share is in place +# +# Pool detection: reads the remote's appdata.cfg to find its cache pool name, +# so appdata-type shares land on the right pool without hardcoding. +# +# Media shares (shareUseCache=no in local .cfg) are created as array-only. +# Remote admin assigns disk sets from the Unraid UI after onboarding. +# +# ============================================================================================== +# USAGE +# ============================================================================================== +# +# share_setup.sh +# Create missing shares on the configured mirror. Skips existing. +# +# share_setup.sh --dry-run +# Show what would be created without writing anything. +# +# ============================================================================================== + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../load_config.sh" +detect_hosts +parse_args "$@" + +# ── Resolve mirror ──────────────────────────────────────────────────────────────────────────── + +[[ -z "${REMOTE_SERVER_NAME:-}" ]] && { error "Cannot determine mirror hostname — check HOST* in master.conf"; exit 1; } +MIRROR="$REMOTE_SERVER_NAME" + +MIRROR_IP=$(resolve_tailscale_ip "$MIRROR") +[[ -z "$MIRROR_IP" ]] && { error "Cannot resolve $MIRROR Tailscale IP — is Tailscale running?"; exit 1; } + +_ssh() { ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no root@"$MIRROR_IP" "$@"; } +_scp() { scp -i "$SSH_KEY" -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$@"; } + +# ── Detect remote appdata pool ──────────────────────────────────────────────────────────────── + +REMOTE_POOL=$(_ssh "grep -oP '(?<=shareCachePool=\")[^\"]+' /boot/config/shares/appdata.cfg 2>/dev/null | head -1" 2>/dev/null || true) +REMOTE_POOL="${REMOTE_POOL:-cache}" +log "Remote appdata pool: $REMOTE_POOL" + +# ── Collect paths from all sync share arrays ────────────────────────────────────────────────── + +declare -a ALL_PATHS=() + +_add_array() { + local varname="$1" + local -n _ref="$varname" 2>/dev/null || return 0 + for entry in "${_ref[@]+"${_ref[@]}"}"; do + [[ -n "$entry" ]] && ALL_PATHS+=("${entry%%|*}") + done +} + +_add_array "${MY_ID}_DAILY_SYNC_SHARES" +_add_array "${MY_ID}_WEEKLY_SYNC_SHARES" +_add_array "${MY_ID}_CRITICAL_SYNC_SHARES" +_add_array "${MY_ID}_INTERMEDIATE_SYNC_SHARES" + +# ── Separate top-level shares from sub-paths ────────────────────────────────────────────────── + +declare -A SEEN_TOP=() +declare -A SEEN_SUB=() +declare -a TOP_SHARES=() +declare -a SUB_PATHS=() + +for path in "${ALL_PATHS[@]+"${ALL_PATHS[@]}"}"; do + [[ "$path" != /mnt/user/* ]] && continue + rel="${path#/mnt/user/}" + top="${rel%%/*}" + [[ -z "${SEEN_TOP[$top]+_}" ]] && { TOP_SHARES+=("$top"); SEEN_TOP["$top"]=1; } + if [[ "$rel" == */* && -z "${SEEN_SUB[$path]+_}" ]]; then + SUB_PATHS+=("$path"); SEEN_SUB["$path"]=1 + fi +done + +[[ ${#TOP_SHARES[@]} -eq 0 ]] && { echo "No shares to process."; exit 0; } + +# ── Build .cfg for a share ──────────────────────────────────────────────────────────────────── +# Uses local .cfg as template: substitutes pool, clears disk sets, drops comment. +# Falls back to a minimal cfg inferred from share name when no local .cfg exists. + +_make_cfg() { + local share_name="$1" + local local_cfg="/boot/config/shares/${share_name}.cfg" + + if [[ -f "$local_cfg" ]]; then + sed \ + -e "s|^shareCachePool=\"[^\"]*\"|shareCachePool=\"${REMOTE_POOL}\"|" \ + -e 's|^shareInclude="[^"]*"|shareInclude=""|' \ + -e 's|^shareExclude="[^"]*"|shareExclude=""|' \ + -e '/^shareComment=/d' \ + "$local_cfg" + else + local use_cache="no" pool="" allocator="mostfree" + case "$share_name" in appdata*|Media_Server*) + use_cache="yes"; pool="$REMOTE_POOL"; allocator="highwater" ;; + esac + printf 'shareUseCache="%s"\nshareCachePool="%s"\nshareCachePool2=""\n' "$use_cache" "$pool" + printf 'shareCOW="auto"\nshareAllocator="%s"\nshareInclude=""\nshareExclude=""\n' "$allocator" + printf 'shareFloor="0"\nshareExport="e"\nshareCaseSensitive="auto"\n' + printf 'shareSecurity="private"\nshareReadList=""\nshareWriteList=""\n' + printf 'shareVolsizelimit=""\nshareExportNFS="-"\nshareExportNFSFsid="0"\n' + printf 'shareSecurityNFS="public"\nshareHostListNFS=""\n' + fi +} + +# ── Process each top-level share ────────────────────────────────────────────────────────────── + +TMPFILE=$(mktemp) +trap 'rm -f "$TMPFILE"' EXIT + +CREATED=0 SKIPPED=0 + +for share in "${TOP_SHARES[@]}"; do + existing=$(_ssh "[[ -f '/boot/config/shares/${share}.cfg' ]] && echo yes || echo no" 2>/dev/null) + + if [[ "$existing" == "yes" ]]; then + log " exists: $share" + (( SKIPPED++ )) || true + continue + fi + + if [[ "$DRY_RUN" == true ]]; then + local_cfg="/boot/config/shares/${share}.cfg" + if [[ -f "$local_cfg" ]]; then + use_cache=$(grep -oP '(?<=shareUseCache=")[^"]+' "$local_cfg" || echo "?") + echo " [dry-run] CREATE $share (useCache=$use_cache → pool: $REMOTE_POOL)" + else + echo " [dry-run] CREATE $share (no local cfg — minimal fallback)" + fi + (( CREATED++ )) || true + continue + fi + + _make_cfg "$share" > "$TMPFILE" + _scp "$TMPFILE" "root@${MIRROR_IP}:/boot/config/shares/${share}.cfg" >/dev/null + _ssh "mkdir -p /mnt/user/${share}" >/dev/null + echo " Created: $share ✅" + (( CREATED++ )) || true +done + +# ── Ensure sub-path directories exist ──────────────────────────────────────────────────────── + +for path in "${SUB_PATHS[@]+"${SUB_PATHS[@]}"}"; do + if [[ "$DRY_RUN" == true ]]; then + echo " [dry-run] MKDIR $path" + continue + fi + _ssh "mkdir -p '$path'" 2>/dev/null && log " mkdir: $path" || true +done + +# ── Summary ─────────────────────────────────────────────────────────────────────────────────── + +echo "" +echo " Shares: $CREATED created, $SKIPPED already existed on $MIRROR" +[[ "$DRY_RUN" == true ]] && echo " (dry-run — nothing written)"