Files
darkforge/tests/proxmox/create-vm.sh
Danny fe6ee25d1c Fix Proxmox VM login — force user creation via runcmd
The Arch Linux cloud image ignores Proxmox's --ciuser/--cipassword
and the cloud-init chpasswd module depending on version. The previous
approach had three conflicting methods fighting each other.

Fixed by:
- Removed --ciuser/--cipassword from qm set (they conflict with snippet)
- Removed chpasswd cloud-init module (unreliable on Arch)
- Set users: [] to disable cloud-init's default user module
- ALL user setup now done via runcmd (runs as root, always works):
  - Sets root password to 'darkforge' as fallback
  - Creates darkforge user via useradd + chpasswd
  - Grants passwordless sudo via /etc/sudoers.d/
  - Enables PermitRootLogin yes as safety net
- Package install via explicit pacman commands instead of packages: module
  (Arch cloud-init packages module can be unreliable)
- Added pacman-key --init/--populate before package install

Login credentials:
  user: darkforge  password: darkforge
  user: root       password: darkforge  (fallback)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:48:24 +01:00

233 lines
8.6 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# DarkForge — Proxmox VM Creation Script
# ============================================================================
# Run this on the Proxmox host to create an Arch Linux test VM.
#
# Requirements:
# - Proxmox VE 8.x or 9.x
# - ~100GB free on a storage pool
# - Internet access
#
# Usage:
# bash create-vm.sh # use defaults
# bash create-vm.sh --vmid 200 # custom VM ID
# bash create-vm.sh --storage local-lvm # custom storage
# ============================================================================
set -euo pipefail
# --- Configuration (override via environment or flags) -----------------------
VMID="${VMID:-900}"
VM_NAME="${VM_NAME:-darkforge-test}"
STORAGE="${STORAGE:-local-lvm}"
DISK_SIZE="${DISK_SIZE:-100G}"
RAM="${RAM:-28672}" # 16GB
CORES="${CORES:-18}"
BRIDGE="${BRIDGE:-vmbr0}"
# Arch Linux cloud image
ARCH_IMG_URL="https://geo.mirror.pkgbuild.com/images/latest/Arch-Linux-x86_64-cloudimg.qcow2"
ARCH_IMG_FILE="/var/lib/vz/template/iso/arch-cloudimg.qcow2"
# Git repo to clone inside the VM
DARKFORGE_REPO="gitea@git.dannyhaslund.dk:danny8632/darkforge.git"
# Fallback if SSH key isn't available in the VM
DARKFORGE_REPO_HTTPS="https://git.dannyhaslund.dk/danny8632/darkforge.git"
# Parse args
for arg in "$@"; do
case "$arg" in
--vmid=*) VMID="${arg#*=}" ;;
--storage=*) STORAGE="${arg#*=}" ;;
--cores=*) CORES="${arg#*=}" ;;
--ram=*) RAM="${arg#*=}" ;;
--bridge=*) BRIDGE="${arg#*=}" ;;
esac
done
echo "═══════════════════════════════════════════════"
echo " DarkForge Test VM Creator"
echo " VMID: ${VMID} | Cores: ${CORES} | RAM: ${RAM}MB"
echo " Storage: ${STORAGE} | Disk: ${DISK_SIZE}"
echo "═══════════════════════════════════════════════"
echo ""
# --- Check if VM already exists ----------------------------------------------
if qm status "${VMID}" &>/dev/null; then
echo "VM ${VMID} already exists."
read -p "Destroy and recreate? [y/N] " confirm
if [[ "${confirm}" =~ ^[Yy]$ ]]; then
qm stop "${VMID}" 2>/dev/null || true
sleep 3
qm destroy "${VMID}" --purge
echo "Old VM destroyed."
else
echo "Aborted."
exit 0
fi
fi
# --- Download Arch cloud image if not cached ---------------------------------
if [ ! -f "${ARCH_IMG_FILE}" ]; then
echo ">>> Downloading Arch Linux cloud image..."
mkdir -p "$(dirname "${ARCH_IMG_FILE}")"
wget -q --show-progress -O "${ARCH_IMG_FILE}" "${ARCH_IMG_URL}"
echo ">>> Downloaded: ${ARCH_IMG_FILE}"
else
echo ">>> Using cached image: ${ARCH_IMG_FILE}"
fi
# --- Create the VM -----------------------------------------------------------
echo ">>> Creating VM ${VMID}..."
qm create "${VMID}" \
--name "${VM_NAME}" \
--ostype l26 \
--machine q35 \
--bios ovmf \
--cpu host \
--cores "${CORES}" \
--memory "${RAM}" \
--net0 "virtio,bridge=${BRIDGE}" \
--agent enabled=1 \
--serial0 socket \
--vga serial0
# Add EFI disk (required for OVMF BIOS)
qm set "${VMID}" --efidisk0 "${STORAGE}:1,efitype=4m,pre-enrolled-keys=0"
# Import the cloud image as the boot disk
echo ">>> Importing cloud image as boot disk..."
qm importdisk "${VMID}" "${ARCH_IMG_FILE}" "${STORAGE}" --format qcow2 2>/dev/null
qm set "${VMID}" --scsi0 "${STORAGE}:vm-${VMID}-disk-1,size=${DISK_SIZE}"
qm set "${VMID}" --boot order=scsi0
qm set "${VMID}" --scsihw virtio-scsi-single
# --- Configure cloud-init ----------------------------------------------------
echo ">>> Configuring cloud-init..."
qm set "${VMID}" --ide2 "${STORAGE}:cloudinit"
# Don't use --ciuser/--cipassword — they conflict with the snippet on Arch.
# We handle ALL user creation in the cloud-init snippet runcmd instead.
qm set "${VMID}" --ipconfig0 "ip=dhcp"
# Enable nested virtualization (for QEMU-in-QEMU boot tests)
qm set "${VMID}" --args "-cpu host,+vmx"
# Resize the disk to our desired size
echo ">>> Resizing disk to ${DISK_SIZE}..."
qm resize "${VMID}" scsi0 "${DISK_SIZE}" 2>/dev/null || true
# --- Generate cloud-init user-data snippet -----------------------------------
# NOTE: The Arch Linux cloud image has quirks with user/password handling.
# We do EVERYTHING via runcmd to guarantee it works regardless of cloud-init
# version or Proxmox's cloud-init integration behavior.
SNIPPET_DIR="/var/lib/vz/snippets"
mkdir -p "${SNIPPET_DIR}"
cat > "${SNIPPET_DIR}/darkforge-test-init.yaml" << 'CLOUDINIT'
#cloud-config
# Disable the default user module to avoid conflicts
# We create our user manually via runcmd below
users: []
ssh_pwauth: true
write_files:
- path: /etc/ssh/sshd_config.d/99-darkforge.conf
content: |
PasswordAuthentication yes
PermitRootLogin yes
runcmd:
# --- USER SETUP (do this first, before anything else) ----------------------
# Set root password so we always have a fallback login
- echo 'root:darkforge' | chpasswd
# Create the darkforge user if it doesn't exist
- id darkforge &>/dev/null || useradd -m -G wheel -s /bin/bash darkforge
- echo 'darkforge:darkforge' | chpasswd
# Give darkforge sudo/wheel access without password
- echo 'darkforge ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/darkforge
- chmod 440 /etc/sudoers.d/darkforge
# Enable and restart sshd
- systemctl enable sshd
- systemctl restart sshd
# --- DISK RESIZE -----------------------------------------------------------
- growpart /dev/sda 2 || growpart /dev/vda 2 || true
- resize2fs /dev/sda2 || resize2fs /dev/vda2 || btrfs filesystem resize max / || true
# --- PACKAGE INSTALL -------------------------------------------------------
- pacman-key --init
- pacman-key --populate archlinux
- pacman -Syu --noconfirm
- pacman -S --noconfirm --needed base-devel git wget curl rust cargo qemu-full edk2-ovmf squashfs-tools xorriso dosfstools mtools python bc rsync openssh tmux
# --- CLONE PROJECT ---------------------------------------------------------
- |
su - darkforge -c '
cd /home/darkforge
git clone --recurse-submodules https://git.dannyhaslund.dk/danny8632/darkforge.git 2>/dev/null || \
git clone --recurse-submodules https://github.com/danny8632/darkforge.git 2>/dev/null || \
echo "CLONE FAILED — manually clone the repo after login"
'
# --- INSTALL CONVENIENCE COMMAND -------------------------------------------
- |
cat > /usr/local/bin/darkforge-test << 'DTEOF'
#!/bin/bash
SCRIPT="/home/darkforge/darkforge/tests/proxmox/run-in-vm.sh"
if [ ! -f "$SCRIPT" ]; then
echo "ERROR: Test script not found. Is the repo cloned?"
echo " git clone --recurse-submodules https://git.dannyhaslund.dk/danny8632/darkforge.git ~/darkforge"
exit 1
fi
ARGS="$*"
exec tmux new-session -d -s darkforge \
"bash ${SCRIPT} --tmux ${ARGS}; echo ''; echo 'Tests finished. Press Enter to close.'; read" \; \
attach-session -t darkforge
DTEOF
chmod +x /usr/local/bin/darkforge-test
# --- SIGNAL DONE -----------------------------------------------------------
- touch /home/darkforge/.provisioned
- chown darkforge:darkforge /home/darkforge/.provisioned
CLOUDINIT
qm set "${VMID}" --cicustom "user=local:snippets/darkforge-test-init.yaml"
# --- Start the VM ------------------------------------------------------------
echo ""
echo ">>> Starting VM ${VMID}..."
qm start "${VMID}"
echo ""
echo "═══════════════════════════════════════════════"
echo " VM ${VMID} created and starting."
echo ""
echo " Cloud-init will install packages and clone the repo."
echo " Wait ~5 min for provisioning, then SSH in to run tests."
echo ""
echo " Get the VM IP:"
echo " qm guest cmd ${VMID} network-get-interfaces | grep -oP '\"ip-address\":\\s*\"\\K[0-9.]+'"
echo ""
echo " SSH in:"
echo " ssh darkforge@<IP> (password: darkforge)"
echo ""
echo " Run tests in a tmux session (detachable):"
echo " darkforge-test # starts tests in tmux"
echo " darkforge-test --quick # fast mode (30 min)"
echo ""
echo " Detach from tmux: Ctrl+B then D"
echo " Reattach later: tmux attach -t darkforge"
echo ""
echo " Collect report:"
echo " scp darkforge@<IP>:~/darkforge/tests/report.* ./"
echo "═══════════════════════════════════════════════"