Files
Varaverk/Partnership/share_setup.sh
T
Gmer4Lfe f42ecc8464 Add docker actions, arr profile enforcer, monitor caching, and web file symlink
Web files now served via symlink to the git repo so git pull changes survive
reboots without rebuilding the txz. Also includes: docker pull/rebuild/restart
with live log streaming, arr_profile_enforcer for Sonarr/Radarr quality
profiles, monitor page cache fix (background writer now in cron), and
ARR_KIDS/SONARR/RADARR profile name vars in master.conf.
2026-06-19 11:09:40 -04:00

178 lines
8.2 KiB
Bash
Executable File

#!/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)"