Finishes the pass: every script now documents its safeguards, and the deliberate absences in the sourced libraries are recorded so they are not "corrected" later.
400 lines
19 KiB
Bash
Executable File
400 lines
19 KiB
Bash
Executable File
#!/bin/bash
|
|
# ==============================================================================================
|
|
# ================================= Storage Migration ==========================================
|
|
# ==============================================================================================
|
|
#
|
|
# PURPOSE
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Migrates Varaverk between internal NVMe and USB flash storage modes.
|
|
#
|
|
# Internal mode: SCRIPTS_DIR = /boot/config/plugins/varaverk
|
|
# All scripts, conf, state, and git repo live on fast internal storage.
|
|
# Direct git pull/push. Zero write-wear concern.
|
|
#
|
|
# Flash mode: SCRIPTS_DIR = /mnt/user/appdata/Varaverk
|
|
# All scripts, conf, state, and git repo live in appdata.
|
|
# Preserves USB flash lifetime. Array must be started for Varaverk to function.
|
|
# git_pull_execute.sh syncs Plugin/ back to /boot/ after each pull so the
|
|
# Unraid webUI always serves current PHP files.
|
|
#
|
|
# What this script updates:
|
|
# varaverk.cfg SCRIPTS_DIR
|
|
# master.conf TARGET_DIR
|
|
# host*.conf HOST*_STORAGE_MODE_INTERNAL
|
|
# varaverk.cron rebuilt via PHP (job paths regenerated for new SCRIPTS_DIR)
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL MODEL
|
|
# ==============================================================================================
|
|
#
|
|
# 1. Determine current mode from varaverk.cfg SCRIPTS_DIR, and the requested target mode
|
|
# Already in the target mode → exit cleanly, nothing to do
|
|
# 2. rsync -a --delete SRC → DST, then carry .git across so history survives the move
|
|
# 3. Rewrite the pointers, in this order:
|
|
# varaverk.cfg SCRIPTS_DIR
|
|
# master.conf TARGET_DIR
|
|
# host*.conf HOST*_STORAGE_MODE_INTERNAL
|
|
# 4. Rebuild varaverk.cron via PHP so every job path points at the new SCRIPTS_DIR
|
|
# 5. Flash mode only: sync Plugin/ back to /boot so the webUI keeps serving current PHP
|
|
# 6. Remove the old location once the new one is confirmed in place
|
|
#
|
|
# ==============================================================================================
|
|
# DESIGN PRINCIPLES
|
|
# ==============================================================================================
|
|
#
|
|
# Atomic Path Transition
|
|
# All four files (varaverk.cfg, master.conf, host*.conf, varaverk.cron) are
|
|
# updated in a single pass. A partial migration would leave cron entries
|
|
# pointing at the wrong SCRIPTS_DIR — all or nothing.
|
|
#
|
|
# PHP Rebuilds Cron
|
|
# Job paths in varaverk.cron are derived from SCRIPTS_DIR. Rather than
|
|
# text-substituting the cron file, the script regenerates it via PHP using
|
|
# the new SCRIPTS_DIR as the source of truth.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL SAFEGUARDS
|
|
# ==============================================================================================
|
|
#
|
|
# acquire_lock — prevents concurrent migration attempts
|
|
# dry-run mode — shows all changes without touching any file
|
|
# --status mode — reports current mode without requiring a target
|
|
# --to= required — refuses to run without an explicit target mode
|
|
#
|
|
# ==============================================================================================
|
|
# CONFIGURATION
|
|
# ==============================================================================================
|
|
#
|
|
# This script WRITES these rather than reading them — they are the migration's output:
|
|
#
|
|
# varaverk.cfg
|
|
# SCRIPTS_DIR the authoritative install path. Everything else in the
|
|
# ecosystem derives from it, which is why it is written
|
|
# first and the cron is rebuilt from it afterwards.
|
|
#
|
|
# master.conf
|
|
# TARGET_DIR kept in step with SCRIPTS_DIR
|
|
#
|
|
# host*.conf
|
|
# HOST*_STORAGE_MODE_INTERNAL true = /boot/config/plugins/varaverk
|
|
# false = /mnt/user/appdata/Varaverk
|
|
#
|
|
# Load-bearing: STATE_DIR, DATA_DIR, PERSISTENT_CONF_CACHE and the orchestrator job paths are
|
|
# all built from SCRIPTS_DIR. Changing storage mode moves every one of them, which is why the
|
|
# cron is regenerated rather than edited.
|
|
#
|
|
# In flash mode the array must be started before Varaverk can function at all — appdata is
|
|
# not mounted before that.
|
|
#
|
|
# ==============================================================================================
|
|
# RUNTIME MODES
|
|
# ==============================================================================================
|
|
#
|
|
# storage_migrate.sh --to=internal
|
|
# Migrate to /boot/config/plugins/varaverk
|
|
#
|
|
# storage_migrate.sh --to=flash
|
|
# Migrate to /mnt/user/appdata/Varaverk
|
|
#
|
|
# storage_migrate.sh --dry-run --to=<mode>
|
|
# Show what would happen — no changes made
|
|
#
|
|
# storage_migrate.sh --status
|
|
# Show current mode, paths, and boot device info
|
|
#
|
|
# ==============================================================================================
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/../../../load_config.sh"
|
|
|
|
parse_args "$@"
|
|
|
|
# Relocates the entire Varaverk installation with rsync --delete and rm -rf, and rewrites
|
|
# varaverk.cfg, master.conf and host*.conf. Everything here needs root.
|
|
[[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; }
|
|
|
|
acquire_lock
|
|
detect_hosts
|
|
|
|
VV_CFG="/boot/config/plugins/varaverk/varaverk.cfg"
|
|
INTERNAL_DIR="/boot/config/plugins/varaverk"
|
|
FLASH_DIR="/mnt/user/appdata/Varaverk"
|
|
CONF_FILE="$CONF_DIR/${MY_ID,,}.conf"
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Parse --to= from raw args (parse_args doesn't handle this flag)
|
|
TO_MODE=""
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--to=internal) TO_MODE="internal" ;;
|
|
--to=flash) TO_MODE="flash" ;;
|
|
esac
|
|
done
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Boot device detection
|
|
detect_boot_storage() {
|
|
local boot_part boot_disk transport
|
|
boot_part=$(findmnt -n -o SOURCE /boot 2>/dev/null)
|
|
boot_disk=$(lsblk -no pkname "$boot_part" 2>/dev/null)
|
|
transport=$(lsblk -dno TRAN "/dev/$boot_disk" 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
|
echo "${transport:-unknown}"
|
|
}
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Status
|
|
if [[ "$SHOW_STATUS" == true ]]; then
|
|
TRANSPORT=$(detect_boot_storage)
|
|
DETECTED=$([[ "$TRANSPORT" == "usb" ]] && echo "flash" || echo "internal")
|
|
echo ""
|
|
echo "━━━━━ $ICON_SUMMARY STORAGE STATUS ━━━━━"
|
|
echo "$ICON_GEAR SCRIPTS_DIR: $SCRIPTS_DIR"
|
|
echo "$ICON_GEAR varaverk.cfg: $VV_CFG"
|
|
echo "$ICON_HOST Boot device: transport=$TRANSPORT → detected=$DETECTED"
|
|
echo "$ICON_GEAR Target dirs:"
|
|
echo " internal: $INTERNAL_DIR"
|
|
echo " flash: $FLASH_DIR"
|
|
CONF_MODE=$(grep -m1 "${MY_ID}_STORAGE_MODE_INTERNAL" "$CONF_FILE" 2>/dev/null | cut -d= -f2 | tr -d '"' | tr -d '[:space:]')
|
|
echo "$ICON_GEAR conf setting: ${MY_ID}_STORAGE_MODE_INTERNAL=${CONF_MODE:-not set}"
|
|
if [[ "$SCRIPTS_DIR" == "$INTERNAL_DIR" ]]; then
|
|
echo "$ICON_DONE Current mode: INTERNAL ✅"
|
|
elif [[ "$SCRIPTS_DIR" == "$FLASH_DIR" ]]; then
|
|
echo "$ICON_DONE Current mode: FLASH ✅"
|
|
else
|
|
echo "$ICON_WARN Current mode: CUSTOM ($SCRIPTS_DIR)"
|
|
fi
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
exit 0
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
if [[ -z "$TO_MODE" ]]; then
|
|
error "Usage: storage_migrate.sh --to=internal|flash [--dry-run] [--log]"
|
|
exit 1
|
|
fi
|
|
|
|
SRC="$SCRIPTS_DIR"
|
|
DST=$([[ "$TO_MODE" == "internal" ]] && echo "$INTERNAL_DIR" || echo "$FLASH_DIR")
|
|
NEW_INTERNAL=$([[ "$TO_MODE" == "internal" ]] && echo "true" || echo "false")
|
|
|
|
log "$ICON_GEAR Config: to=${TO_MODE} src=${SRC} dst=${DST} dry-run=${DRY_RUN}"
|
|
|
|
echo ""
|
|
echo "━━━━━ $ICON_SYNC Storage Migration ━━━━━"
|
|
echo "$ICON_GEAR From: $SRC"
|
|
echo "$ICON_GEAR To: $DST"
|
|
echo "$ICON_GEAR Mode: $TO_MODE"
|
|
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
|
echo ""
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Pre-flight checks
|
|
if [[ "$SRC" == "$DST" ]]; then
|
|
info "Already in $TO_MODE mode — nothing to do"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "$TO_MODE" == "flash" ]]; then
|
|
if ! mountpoint -q /mnt/user 2>/dev/null; then
|
|
error "Array not started — /mnt/user is not mounted. Start the array before migrating to flash."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [[ ! -f "$SRC/load_config.sh" ]]; then
|
|
error "Source directory looks invalid: $SRC (load_config.sh not found)"
|
|
exit 1
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Step 1: Git push — ensure remote has everything before we touch the local repo
|
|
echo "━━━ $ICON_SYNC Step 1: Git push ━━━"
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
if [[ -d "$SRC/.git" ]]; then
|
|
log "Pushing to Gitea before migration..."
|
|
if GIT_SSH_COMMAND="ssh -i $GITEA_SSH_KEY -p $SSH_PORT" \
|
|
git -C "$SRC" push origin main 2>&1 | while IFS= read -r line; do echo " $line"; done; then
|
|
echo " Git push complete ✅"
|
|
else
|
|
warn "Git push failed — continuing (data safe locally, push manually after migration)"
|
|
fi
|
|
else
|
|
warn "No .git directory in $SRC — skipping push"
|
|
fi
|
|
else
|
|
warn "DRY RUN — would push $SRC to Gitea"
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Step 2: Rsync content to destination
|
|
echo ""
|
|
echo "━━━ $ICON_SYNC Step 2: Copy files ━━━"
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
mkdir -p "$DST"
|
|
echo " rsync: $SRC/ → $DST/"
|
|
if rsync -av --delete \
|
|
--exclude='.git' \
|
|
"$SRC/" "$DST/" 2>&1 | \
|
|
grep -v "/$" | \
|
|
while IFS= read -r line; do log "$line"; done; then
|
|
echo " Files copied ✅"
|
|
else
|
|
error "rsync failed — aborting migration"
|
|
exit 1
|
|
fi
|
|
|
|
# Copy .git separately (rsync --exclude='.git' above skipped it)
|
|
echo " Copying .git..."
|
|
if cp -a "$SRC/.git" "$DST/.git" 2>/dev/null || \
|
|
rsync -a "$SRC/.git/" "$DST/.git/" 2>/dev/null; then
|
|
echo " .git copied ✅"
|
|
else
|
|
error ".git copy failed — aborting"
|
|
exit 1
|
|
fi
|
|
|
|
# Mark git safe directory
|
|
git config --global --add safe.directory "$DST" 2>/dev/null
|
|
else
|
|
warn "DRY RUN — would rsync $SRC/ → $DST/ (including .git)"
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Step 3: Update varaverk.cfg
|
|
echo ""
|
|
echo "━━━ $ICON_GEAR Step 3: Update varaverk.cfg ━━━"
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
if grep -q '^SCRIPTS_DIR' "$VV_CFG"; then
|
|
sed -i "s|^SCRIPTS_DIR=.*|SCRIPTS_DIR=\"$DST\"|" "$VV_CFG"
|
|
else
|
|
echo "SCRIPTS_DIR=\"$DST\"" >> "$VV_CFG"
|
|
fi
|
|
echo " SCRIPTS_DIR → $DST ✅"
|
|
else
|
|
warn "DRY RUN — would set SCRIPTS_DIR=\"$DST\" in $VV_CFG"
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Step 4: Update TARGET_DIR, DATA_DIR, STATE_DIR in master.conf (new location)
|
|
echo ""
|
|
echo "━━━ $ICON_GEAR Step 4: Update master.conf paths ━━━"
|
|
NEW_MASTER="$DST/Configurations/master.conf"
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
if [[ -f "$NEW_MASTER" ]]; then
|
|
sed -i "s|^\(\s*TARGET_DIR\s*=\s*\)\"[^\"]*\"|\1\"$DST\"|" "$NEW_MASTER"
|
|
sed -i "s|^\(\s*DATA_DIR\s*=\s*\)\"[^\"]*\"|\1\"$DST/data\"|" "$NEW_MASTER"
|
|
sed -i "s|^\(\s*STATE_DIR\s*=\s*\)\"[^\"]*\"|\1\"$DST/State_Files\"|" "$NEW_MASTER"
|
|
echo " TARGET_DIR → $DST ✅"
|
|
echo " DATA_DIR → $DST/data ✅"
|
|
echo " STATE_DIR → $DST/State_Files ✅"
|
|
else
|
|
error "master.conf not found at $NEW_MASTER"
|
|
exit 1
|
|
fi
|
|
else
|
|
warn "DRY RUN — would update TARGET_DIR, DATA_DIR, STATE_DIR in master.conf"
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Step 5: Update STORAGE_MODE_INTERNAL in host*.conf (new location)
|
|
echo ""
|
|
echo "━━━ $ICON_GEAR Step 5: Update STORAGE_MODE_INTERNAL ━━━"
|
|
NEW_CONF="$DST/Configurations/${MY_ID,,}.conf"
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
if [[ -f "$NEW_CONF" ]]; then
|
|
if grep -q "${MY_ID}_STORAGE_MODE_INTERNAL" "$NEW_CONF"; then
|
|
sed -i "s|^\(\s*${MY_ID}_STORAGE_MODE_INTERNAL\s*=\s*\).*|\1${NEW_INTERNAL}|" "$NEW_CONF"
|
|
else
|
|
sed -i "/# ━━━ Storage mode/a\\ ${MY_ID}_STORAGE_MODE_INTERNAL=${NEW_INTERNAL}" "$NEW_CONF"
|
|
fi
|
|
echo " ${MY_ID}_STORAGE_MODE_INTERNAL → $NEW_INTERNAL ✅"
|
|
else
|
|
warn "${MY_ID,,}.conf not found at $NEW_CONF — skipping conf update"
|
|
fi
|
|
else
|
|
warn "DRY RUN — would set ${MY_ID}_STORAGE_MODE_INTERNAL=$NEW_INTERNAL"
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Step 6: Rebuild cron (paths must reference new SCRIPTS_DIR)
|
|
echo ""
|
|
echo "━━━ $ICON_GEAR Step 6: Rebuild cron ━━━"
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
RESULT=$(php -r "
|
|
define('PLUGIN_CFG', '/boot/config/plugins/varaverk/varaverk.cfg');
|
|
\$_c = @parse_ini_file(PLUGIN_CFG) ?: [];
|
|
define('SCRIPTS_DIR', \$_c['SCRIPTS_DIR'] ?? '/boot/config/plugins/varaverk');
|
|
define('CONF_DIR', SCRIPTS_DIR . '/Configurations');
|
|
define('DATA_DIR', SCRIPTS_DIR . '/data');
|
|
define('STATE_DIR', SCRIPTS_DIR . '/State_Files');
|
|
define('LOG_DIR', '/var/log/varaverk');
|
|
define('VV_SETUP_STATE_FILE', STATE_DIR . '/varaverk_setup.db');
|
|
require_once '/usr/local/emhttp/plugins/varaverk/include/confform.php';
|
|
require_once '/usr/local/emhttp/plugins/varaverk/include/scheduler.php';
|
|
\$ok = vv_cron_rebuild(vv_schedule_load());
|
|
echo \$ok ? 'ok' : 'fail';
|
|
" 2>/dev/null)
|
|
if [[ "$RESULT" == "ok" ]]; then
|
|
echo " Cron rebuilt ✅"
|
|
else
|
|
warn "Cron rebuild failed — run Settings → Scheduler → Save to regenerate"
|
|
fi
|
|
else
|
|
warn "DRY RUN — would rebuild cron with new SCRIPTS_DIR paths"
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Step 7: Flash mode — sync Plugin/ to /boot/ so webUI is current
|
|
if [[ "$TO_MODE" == "flash" && "$DRY_RUN" == false ]]; then
|
|
echo ""
|
|
echo "━━━ $ICON_SYNC Step 7: Sync Plugin/ → /boot/ ━━━"
|
|
if rsync -a --delete "$DST/Plugin/" "$INTERNAL_DIR/Plugin/" 2>/dev/null; then
|
|
echo " Plugin/ synced to /boot/ ✅"
|
|
else
|
|
warn "Plugin/ sync to /boot/ failed — webUI may be stale"
|
|
fi
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
# Step 8: Delete old location
|
|
echo ""
|
|
echo "━━━ $ICON_GEAR Step $([[ "$TO_MODE" == "flash" ]] && echo 8 || echo 7): Clean up old location ━━━"
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
if [[ "$SRC" == "$INTERNAL_DIR" ]]; then
|
|
# Migrating internal→flash: keep varaverk.cfg and Plugin/ in /boot/, remove everything else
|
|
echo " Removing scripts/conf/state from /boot/ (keeping Plugin/ and varaverk.cfg)..."
|
|
find "$SRC" -mindepth 1 -maxdepth 1 \
|
|
! -name 'Plugin' \
|
|
! -name 'varaverk.cfg' \
|
|
! -name '*.plg' \
|
|
! -name '*.txz' \
|
|
-exec rm -rf {} + 2>/dev/null
|
|
echo " /boot/ cleaned ✅ (Plugin/ and varaverk.cfg preserved)"
|
|
else
|
|
# Migrating flash→internal: remove appdata copy entirely
|
|
echo " Removing $SRC..."
|
|
rm -rf "$SRC"
|
|
echo " $SRC removed ✅"
|
|
fi
|
|
else
|
|
if [[ "$SRC" == "$INTERNAL_DIR" ]]; then
|
|
warn "DRY RUN — would remove scripts/conf/state from /boot/ (keeping Plugin/ and varaverk.cfg)"
|
|
else
|
|
warn "DRY RUN — would remove $SRC"
|
|
fi
|
|
fi
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "━━━━━ $ICON_DONE Migration complete ━━━━━"
|
|
echo "$ICON_GEAR Mode: $TO_MODE"
|
|
echo "$ICON_GEAR SCRIPTS_DIR: $DST"
|
|
if [[ "$TO_MODE" == "flash" ]]; then
|
|
echo ""
|
|
warn "IMPORTANT: Varaverk requires the array to be started to function in flash mode."
|
|
warn "The webUI plugin tab will load normally at all times (Plugin/ stays in /boot/)."
|
|
fi
|
|
echo ""
|
|
echo " Reload the Varaverk plugin tab to apply changes."
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|