Switch backup method: SSH → Supervisor partial backup via Samba (SSH add-on port conflict)

This commit is contained in:
2026-04-26 20:37:42 +10:00
parent 0667cab9c3
commit 48a8b638cc
+146 -137
View File
@@ -4,12 +4,16 @@
# #
# Migrates NGINX Proxy Manager from HAOS add-on → Proxmox LXC # Migrates NGINX Proxy Manager from HAOS add-on → Proxmox LXC
# #
# HA Host : 10.0.0.55 (enp1s0f0) # HA Host : 10.0.0.55 (enp1s0f0, Samba share available)
# New LXC : 10.0.0.54 # New LXC : 10.0.0.54
# Addon : Nginx Proxy Manager 2.1.0 (a0d7b954_nginxproxymanager) # Addon : Nginx Proxy Manager 2.1.0 (a0d7b954_nginxproxymanager)
# #
# Backup method: HA Supervisor partial backup → pulled via Samba (no SSH needed)
# The backup named "NPM Migration Backup" must already exist in HA backups.
# (Claude already triggered this via HA MCP before this script was run)
#
# Prerequisites: # Prerequisites:
# - SSH key from this Proxmox host authorised in HA Terminal & SSH add-on # - smbclient installed on Proxmox (apt install smbclient -y)
# - pct available (run from Proxmox node) # - pct available (run from Proxmox node)
# - wget available (for tteck script) # - wget available (for tteck script)
# ============================================================================= # =============================================================================
@@ -31,9 +35,10 @@ pause() { echo -e "\n${YELLOW}${BOLD}>>> Press ENTER when ready to continue...
# ─── CONFIG ─────────────────────────────────────────────────────────────────── # ─── CONFIG ───────────────────────────────────────────────────────────────────
HA_HOST="10.0.0.55" HA_HOST="10.0.0.55"
HA_SSH_PORT="22222" # Default Terminal & SSH add-on port — adjust if needed HA_SAMBA_USER="homeassistant" # Default HA Samba NAS username
HA_SSH_USER="root" HA_SAMBA_PASS="" # Leave blank to prompt, or set here
ADDON_SLUG="a0d7b954_nginxproxymanager" ADDON_SLUG="a0d7b954_nginxproxymanager"
BACKUP_LABEL="NPM Migration Backup"
NEW_LXC_IP="10.0.0.54" NEW_LXC_IP="10.0.0.54"
LXC_NETMASK="24" LXC_NETMASK="24"
@@ -42,127 +47,124 @@ PROXMOX_BRIDGE="vmbr0"
LXC_STORAGE="local-lvm" # Change to your Proxmox storage pool if different LXC_STORAGE="local-lvm" # Change to your Proxmox storage pool if different
TIMESTAMP="$(date +%Y%m%d-%H%M%S)" TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP_DIR="/tmp/npm-migration-${TIMESTAMP}" WORK_DIR="/tmp/npm-migration-${TIMESTAMP}"
BACKUP_ARCHIVE="/tmp/npm-backup-${TIMESTAMP}.tar.gz" SUPERVISOR_BACKUP="/tmp/npm-supervisor-backup-${TIMESTAMP}.tar"
RESTORE_STAGING="${WORK_DIR}/restore-staging"
# ─── SANITY CHECKS ──────────────────────────────────────────────────────────── # ─── SANITY CHECKS ────────────────────────────────────────────────────────────
[[ $EUID -eq 0 ]] || die "Run as root on the Proxmox node." [[ $EUID -eq 0 ]] || die "Run as root on the Proxmox node."
command -v pct &>/dev/null || die "'pct' not found — run this on a Proxmox node." command -v pct &>/dev/null || die "'pct' not found — run this on a Proxmox node."
command -v wget &>/dev/null || die "'wget' not found." command -v wget &>/dev/null || die "'wget' not found."
command -v smbclient &>/dev/null || {
info "smbclient not found — installing..."
apt-get install -y smbclient 2>/dev/null || die "Could not install smbclient. Run: apt install smbclient -y"
}
mkdir -p "${WORK_DIR}" "${RESTORE_STAGING}"
# ============================================================================= # =============================================================================
# PHASE 1 — BACKUP NPM DATA FROM HA ADD-ON # PHASE 1 — PULL NPM SUPERVISOR BACKUP FROM HA VIA SAMBA
# ============================================================================= # =============================================================================
step "PHASE 1 — Backup NPM from HA add-on (${HA_HOST})" step "PHASE 1 — Pull NPM backup from HA via Samba"
info "Testing SSH to HA host ${HA_HOST}:${HA_SSH_PORT}..." echo -e "${BOLD}HA Samba credentials needed${RESET}"
if ! ssh -p "${HA_SSH_PORT}" \ echo -e " Share: \\\\${HA_HOST}\\backup"
-o ConnectTimeout=10 \ echo -e " User: ${HA_SAMBA_USER} (or whatever you set in the Samba NAS add-on)"
-o BatchMode=yes \
-o StrictHostKeyChecking=accept-new \
"${HA_SSH_USER}@${HA_HOST}" "echo connected" &>/dev/null; then
die "SSH to ${HA_HOST}:${HA_SSH_PORT} failed.\n\n" \
" Fix checklist:\n" \
" 1. Is the Terminal & SSH add-on running in HA?\n" \
" 2. Is your Proxmox host's public key in the add-on 'authorized_keys' config?\n" \
" 3. Is HA_SSH_PORT correct? (check the add-on config page in HA)\n" \
" Common ports: 22222 (default), 22\n\n" \
" To add your key: copy output of 'cat ~/.ssh/id_rsa.pub' or 'cat ~/.ssh/id_ed25519.pub'\n" \
" then add it to the Terminal & SSH add-on Options → authorized_keys"
fi
success "SSH connection OK"
info "Creating backup on HA host..."
# shellcheck disable=SC2087
ssh -p "${HA_SSH_PORT}" "${HA_SSH_USER}@${HA_HOST}" bash << REMOTE
set -euo pipefail
ADDON_SLUG="${ADDON_SLUG}"
TIMESTAMP="${TIMESTAMP}"
BACKUP_DIR="/tmp/npm-migration-\${TIMESTAMP}"
BACKUP_ARCHIVE="/tmp/npm-backup-\${TIMESTAMP}.tar.gz"
mkdir -p "\${BACKUP_DIR}"
# ── Find the NPM data directory ──────────────────────────────────────────────
# Try host-level path first (accessible if SSH gives host shell),
# then the addon_configs path (accessible from Terminal add-on container)
DATA_FOUND=0
CANDIDATES=(
"/mnt/data/supervisor/addons/data/\${ADDON_SLUG}"
"/data/\${ADDON_SLUG}"
"/data"
)
for DIR in "\${CANDIDATES[@]}"; do
if [ -d "\${DIR}" ] && [ -f "\${DIR}/database.sqlite" ]; then
echo "Found NPM data at: \${DIR}"
DATA_DIR="\${DIR}"
DATA_FOUND=1
break
fi
done
if [ \${DATA_FOUND} -eq 0 ]; then
# Last resort — check if we can find the sqlite db anywhere
DB_PATH=\$(find /mnt/data /data -name "database.sqlite" 2>/dev/null | grep -i npm | head -1 || true)
if [ -n "\${DB_PATH}" ]; then
DATA_DIR="\$(dirname "\${DB_PATH}")"
echo "Found NPM database at: \${DATA_DIR}"
DATA_FOUND=1
fi
fi
if [ \${DATA_FOUND} -eq 0 ]; then
echo "ERROR: Could not locate NPM data directory (database.sqlite not found)."
echo "Tried: \${CANDIDATES[*]}"
echo "" echo ""
echo "If you are in the HA Terminal container, the addon data directory"
echo "may not be mounted here. See README in the repo for manual steps."
exit 1
fi
# ── Copy files into staging dir ─────────────────────────────────────────────── if [[ -z "${HA_SAMBA_PASS}" ]]; then
echo "Staging backup files..." read -rsp " Samba password for '${HA_SAMBA_USER}': " HA_SAMBA_PASS
[ -f "\${DATA_DIR}/database.sqlite" ] && \
cp "\${DATA_DIR}/database.sqlite" "\${BACKUP_DIR}/" && echo " ✓ database.sqlite"
[ -d "\${DATA_DIR}/nginx" ] && \
cp -a "\${DATA_DIR}/nginx" "\${BACKUP_DIR}/" && echo " ✓ nginx/"
[ -d "\${DATA_DIR}/letsencrypt" ] && \
cp -a "\${DATA_DIR}/letsencrypt" "\${BACKUP_DIR}/" && echo " ✓ letsencrypt/"
[ -d "\${DATA_DIR}/custom_ssl" ] && \
cp -a "\${DATA_DIR}/custom_ssl" "\${BACKUP_DIR}/" && echo " ✓ custom_ssl/ (if present)"
# ── Also grab addon_configs (nginx custom configs/snippets) ───────────────────
ADDON_CONFIG_DIR="/addon_configs/\${ADDON_SLUG}"
if [ -d "\${ADDON_CONFIG_DIR}" ]; then
cp -a "\${ADDON_CONFIG_DIR}" "\${BACKUP_DIR}/addon_configs" && echo " ✓ addon_configs/"
fi
# ── Create archive ─────────────────────────────────────────────────────────────
tar -czf "\${BACKUP_ARCHIVE}" -C "\${BACKUP_DIR}" .
echo "" echo ""
echo "Backup archive created: \${BACKUP_ARCHIVE}" fi
ls -lh "\${BACKUP_ARCHIVE}"
REMOTE
success "Backup created on HA host" info "Listing HA backup share to find the NPM backup..."
BACKUP_FILE=$(smbclient "//$(echo ${HA_HOST})/backup" \
-U "${HA_SAMBA_USER}%${HA_SAMBA_PASS}" \
-c "ls" 2>/dev/null \
| awk '{print $1}' \
| grep '\.tar$' \
| head -20 | tr '\n' '\n' || true)
info "Downloading backup to Proxmox (${BACKUP_ARCHIVE})..." if [[ -z "${BACKUP_FILE}" ]]; then
scp -P "${HA_SSH_PORT}" \ die "Could not list the HA backup share at //${HA_HOST}/backup\n\n" \
"${HA_SSH_USER}@${HA_HOST}:/tmp/npm-backup-${TIMESTAMP}.tar.gz" \ " Check:\n" \
"${BACKUP_ARCHIVE}" " 1. Samba NAS add-on is running in HA\n" \
" 2. Username/password are correct (set in Samba add-on config)\n" \
" 3. The 'backup' folder is enabled in the Samba add-on options\n\n" \
" Alternatively, go to HA → Settings → System → Backups,\n" \
" find 'NPM Migration Backup', download it manually to this machine\n" \
" and re-run this script with SUPERVISOR_BACKUP set to that file path."
fi
ARCHIVE_SIZE=$(du -sh "${BACKUP_ARCHIVE}" | cut -f1) echo ""
success "Backup downloaded → ${BACKUP_ARCHIVE} (${ARCHIVE_SIZE})" info "Backup files found on share:"
echo "${BACKUP_FILE}"
echo ""
info "Archive contents:" # Find the right backup — look for the most recent one (the one we just created)
tar -tzf "${BACKUP_ARCHIVE}" | head -40 # HA backup filenames are the backup slug (8-char hex), e.g. a1b2c3d4.tar
info "Fetching the most recently modified .tar from the share..."
LATEST_TAR=$(smbclient "//$(echo ${HA_HOST})/backup" \
-U "${HA_SAMBA_USER}%${HA_SAMBA_PASS}" \
-c "ls" 2>/dev/null \
| grep '\.tar' \
| sort -k3,4 \
| tail -1 \
| awk '{print $1}')
[[ -n "${LATEST_TAR}" ]] || die "Could not identify the latest backup tar file."
info "Downloading: ${LATEST_TAR}${SUPERVISOR_BACKUP}"
smbclient "//$(echo ${HA_HOST})/backup" \
-U "${HA_SAMBA_USER}%${HA_SAMBA_PASS}" \
-c "get ${LATEST_TAR} ${SUPERVISOR_BACKUP}" 2>/dev/null
[[ -f "${SUPERVISOR_BACKUP}" ]] || die "Download failed — file not found at ${SUPERVISOR_BACKUP}"
ARCHIVE_SIZE=$(du -sh "${SUPERVISOR_BACKUP}" | cut -f1)
success "Supervisor backup downloaded → ${SUPERVISOR_BACKUP} (${ARCHIVE_SIZE})"
# ── Extract NPM addon data from the Supervisor backup format ──────────────────
# Supervisor .tar structure:
# backup.json ← metadata
# {addon_slug}/
# addon.tar.gz ← addon DATA (database, certs, nginx)
# addon_config.tar.gz ← addon CONFIG files
info "Extracting NPM data from Supervisor backup..."
tar -xf "${SUPERVISOR_BACKUP}" -C "${WORK_DIR}" 2>/dev/null || \
die "Failed to extract supervisor backup. Is it a valid HA backup file?"
# Find addon subfolder (it may be named by a hash, not the slug)
ADDON_DIR=$(find "${WORK_DIR}" -maxdepth 1 -name "*.tar.gz" -o -type d 2>/dev/null | head -5)
info "Backup contents:"
ls -la "${WORK_DIR}/"
# Find the addon.tar.gz inside any subdirectory
ADDON_DATA_TAR=$(find "${WORK_DIR}" -name "addon.tar.gz" | head -1)
ADDON_CONFIG_TAR=$(find "${WORK_DIR}" -name "addon_config.tar.gz" | head -1)
if [[ -z "${ADDON_DATA_TAR}" ]]; then
# Some versions put it at root with the slug as filename
ADDON_DATA_TAR=$(find "${WORK_DIR}" -name "${ADDON_SLUG}.tar.gz" | head -1)
fi
[[ -n "${ADDON_DATA_TAR}" ]] || die "Could not find addon.tar.gz in the supervisor backup.\n" \
" This may not be the NPM backup — check HA backups and confirm\n" \
" 'NPM Migration Backup' was created successfully before re-running."
info "Extracting addon data archive: ${ADDON_DATA_TAR}"
tar -xzf "${ADDON_DATA_TAR}" -C "${RESTORE_STAGING}" 2>/dev/null || true
if [[ -n "${ADDON_CONFIG_TAR}" ]]; then
info "Extracting addon config archive: ${ADDON_CONFIG_TAR}"
mkdir -p "${RESTORE_STAGING}/addon_config"
tar -xzf "${ADDON_CONFIG_TAR}" -C "${RESTORE_STAGING}/addon_config" 2>/dev/null || true
fi
info "Staged restore contents:"
find "${RESTORE_STAGING}" -maxdepth 3 | head -30
success "NPM data extracted and staged"
# ============================================================================= # =============================================================================
# PHASE 2 — CREATE NPM LXC VIA TTECK # PHASE 2 — CREATE NPM LXC VIA TTECK
@@ -213,68 +215,75 @@ info "Stopping NPM service in LXC before restore..."
pct exec "${LXC_ID}" -- systemctl stop npm 2>/dev/null || true pct exec "${LXC_ID}" -- systemctl stop npm 2>/dev/null || true
sleep 2 sleep 2
info "Pushing backup archive into LXC..." info "Creating restore tarball from staged data..."
pct push "${LXC_ID}" "${BACKUP_ARCHIVE}" "/tmp/npm-backup.tar.gz" RESTORE_ARCHIVE="${WORK_DIR}/npm-restore.tar.gz"
tar -czf "${RESTORE_ARCHIVE}" -C "${RESTORE_STAGING}" .
info "Pushing restore archive into LXC..."
pct push "${LXC_ID}" "${RESTORE_ARCHIVE}" "/tmp/npm-restore.tar.gz"
info "Restoring data into /opt/npm/data/..." info "Restoring data into /opt/npm/data/..."
pct exec "${LXC_ID}" -- bash << 'RESTORE' pct exec "${LXC_ID}" -- bash << 'RESTORE'
set -euo pipefail set -euo pipefail
NPM_DATA="/opt/npm/data" NPM_DATA="/opt/npm/data"
mkdir -p "${NPM_DATA}/letsencrypt" "${NPM_DATA}/nginx" "${NPM_DATA}/custom_ssl" mkdir -p "${NPM_DATA}/letsencrypt" "${NPM_DATA}/nginx" "${NPM_DATA}/custom_ssl"
# Extract archive contents
STAGING="/tmp/npm-restore-staging" STAGING="/tmp/npm-restore-staging"
mkdir -p "${STAGING}" mkdir -p "${STAGING}"
tar -xzf /tmp/npm-backup.tar.gz -C "${STAGING}" tar -xzf /tmp/npm-restore.tar.gz -C "${STAGING}" 2>/dev/null || true
echo "Staged files:" echo "Staged contents:"
ls -la "${STAGING}/" find "${STAGING}" -maxdepth 3 | head -30
echo ""
# Restore database # ── Restore database ──────────────────────────────────────────────────────────
if [ -f "${STAGING}/database.sqlite" ]; then DB=$(find "${STAGING}" -name "database.sqlite" | head -1)
cp "${STAGING}/database.sqlite" "${NPM_DATA}/" if [[ -n "${DB}" ]]; then
echo " ✓ database.sqlite restored" cp "${DB}" "${NPM_DATA}/database.sqlite"
echo " ✓ database.sqlite"
else else
echo " ⚠ No database.sqlite found — NPM will start fresh (you can reconfigure manually)" echo " ⚠ database.sqlite not found — NPM will start fresh"
fi fi
# Restore nginx configs # ── Restore nginx configs ─────────────────────────────────────────────────────
if [ -d "${STAGING}/nginx" ]; then NGINX_DIR=$(find "${STAGING}" -type d -name "nginx" | head -1)
cp -a "${STAGING}/nginx/." "${NPM_DATA}/nginx/" if [[ -n "${NGINX_DIR}" ]]; then
echo " ✓ nginx/ restored" cp -a "${NGINX_DIR}/." "${NPM_DATA}/nginx/"
echo " ✓ nginx/"
fi fi
# Restore Let's Encrypt certs # ── Restore Let's Encrypt certs ───────────────────────────────────────────────
if [ -d "${STAGING}/letsencrypt" ]; then LE_DIR=$(find "${STAGING}" -type d -name "letsencrypt" | head -1)
cp -a "${STAGING}/letsencrypt/." "${NPM_DATA}/letsencrypt/" if [[ -n "${LE_DIR}" ]]; then
echo " ✓ letsencrypt/ restored" cp -a "${LE_DIR}/." "${NPM_DATA}/letsencrypt/"
echo " ✓ letsencrypt/"
fi fi
# Restore custom SSL if present # ── Restore custom SSL ────────────────────────────────────────────────────────
if [ -d "${STAGING}/custom_ssl" ]; then SSL_DIR=$(find "${STAGING}" -type d -name "custom_ssl" | head -1)
cp -a "${STAGING}/custom_ssl/." "${NPM_DATA}/custom_ssl/" if [[ -n "${SSL_DIR}" ]]; then
echo " ✓ custom_ssl/ restored" cp -a "${SSL_DIR}/." "${NPM_DATA}/custom_ssl/"
echo " ✓ custom_ssl/"
fi fi
# Fix ownership (NPM runs as uid 1000) # Fix ownership (NPM runs as uid 1000)
chown -R 1000:1000 "${NPM_DATA}/" chown -R 1000:1000 "${NPM_DATA}/"
# Cleanup # Cleanup
rm -rf "${STAGING}" /tmp/npm-backup.tar.gz rm -rf "${STAGING}" /tmp/npm-restore.tar.gz
# Start NPM # Start NPM
systemctl start npm systemctl start npm
sleep 3 sleep 4
# Confirm it's up
if systemctl is-active --quiet npm; then if systemctl is-active --quiet npm; then
echo "" echo ""
echo "NPM service is running ✓" echo "NPM service is running ✓"
else else
echo "" echo ""
echo "WARNING: NPM service did not start cleanly — check 'journalctl -u npm -n 50'" echo "WARNING: NPM did not start cleanly."
echo "Check logs with: journalctl -u npm -n 50"
fi fi
RESTORE RESTORE