#!/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" 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 (don't start yet - need AppArmor fix first) --- 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 \ --unprivileged 1 \ --features "nesting=1,keyctl=1" \ --startup "order=2,up=10" # --- Apply AppArmor fix for Docker-in-LXC --- echo "[3/9] Applying AppArmor fix for Docker compatibility..." CT_CONF="/etc/pve/lxc/${CT_ID}.conf" if ! grep -q "lxc.apparmor.profile" "${CT_CONF}" 2>/dev/null; then echo "lxc.apparmor.profile: unconfined" >> "${CT_CONF}" fi pct start "${CT_ID}" echo "[4/9] Waiting for container to start..." sleep 5 # --- Install Docker inside the container --- echo "[5/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 "[6/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 "[7/9] Attempting data migration from HA addon..." MIGRATED=false # --- Approach 1: Try SSH (ports 22222 and 22) --- for PORT in 22222 22; do if ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no -p ${PORT} \ root@${HAOS_IP} "test -d /addon_data/${ADDON_SLUG}" 2>/dev/null; then echo " SSH connected (port ${PORT}), copying data..." TMPDIR=$(mktemp -d) scp -P ${PORT} -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 DB_SRC=$(find "${ADDON_DATA}" -name "database.sqlite" -type f 2>/dev/null | head -1) [ -n "${DB_SRC}" ] && pct push "${CT_ID}" "${DB_SRC}" /opt/npm/data/database.sqlite && echo " DB migrated" CERT_DIR=$(find "${ADDON_DATA}" -type d -name "letsencrypt" 2>/dev/null | head -1) if [ -n "${CERT_DIR}" ]; then tar -czf "${TMPDIR}/c.tar.gz" -C "${CERT_DIR}" . 2>/dev/null pct push "${CT_ID}" "${TMPDIR}/c.tar.gz" /tmp/c.tar.gz pct exec "${CT_ID}" -- bash -c 'tar xzf /tmp/c.tar.gz -C /opt/npm/letsencrypt/ && rm /tmp/c.tar.gz' echo " Certs migrated" fi MIGRATED=true fi rm -rf "${TMPDIR}" break fi done # --- Approach 2: HA Supervisor partial backup (prompt for token) --- if [ "$MIGRATED" = false ]; then echo "" echo " SSH to HAOS failed." echo "" echo " To migrate your NPM data automatically, this script can" echo " use the Home Assistant API to create a partial backup." echo "" echo " You need a Long-Lived Access Token from HA:" echo " Profile > Security > Long-lived access tokens > Create" echo "" if [ -n "${HA_TOKEN:-}" ]; then echo " Using HA_TOKEN from environment." else read -r -p " Paste your HA token (or press Enter to skip): " HA_TOKEN fi if [ -n "${HA_TOKEN:-}" ]; then HA_URL="http://${HAOS_IP}:8123" TMPDIR=$(mktemp -d) echo " Creating partial backup of NPM addon..." BACKUP_SLUG=$(curl -s -X POST \ -H "Authorization: Bearer ${HA_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"name\":\"npm-migration\",\"addons\":[\"${ADDON_SLUG}\"],\"folders\":[]}" \ "${HA_URL}/api/hassio/backups/new/partial" 2>/dev/null \ | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('slug',''))" 2>/dev/null) if [ -n "${BACKUP_SLUG}" ]; then echo " Backup created (${BACKUP_SLUG}), downloading..." curl -s -o "${TMPDIR}/backup.tar" \ -H "Authorization: Bearer ${HA_TOKEN}" \ "${HA_URL}/api/hassio/backups/${BACKUP_SLUG}/download" 2>/dev/null if [ -s "${TMPDIR}/backup.tar" ]; then cd "${TMPDIR}" && tar xf backup.tar 2>/dev/null || true ADDON_TAR=$(find "${TMPDIR}" -name "*nginxproxymanager*" -name "*.tar.gz" 2>/dev/null | head -1) if [ -n "${ADDON_TAR}" ]; then mkdir -p "${TMPDIR}/npm_data" tar xzf "${ADDON_TAR}" -C "${TMPDIR}/npm_data/" 2>/dev/null || true DB_SRC=$(find "${TMPDIR}/npm_data" -name "database.sqlite" -type f 2>/dev/null | head -1) [ -n "${DB_SRC}" ] && pct push "${CT_ID}" "${DB_SRC}" /opt/npm/data/database.sqlite && echo " DB migrated" CERT_DIR=$(find "${TMPDIR}/npm_data" -type d -name "letsencrypt" 2>/dev/null | head -1) if [ -n "${CERT_DIR}" ]; then tar -czf "${TMPDIR}/c.tar.gz" -C "${CERT_DIR}" . 2>/dev/null pct push "${CT_ID}" "${TMPDIR}/c.tar.gz" /tmp/c.tar.gz pct exec "${CT_ID}" -- bash -c 'tar xzf /tmp/c.tar.gz -C /opt/npm/letsencrypt/ && rm /tmp/c.tar.gz' echo " Certs migrated" fi MIGRATED=true else echo " Could not find addon data in backup" fi else echo " Backup download failed" fi # Clean up the backup from HA curl -s -X DELETE -H "Authorization: Bearer ${HA_TOKEN}" \ "${HA_URL}/api/hassio/backups/${BACKUP_SLUG}" 2>/dev/null || true else echo " Backup creation failed (check token permissions)" fi rm -rf "${TMPDIR}" else echo " Skipping API migration." fi fi if [ "$MIGRATED" = false ]; then echo "" echo " =========================================" echo " DATA MIGRATION SKIPPED" echo " =========================================" echo " NPM will start fresh. To migrate later:" echo " 1. HA > Settings > Backups > Create > Partial" echo " Select ONLY 'Nginx Proxy Manager'" echo " 2. Download the .tar backup" echo " 3. Copy to Proxmox: scp backup.tar root@10.0.0.240:/tmp/" echo " 4. Extract:" echo " cd /tmp && tar xf *.tar" echo " tar xzf *nginxproxymanager*.tar.gz -C /tmp/npm_extract/" echo " find /tmp/npm_extract -name database.sqlite" echo " 5. Push to NPM LXC:" echo " pct exec ${CT_ID} -- bash -c 'cd /opt/npm && docker compose down'" echo " pct push ${CT_ID} /database.sqlite /opt/npm/data/database.sqlite" echo " pct exec ${CT_ID} -- bash -c 'cd /opt/npm && docker compose up -d'" fi # --- Start NPM --- echo "[8/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 "[9/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 ' CT_IP_CLEAN=$(echo "${CT_IP}" | cut -d'/' -f1) 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 migrated from HA addon." echo " Login with your existing NPM credentials." else echo " Default login (fresh install):" echo " Email: admin@example.com" echo " Password: changeme" echo " >>> CHANGE THIS IMMEDIATELY <<<" fi echo "" echo " NEXT STEPS:" echo " 1. Verify proxy hosts at http://${CT_IP_CLEAN}:81" echo " 2. Update OPNsense port forwards (80/443):" echo " OLD: 10.0.0.55 -> NEW: ${CT_IP_CLEAN}" echo " 3. If certs didn't migrate, re-request via NPM" echo " 4. Stop HA NPM add-on after confirming" echo "============================================"