2626036a21
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>
211 lines
8.2 KiB
Bash
Executable File
211 lines
8.2 KiB
Bash
Executable File
#!/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 (01–05) 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"
|