Files
ha-performance-fix/setup-npm-lxc.sh
T

315 lines
12 KiB
Bash

#!/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} <path>/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 "============================================"