#!/bin/bash # ===================================================================== # Nginx Proxy Manager LXC Setup + Migration from HA Addon # ===================================================================== # Migrates NPM from a Home Assistant add-on to a dedicated LXC # container on Proxmox. Copies the SQLite database (all proxy hosts, # users, settings) and Let's Encrypt certificates so you don't have # to re-create anything. # # Run this script on the Proxmox host (HAL-HOST / 10.0.0.240) as root. # # Network: # - Proxmox host: 10.0.0.240 # - HAOS VM: 10.0.0.55 # - OPNsense: 10.0.0.254 # - AdGuard LXC: 10.0.0.224 (CT 120) # - Guacamole LXC: 10.0.0.225 (CT 121) # - NPM LXC: 10.0.0.226 (CT 122) # # HA Addon slug: a0d7b954_nginxproxymanager # Addon data path on HAOS: /addon_data/a0d7b954_nginxproxymanager/ # # IMPORTANT: After migration, update your OPNsense port forwards # (80/443) to point to 10.0.0.226 instead of 10.0.0.55. # ===================================================================== set -euo pipefail # --- Configuration --- CT_ID="122" CT_NAME="npm" CT_IP="10.0.0.226/24" CT_GW="10.0.0.254" CT_STORAGE="local-lvm" CT_TEMPLATE="local:vztmpl/debian-12-standard_12.12-1_amd64.tar.zst" CT_MEMORY=512 CT_SWAP=256 CT_CORES=2 CT_DISK="4" BRIDGE="vmbr0" HAOS_IP="10.0.0.55" ADDON_SLUG="a0d7b954_nginxproxymanager" NPM_DATA="/opt/npm/data" echo "============================================" echo " Nginx Proxy Manager LXC Setup + Migration" echo " Container ID: ${CT_ID}" echo " IP Address: ${CT_IP}" echo "============================================" # --- Check if template exists --- if ! pveam list local | grep -q "debian-12-standard"; then echo "[1/9] Downloading Debian 12 template..." pveam download local debian-12-standard_12.12-1_amd64.tar.zst else echo "[1/9] Debian 12 template already available" fi # --- Create the container --- echo "[2/9] Creating LXC container ${CT_ID}..." pct create "${CT_ID}" "${CT_TEMPLATE}" \ --hostname "${CT_NAME}" \ --memory "${CT_MEMORY}" \ --swap "${CT_SWAP}" \ --cores "${CT_CORES}" \ --rootfs "${CT_STORAGE}:${CT_DISK}" \ --net0 "name=eth0,bridge=${BRIDGE},ip=${CT_IP},gw=${CT_GW},firewall=0" \ --nameserver "10.0.0.224" \ --onboot 1 \ --start 1 \ --unprivileged 1 \ --features "nesting=1,keyctl=1" \ --startup "order=2,up=10" echo "[3/9] Waiting for container to start..." sleep 5 # --- Install Docker inside the container --- echo "[4/9] Installing Docker..." pct exec "${CT_ID}" -- bash -c ' apt-get update -qq apt-get install -y -qq ca-certificates curl gnupg install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list apt-get update -qq apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-compose-plugin systemctl enable docker systemctl start docker ' # --- Create Docker Compose config --- echo "[5/9] Creating NPM Docker Compose configuration..." pct exec "${CT_ID}" -- bash -c " mkdir -p /opt/npm/data /opt/npm/letsencrypt cat > /opt/npm/docker-compose.yml << 'DCEOF' services: npm: image: jc21/nginx-proxy-manager:latest container_name: npm restart: unless-stopped ports: - '80:80' - '443:443' - '81:81' volumes: - /opt/npm/data:/data - /opt/npm/letsencrypt:/etc/letsencrypt environment: TZ: Australia/Melbourne DB_SQLITE_FILE: /data/database.sqlite healthcheck: test: ['CMD', '/usr/bin/check-health'] interval: 10s timeout: 3s DCEOF " # --- Attempt data migration from HA addon --- echo "[6/9] Attempting data migration from HA addon..." MIGRATED=false if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@${HAOS_IP} "test -d /addon_data/${ADDON_SLUG}" 2>/dev/null; then echo " Found NPM addon data. Copying..." TMPDIR=$(mktemp -d) # Copy entire addon data directory from HAOS scp -r -o StrictHostKeyChecking=no root@${HAOS_IP}:/addon_data/${ADDON_SLUG}/ "${TMPDIR}/" 2>/dev/null ADDON_DATA="${TMPDIR}/${ADDON_SLUG}" if [ -d "${ADDON_DATA}" ]; then echo " Addon data downloaded to ${TMPDIR}" # --- Migrate SQLite database --- # HA addon NPM may store the DB in various locations DB_SRC="" for candidate in \ "${ADDON_DATA}/database.sqlite" \ "${ADDON_DATA}/data/database.sqlite" \ "${ADDON_DATA}/nginx/database.sqlite"; do if [ -f "$candidate" ]; then DB_SRC="$candidate" break fi done if [ -n "${DB_SRC}" ]; then echo " Found database: ${DB_SRC}" pct push "${CT_ID}" "${DB_SRC}" /opt/npm/data/database.sqlite echo " Database migrated (proxy hosts, users, settings)" else echo " WARNING: database.sqlite not found in addon data" # List what we did find for debugging echo " Contents of addon data:" find "${ADDON_DATA}" -maxdepth 3 -type f | head -30 fi # --- Migrate Let's Encrypt certificates --- CERTS_SRC="" for candidate in \ "${ADDON_DATA}/letsencrypt" \ "${ADDON_DATA}/ssl" \ "${ADDON_DATA}/data/letsencrypt"; do if [ -d "$candidate" ]; then CERTS_SRC="$candidate" break fi done if [ -n "${CERTS_SRC}" ]; then echo " Found certificates: ${CERTS_SRC}" # Tar up certs, push to LXC, extract tar -czf "${TMPDIR}/certs.tar.gz" -C "${CERTS_SRC}" . 2>/dev/null pct push "${CT_ID}" "${TMPDIR}/certs.tar.gz" /tmp/certs.tar.gz pct exec "${CT_ID}" -- bash -c ' mkdir -p /opt/npm/letsencrypt tar -xzf /tmp/certs.tar.gz -C /opt/npm/letsencrypt/ rm /tmp/certs.tar.gz ' CERT_COUNT=$(find "${CERTS_SRC}" -name "*.pem" -o -name "*.crt" 2>/dev/null | wc -l) echo " Certificates migrated (${CERT_COUNT} cert files)" else echo " No certificate directory found (will need re-issue)" fi # --- Migrate custom Nginx configs --- for custom_dir in \ "${ADDON_DATA}/nginx/custom" \ "${ADDON_DATA}/data/nginx/custom"; do if [ -d "$custom_dir" ] && [ "$(ls -A "$custom_dir" 2>/dev/null)" ]; then echo " Found custom Nginx configs: ${custom_dir}" tar -czf "${TMPDIR}/custom.tar.gz" -C "${custom_dir}" . 2>/dev/null pct push "${CT_ID}" "${TMPDIR}/custom.tar.gz" /tmp/custom.tar.gz pct exec "${CT_ID}" -- bash -c ' mkdir -p /opt/npm/data/nginx/custom tar -xzf /tmp/custom.tar.gz -C /opt/npm/data/nginx/custom/ rm /tmp/custom.tar.gz ' echo " Custom configs migrated" break fi done MIGRATED=true fi rm -rf "${TMPDIR}" else echo " Could not SSH to HAOS at ${HAOS_IP}." echo " Make sure Terminal & SSH addon is installed." echo "" echo " MANUAL MIGRATION:" echo " From HAOS SSH terminal, run:" echo " cd /addon_data/${ADDON_SLUG}" echo " tar -czf /tmp/npm-backup.tar.gz ." echo " scp /tmp/npm-backup.tar.gz root@10.0.0.240:/tmp/" echo "" echo " Then on Proxmox (10.0.0.240):" echo " pct push ${CT_ID} /tmp/npm-backup.tar.gz /tmp/npm-backup.tar.gz" echo " pct exec ${CT_ID} -- bash -c '" echo " tar -xzf /tmp/npm-backup.tar.gz -C /opt/npm/data/" echo " rm /tmp/npm-backup.tar.gz'" fi # --- Start NPM --- echo "[7/9] Starting Nginx Proxy Manager..." pct exec "${CT_ID}" -- bash -c ' cd /opt/npm docker compose up -d echo "Waiting for NPM to initialise..." sleep 15 docker compose ps ' # --- Create systemd service --- echo "[8/9] Creating systemd service for auto-start..." pct exec "${CT_ID}" -- bash -c ' cat > /etc/systemd/system/npm.service << "SVCEOF" [Unit] Description=Nginx Proxy Manager Docker Requires=docker.service After=docker.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=/opt/npm ExecStart=/usr/bin/docker compose up -d ExecStop=/usr/bin/docker compose down TimeoutStartSec=120 [Install] WantedBy=multi-user.target SVCEOF systemctl daemon-reload systemctl enable npm.service ' # --- Verify --- echo "[9/9] Verifying NPM is accessible..." CT_IP_CLEAN=$(echo "${CT_IP}" | cut -d'/' -f1) sleep 5 HTTP_CODE=$(pct exec "${CT_ID}" -- curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:81/ 2>/dev/null || echo "000") if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ]; then echo " NPM admin panel responding (HTTP ${HTTP_CODE})" else echo " NPM may still be starting up (HTTP ${HTTP_CODE})" echo " Give it another minute, then check http://${CT_IP_CLEAN}:81" fi echo "" echo "============================================" echo " Nginx Proxy Manager LXC setup complete!" echo "============================================" echo "" echo " Admin UI: http://${CT_IP_CLEAN}:81" echo " HTTP: ${CT_IP_CLEAN}:80" echo " HTTPS: ${CT_IP_CLEAN}:443" echo "" if [ "$MIGRATED" = true ]; then echo " Data was migrated from HA addon!" echo " Your proxy hosts and SSL certs should be intact." echo "" echo " Default login (if DB migration succeeded):" echo " Use your existing NPM credentials." echo "" echo " If login fails (schema mismatch), use defaults:" echo " Email: admin@example.com" echo " Password: changeme" else echo " Default login (fresh install):" echo " Email: admin@example.com" echo " Password: changeme" echo " >>> CHANGE THIS IMMEDIATELY <<<" fi echo "" echo " CRITICAL NEXT STEPS:" echo "" echo " 1. Verify all proxy hosts work at http://${CT_IP_CLEAN}:81" echo "" echo " 2. Update OPNsense port forwards:" echo " Firewall > NAT > Port Forward" echo " Change destination for ports 80/443:" echo " OLD: 10.0.0.55 (HAOS)" echo " NEW: ${CT_IP_CLEAN} (NPM LXC)" echo "" echo " 3. Update any DNS records pointing to the old IP" echo " (all *.hideawaygaming.com.au should resolve" echo " to your WAN IP, which OPNsense forwards)" echo "" echo " 4. If certs didn't migrate, re-request them:" echo " NPM Admin > SSL Certificates > Add" echo " Use DNS challenge or HTTP challenge" echo "" echo " 5. After confirming everything works:" echo " Stop HA NPM add-on" echo "" echo " Docker management:" echo " pct exec ${CT_ID} -- docker compose -f /opt/npm/docker-compose.yml logs -f" echo " pct exec ${CT_ID} -- docker compose -f /opt/npm/docker-compose.yml restart" echo "============================================"