Files
dillard 2626036a21 Initial commit: Nextcloud bare/Hansson → AIO migration toolkit
Scripts 01-05 voor sequentiële migratie: transfer, staging, upgrade-keten,
AIO-install en DB-import. PostgreSQL + MySQL/MariaDB bronnen ondersteund.
Proxmox/OPNSense VM-builder optioneel in proxmox/. README met 12 gotchas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 01:18:01 +02:00

211 lines
8.2 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# proxmox/build-vm.sh — Maakt de doel-VM aan op een Proxmox-node.
#
# Optioneel script: sla dit over als je de VM handmatig aanmaakt of een
# andere hypervisor gebruikt. De overige scripts (0105) werken op elke
# Linux-VM met SSH-toegang.
#
# Wat het doet:
# - VM aanmaken (Debian 13 cloud image, qcow2)
# - Systeemdisk + aparte datadisk (LVM, ext4, /mnt/ncdata)
# - Netplan vervangen door /etc/network/interfaces
# - Loopback VIP (publiek IP) via systemd oneshot (indien VM_PUBLIC_IP gezet)
# - OPNSense: gateway + static route voor het publieke VIP (indien VM_PUBLIC_IP gezet)
#
# Vereisten:
# - SSH-alias voor PROXMOX_HOST en (indien VIP) fw01 werken
# - Debian 13 genericcloud qcow2 beschikbaar op de PVE-node (zie CLOUD_IMAGE)
# - config.sh ingevuld
set -euo pipefail
source "$(dirname "$0")/../config.sh"
log() { echo "[$(date '+%H:%M:%S')] $*"; }
SCRATCHDIR=$(mktemp -d)
trap 'rm -rf "$SCRATCHDIR"' EXIT
# ── STAP 1: VM aanmaken ───────────────────────────────────────────────────────
log "VM ${PROXMOX_VMID} (${VM_NAME}) aanmaken op ${PROXMOX_HOST}..."
ssh "${PROXMOX_HOST}" "
qm create ${PROXMOX_VMID} \
--name ${VM_NAME} \
--memory ${VM_MEMORY} \
--cores ${VM_CORES} \
--sockets 1 \
--cpu host \
--net0 virtio,bridge=${VM_BRIDGE}$([ -n '${VM_VLAN}' ] && echo ',tag=${VM_VLAN}' || true) \
--scsihw virtio-scsi-single \
--ostype l26 \
--serial0 socket \
--vga serial0 \
--agent enabled=1 \
--onboot 1
"
log "Systeemdisk importeren als qcow2..."
ssh "${PROXMOX_HOST}" \
"qm importdisk ${PROXMOX_VMID} ${CLOUD_IMAGE} local --format qcow2 2>&1 | tail -1"
PUBKEY_PVE=$(ssh "${PROXMOX_HOST}" \
"cat /root/.ssh/id_ed25519.pub 2>/dev/null || cat /root/.ssh/id_rsa.pub")
PUBKEY_WS=$(cat ~/.ssh/id_ed25519.pub 2>/dev/null || cat ~/.ssh/id_rsa.pub)
log "Disks koppelen + cloud-init configureren..."
ssh "${PROXMOX_HOST}" "
qm set ${PROXMOX_VMID} \
--scsi0 local:${PROXMOX_VMID}/vm-${PROXMOX_VMID}-disk-0.qcow2,discard=on,ssd=1,iothread=1 \
--scsi1 local:${VM_DATA_SIZE},format=qcow2,discard=on,iothread=1 \
--boot order=scsi0 \
--ide2 local:cloudinit \
--ciuser root \
--sshkeys <(printf '%s\n%s\n' '${PUBKEY_PVE}' '${PUBKEY_WS}') \
--ipconfig0 ip=${VM_PRIVATE_IP}/${VM_PRIVATE_PREFIX},gw=${VM_PRIVATE_GW} \
--nameserver ${VM_DNS} \
--searchdomain ${VM_SEARCH_DOMAIN}
qm resize ${PROXMOX_VMID} scsi0 ${VM_DISK_SIZE}G
"
# ── STAP 2: VM starten en wachten op SSH ─────────────────────────────────────
log "VM starten..."
ssh "${PROXMOX_HOST}" "qm start ${PROXMOX_VMID}"
log "Wachten op SSH (${VM_PRIVATE_IP})..."
for i in $(seq 1 60); do
ssh "${PROXMOX_HOST}" \
"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=4 \
root@${VM_PRIVATE_IP} hostname 2>/dev/null" && break || true
sleep 5
done
# ── STAP 3: Netplan → ifupdown ───────────────────────────────────────────────
log "Netplan vervangen door ifupdown..."
ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} bash -s" << EOF
set -e
apt-get install -y ifupdown 2>&1 | tail -1
cat > /etc/network/interfaces << 'IFEOF'
source /etc/network/interfaces.d/*
auto lo
iface lo inet loopback
auto ens18
iface ens18 inet static
address ${VM_PRIVATE_IP}/${VM_PRIVATE_PREFIX}
gateway ${VM_PRIVATE_GW}
dns-nameservers ${VM_DNS} 1.1.1.1
dns-search ${VM_SEARCH_DOMAIN}
IFEOF
systemctl disable systemd-networkd systemd-networkd-wait-online 2>/dev/null || true
mkdir -p /etc/cloud/cloud.cfg.d
echo "network: {config: disabled}" > /etc/cloud/cloud.cfg.d/99-disable-network.cfg
apt-get purge -y netplan.io 2>&1 | tail -1
rm -rf /etc/netplan /usr/share/netplan
EOF
# ── STAP 4: Loopback VIP (optioneel) ─────────────────────────────────────────
if [[ -n "${VM_PUBLIC_IP}" ]]; then
log "Loopback VIP ${VM_PUBLIC_IP} instellen..."
ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} bash -s" << EOF
set -e
cat > /etc/systemd/system/loopback-vip.service << 'VIPEOF'
[Unit]
Description=Loopback VIP
After=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ip addr add ${VM_PUBLIC_IP}/32 dev lo label lo:vip
ExecStop=/sbin/ip addr del ${VM_PUBLIC_IP}/32 dev lo label lo:vip
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
VIPEOF
systemctl enable loopback-vip.service
EOF
fi
# ── STAP 5: Datadisk — LVM + ext4 ───────────────────────────────────────────
log "Datadisk inrichten (LVM vg_data/ncdata, ext4, /mnt/ncdata)..."
ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} bash -s" << 'EOF'
set -e
apt-get install -y lvm2 2>&1 | tail -1
DISK=/dev/sdb
[ -b "$DISK" ] || { echo "FOUT: $DISK niet gevonden"; exit 1; }
[ -z "$(lsblk -dn -o FSTYPE $DISK 2>/dev/null)" ] || { echo "FOUT: $DISK is niet leeg"; exit 1; }
pvcreate "$DISK"
vgcreate vg_data "$DISK"
lvcreate -n ncdata -l 100%FREE vg_data
mkfs.ext4 -L ncdata /dev/vg_data/ncdata
mkdir -p /mnt/ncdata
echo "/dev/vg_data/ncdata /mnt/ncdata ext4 defaults,noatime 0 2" >> /etc/fstab
mount -a
df -h /mnt/ncdata
EOF
# ── STAP 6: Basis-pakketten + reboot ─────────────────────────────────────────
log "Basis-pakketten installeren..."
ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} bash -s" << 'EOF'
set -e
apt-get update -qq
apt-get install -y qemu-guest-agent rsync curl ca-certificates gnupg htop 2>&1 | tail -1
systemctl enable --now qemu-guest-agent
EOF
log "Reboot..."
ssh "${PROXMOX_HOST}" "ssh root@${VM_PRIVATE_IP} reboot" || true
sleep 10
log "Wachten op herstart..."
for i in $(seq 1 60); do
ssh "${PROXMOX_HOST}" \
"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=4 \
root@${VM_PRIVATE_IP} hostname 2>/dev/null" && break || true
sleep 5
done
# ── STAP 7: OPNSense gateway + static route (optioneel) ──────────────────────
if [[ -n "${VM_PUBLIC_IP}" ]]; then
log "OPNSense: gateway + static route voor ${VM_PUBLIC_IP}..."
GW_NAME="GW_$(echo ${VM_NAME} | tr '[:lower:]' '[:upper:]')"
cat > "${SCRATCHDIR}/add_route.py" << PYEOF
import xml.etree.ElementTree as ET, uuid, sys
tree = ET.parse('/conf/config.xml')
root = tree.getroot()
name, gw_ip, pub_ip, iface = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
gws = root.find('OPNsense/Gateways')
for old in gws.findall('gateway_item'):
if old.findtext('name') == name:
gws.remove(old)
gw = ET.SubElement(gws, 'gateway_item')
gw.set('uuid', str(uuid.uuid4()))
for tag, val in [
('disabled','0'),('name',name),('descr',name),
('interface',iface),('ipprotocol','inet'),('gateway',gw_ip),
('defaultgw','0'),('fargw','0'),('monitor_disable','1'),
('force_down','0'),('nosync','0'),('priority','255'),('weight','1')
]:
ET.SubElement(gw, tag).text = val
sr = root.find('staticroutes')
for old in sr.findall('route'):
if old.findtext('network') == pub_ip:
sr.remove(old)
route = ET.SubElement(sr, 'route')
route.set('uuid', str(uuid.uuid4()))
for tag, val in [('network',pub_ip),('gateway',name),('descr',name),('enabled','1')]:
ET.SubElement(route, tag).text = val
tree.write('/conf/config.xml')
print('done')
PYEOF
# fw01 root shell is csh — altijd wrappen in sh -c, anders verminkt csh
# fd-redirects (2>/dev/null) en kan een bogus 0.0.0.0/1 route aanmaken.
scp "${SCRATCHDIR}/add_route.py" fw01:/tmp/add_route.py
ssh fw01 "sh -c 'python3 /tmp/add_route.py \
${GW_NAME} ${VM_PRIVATE_IP} ${VM_PUBLIC_IP}/32 opt5 \
&& rm /tmp/add_route.py'"
ssh fw01 "sh -c 'configctl filter reload'"
log "✓ OPNSense route actief."
fi
log "✓ VM klaar: ${VM_PRIVATE_IP}$([ -n '${VM_PUBLIC_IP}' ] && echo " / VIP ${VM_PUBLIC_IP}" || true)"
log " Pas TARGET_SSH aan in config.sh en ga verder met 01-transfer.sh"