411 lines
19 KiB
Bash
Executable File
411 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
|
|
# Only rewrite a path that is a literal. These three are derived from SCRIPTS_DIR in
|
|
# current confs, which load_config.sh exports from its own location — so after the move
|
|
# they already point at the new install and hardcoding them here would turn a
|
|
# self-correcting value back into one that has to be maintained. Older confs still carry
|
|
# literals, so the substitution stays for them.
|
|
_mig_path() { # $1=key $2=new value
|
|
if grep -qE "^\s*$1\s*=\s*\"[^\"]*\\\$\{?(SCRIPTS_DIR|DATA_DIR)" "$NEW_MASTER"; then
|
|
echo " $1 — already derived, left alone ✅"
|
|
else
|
|
sed -i "s|^\(\s*$1\s*=\s*\)\"[^\"]*\"|\1\"$2\"|" "$NEW_MASTER"
|
|
echo " $1 → $2 ✅"
|
|
fi
|
|
}
|
|
_mig_path TARGET_DIR "$DST"
|
|
_mig_path DATA_DIR "$DST/data"
|
|
_mig_path STATE_DIR "$DST/data/state"
|
|
unset -f _mig_path
|
|
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 . '/data/state');
|
|
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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|