diff --git a/playbook/pxe_server_setup.yml b/playbook/pxe_server_setup.yml
index 0932bca..8cee93c 100644
--- a/playbook/pxe_server_setup.yml
+++ b/playbook/pxe_server_setup.yml
@@ -331,6 +331,7 @@
port: "{{ item }}"
proto: "{{ 'udp' if item in ['67','69'] else 'tcp' }}"
loop:
+ - "22"
- "67"
- "69"
- "80"
@@ -430,6 +431,7 @@
copy:
dest: /etc/apache2/sites-available/pxe-server.conf
content: |
+ Listen 9009
DocumentRoot {{ web_root }}
@@ -438,8 +440,19 @@
Require all granted
ProxyPreserveHost On
- ProxyPass /manage http://127.0.0.1:9009/
- ProxyPassReverse /manage http://127.0.0.1:9009/
+ ProxyPass /manage http://127.0.0.1:9010/
+ ProxyPassReverse /manage http://127.0.0.1:9010/
+
+
+ Alias /static /opt/pxe-webapp/static
+
+ Require all granted
+ Options -Indexes
+
+ ProxyPreserveHost On
+ ProxyPass /static !
+ ProxyPass / http://127.0.0.1:9010/
+ ProxyPassReverse / http://127.0.0.1:9010/
- name: "Enable Apache proxy modules"
diff --git a/test-lab.sh b/test-lab.sh
new file mode 100755
index 0000000..637febf
--- /dev/null
+++ b/test-lab.sh
@@ -0,0 +1,332 @@
+#!/bin/bash
+#
+# test-lab.sh — Full PXE lab: server + client VMs on an isolated network
+#
+# Creates an isolated libvirt network, boots the PXE server from the
+# Proxmox installer ISO, then launches a UEFI PXE client to test the
+# full boot chain (DHCP -> TFTP -> iPXE -> boot menu).
+#
+# Usage:
+# ./test-lab.sh /path/to/ubuntu-24.04.iso # Launch server
+# ./test-lab.sh --client # Launch PXE client
+# ./test-lab.sh --status # Check if server is ready
+# ./test-lab.sh --destroy # Remove everything
+#
+# Workflow:
+# 1. Run the script with the Ubuntu ISO — server VM starts installing
+# 2. Wait ~15 minutes (monitor with: sudo virsh console pxe-lab-server)
+# 3. Run --status to check if PXE services are up
+# 4. Run --client to launch a PXE client VM
+# 5. Open virt-viewer to watch the client PXE boot:
+# virt-viewer pxe-lab-client
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+NET_NAME="pxe-lab"
+SERVER_NAME="pxe-lab-server"
+CLIENT_NAME="pxe-lab-client"
+SERVER_DISK="/var/lib/libvirt/images/${SERVER_NAME}.qcow2"
+PROXMOX_ISO="$SCRIPT_DIR/pxe-server-proxmox.iso"
+VM_RAM=4096
+VM_CPUS=2
+VM_DISK_SIZE=40 # GB
+
+# --- Helper: check if we can run virsh ---
+check_virsh() {
+ if ! virsh net-list &>/dev/null; then
+ echo "ERROR: Cannot connect to libvirt. Are you in the 'libvirt' group?"
+ echo " sudo usermod -aG libvirt $USER && newgrp libvirt"
+ exit 1
+ fi
+}
+
+# --- Helper: ensure network exists ---
+ensure_network() {
+ if ! virsh net-info "$NET_NAME" &>/dev/null; then
+ echo "Creating isolated network ($NET_NAME)..."
+ NET_XML=$(mktemp)
+ cat > "$NET_XML" << EOF
+
+ $NET_NAME
+
+
+EOF
+ virsh net-define "$NET_XML" >/dev/null
+ rm "$NET_XML"
+ fi
+ if ! virsh net-info "$NET_NAME" 2>/dev/null | grep -q "Active:.*yes"; then
+ virsh net-start "$NET_NAME" >/dev/null
+ fi
+}
+
+# =====================================================================
+# --destroy: Remove everything
+# =====================================================================
+if [ "${1:-}" = "--destroy" ]; then
+ check_virsh
+ echo "Destroying PXE lab environment..."
+ virsh destroy "$CLIENT_NAME" 2>/dev/null || true
+ virsh undefine "$CLIENT_NAME" --nvram 2>/dev/null || true
+ virsh vol-delete "${CLIENT_NAME}.qcow2" --pool default 2>/dev/null || true
+ virsh destroy "$SERVER_NAME" 2>/dev/null || true
+ virsh undefine "$SERVER_NAME" 2>/dev/null || true
+ virsh vol-delete "${SERVER_NAME}.qcow2" --pool default 2>/dev/null || true
+ rm -f "/tmp/${SERVER_NAME}-vmlinuz" "/tmp/${SERVER_NAME}-initrd"
+ virsh net-destroy "$NET_NAME" 2>/dev/null || true
+ virsh net-undefine "$NET_NAME" 2>/dev/null || true
+ echo "Done."
+ exit 0
+fi
+
+# =====================================================================
+# --status: Check if PXE server is ready
+# =====================================================================
+if [ "${1:-}" = "--status" ]; then
+ check_virsh
+ echo "PXE Lab Status"
+ echo "============================================"
+
+ # Network
+ if virsh net-info "$NET_NAME" &>/dev/null; then
+ echo " Network ($NET_NAME): $(virsh net-info "$NET_NAME" 2>/dev/null | grep Active | awk '{print $2}')"
+ else
+ echo " Network ($NET_NAME): not defined"
+ fi
+
+ # Server VM
+ if virsh dominfo "$SERVER_NAME" &>/dev/null; then
+ STATE=$(virsh domstate "$SERVER_NAME" 2>/dev/null)
+ echo " Server ($SERVER_NAME): $STATE"
+ else
+ echo " Server ($SERVER_NAME): not defined"
+ fi
+
+ # Client VM
+ if virsh dominfo "$CLIENT_NAME" &>/dev/null; then
+ STATE=$(virsh domstate "$CLIENT_NAME" 2>/dev/null)
+ echo " Client ($CLIENT_NAME): $STATE"
+ else
+ echo " Client ($CLIENT_NAME): not defined"
+ fi
+
+ # Try to check PXE services on the server
+ # Add a temporary IP to the bridge so we can reach the server
+ echo ""
+ echo "Checking PXE server services..."
+ BRIDGE_HAS_IP=false
+ ADDED_IP=false
+ if ip addr show virbr-pxe 2>/dev/null | grep -q "10.9.100.254"; then
+ BRIDGE_HAS_IP=true
+ else
+ # Need sudo for IP manipulation — try it, skip if unavailable
+ if sudo -n ip addr add 10.9.100.254/24 dev virbr-pxe 2>/dev/null; then
+ BRIDGE_HAS_IP=true
+ ADDED_IP=true
+ fi
+ fi
+
+ if [ "$BRIDGE_HAS_IP" = true ]; then
+ # Check each service with a short timeout
+ for check in \
+ "DHCP/TFTP (dnsmasq):10.9.100.1:69:udp" \
+ "HTTP (Apache):10.9.100.1:80:tcp" \
+ "iPXE boot script:10.9.100.1:4433:tcp" \
+ "Samba:10.9.100.1:445:tcp" \
+ "Webapp:10.9.100.1:9009:tcp"; do
+ LABEL="${check%%:*}"
+ REST="${check#*:}"
+ HOST="${REST%%:*}"
+ REST="${REST#*:}"
+ PORT="${REST%%:*}"
+ PROTO="${REST#*:}"
+
+ if [ "$PROTO" = "tcp" ]; then
+ if timeout 2 bash -c "echo >/dev/tcp/$HOST/$PORT" 2>/dev/null; then
+ echo " [UP] $LABEL (port $PORT)"
+ else
+ echo " [DOWN] $LABEL (port $PORT)"
+ fi
+ else
+ # UDP — just check if host is reachable
+ if ping -c1 -W1 "$HOST" &>/dev/null; then
+ echo " [PING] $LABEL (host reachable)"
+ else
+ echo " [DOWN] $LABEL (host unreachable)"
+ fi
+ fi
+ done
+
+ # Clean up the temporary IP (only if we added it)
+ if [ "$ADDED_IP" = true ]; then
+ sudo -n ip addr del 10.9.100.254/24 dev virbr-pxe 2>/dev/null || true
+ fi
+ else
+ echo " (Cannot reach server — bridge not available)"
+ echo " Use 'sudo virsh console $SERVER_NAME' to check manually"
+ fi
+
+ echo ""
+ echo "Commands:"
+ echo " sudo virsh console $SERVER_NAME # Server serial console"
+ echo " virt-viewer $CLIENT_NAME # Client VNC display"
+ exit 0
+fi
+
+# =====================================================================
+# --client: Launch PXE client VM
+# =====================================================================
+if [ "${1:-}" = "--client" ]; then
+ check_virsh
+ ensure_network
+
+ # Check if server VM exists and is running
+ if ! virsh domstate "$SERVER_NAME" 2>/dev/null | grep -q "running"; then
+ echo "WARNING: Server VM ($SERVER_NAME) is not running."
+ echo " The PXE client needs the server for DHCP and boot files."
+ read -rp " Continue anyway? (y/N): " PROCEED
+ if [[ ! "$PROCEED" =~ ^[Yy]$ ]]; then
+ exit 1
+ fi
+ fi
+
+ # Remove existing client if present
+ if virsh dominfo "$CLIENT_NAME" &>/dev/null; then
+ echo "Removing existing client VM..."
+ virsh destroy "$CLIENT_NAME" 2>/dev/null || true
+ virsh undefine "$CLIENT_NAME" --nvram 2>/dev/null || true
+ virsh vol-delete "${CLIENT_NAME}.qcow2" --pool default 2>/dev/null || true
+ fi
+
+ echo "Launching PXE client ($CLIENT_NAME)..."
+ echo " UEFI PXE boot on network: $NET_NAME"
+ echo ""
+
+ virsh vol-create-as default "${CLIENT_NAME}.qcow2" "${VM_DISK_SIZE}G" --format qcow2 >/dev/null
+
+ virt-install \
+ --name "$CLIENT_NAME" \
+ --memory "$VM_RAM" \
+ --vcpus "$VM_CPUS" \
+ --disk "vol=default/${CLIENT_NAME}.qcow2" \
+ --network network="$NET_NAME",model=virtio \
+ --os-variant ubuntu24.04 \
+ --boot uefi,network \
+ --graphics vnc,listen=0.0.0.0 \
+ --noautoconsole
+
+ # Get VNC port
+ VNC_PORT=$(virsh vncdisplay "$CLIENT_NAME" 2>/dev/null | sed 's/://' || echo "?")
+ VNC_PORT=$((5900 + VNC_PORT))
+
+ echo ""
+ echo "============================================"
+ echo "PXE client launched!"
+ echo "============================================"
+ echo ""
+ echo "Watch the PXE boot:"
+ echo " virt-viewer $CLIENT_NAME"
+ echo " (or VNC to localhost:$VNC_PORT)"
+ echo ""
+ echo "Expected boot sequence:"
+ echo " 1. UEFI firmware -> PXE boot"
+ echo " 2. DHCP from server (10.9.100.x)"
+ echo " 3. TFTP download ipxe.efi"
+ echo " 4. iPXE loads boot menu from port 4433"
+ echo " 5. GE Aerospace PXE Boot Menu appears"
+ echo ""
+ echo "Manage:"
+ echo " sudo virsh reboot $CLIENT_NAME # Retry PXE boot"
+ echo " sudo virsh destroy $CLIENT_NAME # Stop client"
+ echo " $0 --destroy # Remove everything"
+ exit 0
+fi
+
+# =====================================================================
+# Default: Launch PXE server VM
+# =====================================================================
+check_virsh
+
+UBUNTU_ISO="${1:-}"
+if [ -z "$UBUNTU_ISO" ] || [ ! -f "$UBUNTU_ISO" ]; then
+ echo "Usage: sudo $0 /path/to/ubuntu-24.04-live-server-amd64.iso"
+ echo ""
+ echo "Commands:"
+ echo " $0 /path/to/ubuntu.iso Launch PXE server VM"
+ echo " $0 --client Launch PXE client VM"
+ echo " $0 --status Check server readiness"
+ echo " $0 --destroy Remove everything"
+ exit 1
+fi
+
+# Check if Proxmox ISO exists, build if not
+if [ ! -f "$PROXMOX_ISO" ]; then
+ echo "Proxmox ISO not found. Building it first..."
+ echo ""
+ "$SCRIPT_DIR/build-proxmox-iso.sh" "$UBUNTU_ISO"
+ echo ""
+fi
+
+# Check server doesn't already exist
+if virsh dominfo "$SERVER_NAME" &>/dev/null; then
+ echo "ERROR: Server VM already exists. Destroy first with: sudo $0 --destroy"
+ exit 1
+fi
+
+echo "============================================"
+echo "PXE Lab Environment Setup"
+echo "============================================"
+echo ""
+
+# --- Step 1: Create isolated network ---
+echo "[1/3] Setting up isolated network ($NET_NAME)..."
+ensure_network
+echo " Bridge: virbr-pxe (isolated, no host DHCP)"
+
+# --- Step 2: Extract kernel/initrd for direct boot ---
+echo "[2/3] Extracting kernel and initrd from ISO..."
+KERNEL="/tmp/${SERVER_NAME}-vmlinuz"
+INITRD="/tmp/${SERVER_NAME}-initrd"
+7z e -o/tmp -y "$PROXMOX_ISO" casper/vmlinuz casper/initrd >/dev/null 2>&1
+mv /tmp/vmlinuz "$KERNEL"
+mv /tmp/initrd "$INITRD"
+echo " Extracted vmlinuz and initrd"
+
+# --- Step 3: Launch server VM ---
+echo "[3/3] Launching PXE server ($SERVER_NAME)..."
+
+virsh vol-create-as default "${SERVER_NAME}.qcow2" "${VM_DISK_SIZE}G" --format qcow2 >/dev/null
+
+virt-install \
+ --name "$SERVER_NAME" \
+ --memory "$VM_RAM" \
+ --vcpus "$VM_CPUS" \
+ --disk "vol=default/${SERVER_NAME}.qcow2" \
+ --disk path="$PROXMOX_ISO",device=cdrom,readonly=on \
+ --network network="$NET_NAME" \
+ --os-variant ubuntu24.04 \
+ --graphics none \
+ --console pty,target_type=serial \
+ --install kernel="$KERNEL",initrd="$INITRD",kernel_args="console=ttyS0,115200n8 autoinstall ds=nocloud\;s=/cdrom/server/" \
+ --noautoconsole
+
+echo ""
+echo "============================================"
+echo "PXE server VM launched!"
+echo "============================================"
+echo ""
+echo "The autoinstall + first-boot will take ~15 minutes."
+echo ""
+echo "Step 1 — Monitor the server install:"
+echo " virsh console $SERVER_NAME"
+echo " (Ctrl+] to detach)"
+echo ""
+echo "Step 2 — Check when services are ready:"
+echo " $0 --status"
+echo ""
+echo "Step 3 — Launch a PXE client to test booting:"
+echo " $0 --client"
+echo ""
+echo "Cleanup:"
+echo " $0 --destroy"
+echo ""
diff --git a/webapp/app.py b/webapp/app.py
index e50d235..8745707 100644
--- a/webapp/app.py
+++ b/webapp/app.py
@@ -958,4 +958,4 @@ def inject_globals():
# ---------------------------------------------------------------------------
if __name__ == "__main__":
- app.run(host="0.0.0.0", port=9009, debug=False)
+ app.run(host="127.0.0.1", port=9010, debug=False, threaded=True)
diff --git a/webapp/templates/base.html b/webapp/templates/base.html
index 58d9b42..cf90eb2 100644
--- a/webapp/templates/base.html
+++ b/webapp/templates/base.html
@@ -55,8 +55,9 @@
color: #fff;
border-bottom: 1px solid rgba(255,255,255,0.08);
display: flex;
+ flex-direction: column;
align-items: center;
- gap: 0.5rem;
+ gap: 0.3rem;
}
.sidebar .brand .bi {
font-size: 1.3rem;
diff --git a/webapp/templates/startnet_editor.html b/webapp/templates/startnet_editor.html
index 1f2a139..a90f10f 100644
--- a/webapp/templates/startnet_editor.html
+++ b/webapp/templates/startnet_editor.html
@@ -6,7 +6,9 @@
.cmd-editor {
font-family: 'Consolas', 'Courier New', monospace;
font-size: 0.9rem;
- min-height: 400px;
+ min-height: 600px;
+ height: 70vh;
+ resize: vertical;
background-color: #1e1e1e;
color: #d4d4d4;
border: 1px solid #333;
diff --git a/webapp/templates/unattend_editor.html b/webapp/templates/unattend_editor.html
index 2dfd2c1..72c5638 100644
--- a/webapp/templates/unattend_editor.html
+++ b/webapp/templates/unattend_editor.html
@@ -16,7 +16,8 @@
.raw-xml-editor {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.85rem;
- min-height: 500px;
+ min-height: 600px;
+ height: 70vh;
tab-size: 2;
white-space: pre;
resize: vertical;