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>
This commit is contained in:
2026-07-07 01:18:01 +02:00
commit 2626036a21
9 changed files with 1311 additions and 0 deletions
+210
View File
@@ -0,0 +1,210 @@
#!/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"