From b9d042e899afe6e00402734fee14bd9a45bf8327 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Feb 2026 13:01:44 +0000 Subject: [PATCH 1/3] Add Proxmox VE infrastructure setup scripts and documentation - Network: NAT/MASQUERADE, PVE firewall rules, SSH hardening, Fail2Ban - VM Templates: storage backend config, Cloud-Init template creation - Backup: vzdump backup script with cronjob installer - GPU: passthrough readiness check (IOMMU, vfio-pci, nvidia-smi) - Docs: setup guide with recommended execution order - Updated .gitignore for Proxmox artifacts (ISOs, disk images, keys) https://claude.ai/code/session_01SWoB1YbMmuPwSv5964mbE8 --- .gitignore | 10 ++ docs/proxmox-setup.md | 94 +++++++++++++++ proxmox/backup/install-backup-cronjob.sh | 59 +++++++++ proxmox/backup/vzdump-backup.sh | 43 +++++++ proxmox/gpu/gpu-check.sh | 113 ++++++++++++++++++ proxmox/network/harden-ssh.sh | 63 ++++++++++ proxmox/network/setup-fail2ban.sh | 63 ++++++++++ proxmox/network/setup-firewall.sh | 61 ++++++++++ proxmox/network/setup-nat.sh | 48 ++++++++ .../create-cloud-init-template.sh | 84 +++++++++++++ proxmox/vm-templates/setup-storage.sh | 68 +++++++++++ 11 files changed, 706 insertions(+) create mode 100644 docs/proxmox-setup.md create mode 100755 proxmox/backup/install-backup-cronjob.sh create mode 100755 proxmox/backup/vzdump-backup.sh create mode 100755 proxmox/gpu/gpu-check.sh create mode 100755 proxmox/network/harden-ssh.sh create mode 100755 proxmox/network/setup-fail2ban.sh create mode 100755 proxmox/network/setup-firewall.sh create mode 100755 proxmox/network/setup-nat.sh create mode 100755 proxmox/vm-templates/create-cloud-init-template.sh create mode 100755 proxmox/vm-templates/setup-storage.sh diff --git a/.gitignore b/.gitignore index 15428c3..b0115ec 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,13 @@ dist/ logs/ gordon-mcp.yml /.idea/ + +# Proxmox artifacts +*.iso +*.img +*.qcow2 +*.raw +*.vmdk +proxmox/secrets/ +*.pem +*.key diff --git a/docs/proxmox-setup.md b/docs/proxmox-setup.md new file mode 100644 index 0000000..1cc5863 --- /dev/null +++ b/docs/proxmox-setup.md @@ -0,0 +1,94 @@ +# Proxmox VE Setup Guide + +This document describes the scripts in the `proxmox/` directory and how to use them to set up and harden a Proxmox VE host. + +## Directory Structure + +``` +proxmox/ +├── network/ +│ ├── setup-nat.sh # NAT/MASQUERADE for internal VMs +│ ├── setup-firewall.sh # PVE firewall rules (cluster + host) +│ ├── harden-ssh.sh # SSH hardening (key-only, disable root pw) +│ └── setup-fail2ban.sh # Fail2Ban for SSH + PVE Web GUI +├── vm-templates/ +│ ├── setup-storage.sh # Storage backends (local-lvm, NFS, ZFS) +│ └── create-cloud-init-template.sh # Cloud-Init VM template +├── backup/ +│ ├── vzdump-backup.sh # vzdump backup for all VMs/CTs +│ └── install-backup-cronjob.sh # Install cron job for automated backups +└── gpu/ + └── gpu-check.sh # GPU passthrough readiness check +``` + +## Prerequisites + +- Proxmox VE 7.x or 8.x installed +- Root access to the Proxmox host +- SSH key pair for key-based authentication + +## Recommended Execution Order + +### 1. Network & Security + +```bash +# a) Set up NAT for internal VM network +sudo bash proxmox/network/setup-nat.sh vmbr0 vmbr1 10.10.10.0/24 + +# b) Configure PVE firewall rules +sudo bash proxmox/network/setup-firewall.sh + +# c) Harden SSH (optionally change port) +sudo bash proxmox/network/harden-ssh.sh 2222 + +# d) Install Fail2Ban (block after 3 failures, ban for 1 hour) +sudo bash proxmox/network/setup-fail2ban.sh 3 3600 +``` + +### 2. Storage & VM Templates + +```bash +# a) Configure storage backends +sudo bash proxmox/vm-templates/setup-storage.sh + +# b) Create a Cloud-Init Ubuntu template (VMID 9000) +sudo bash proxmox/vm-templates/create-cloud-init-template.sh 9000 ubuntu-2204-cloud + +# Clone a VM from the template: +qm clone 9000 100 --name my-vm --full +qm set 100 --ciuser myuser --sshkeys ~/.ssh/id_rsa.pub --ipconfig0 ip=10.10.10.100/24,gw=10.10.10.1 +qm start 100 +``` + +### 3. Backups + +```bash +# a) Install daily backup cronjob (runs at 02:00) +sudo bash proxmox/backup/install-backup-cronjob.sh daily + +# b) Or run a manual backup +sudo bash proxmox/backup/vzdump-backup.sh local snapshot 3 +``` + +### 4. GPU Passthrough + +```bash +# Check GPU passthrough readiness +sudo bash proxmox/gpu/gpu-check.sh +``` + +The GPU check script verifies: +- IOMMU is enabled in the kernel +- IOMMU groups are correctly formed +- GPUs are detected via `lspci` +- vfio-pci driver bindings +- NVIDIA VRAM via `nvidia-smi` (if available) + +## Customization + +- **NAT subnet**: Change the third argument to `setup-nat.sh` +- **SSH port**: Pass desired port to `harden-ssh.sh` +- **Fail2Ban thresholds**: Adjust `MAX_RETRY` and `BAN_TIME` in `setup-fail2ban.sh` +- **Backup schedule**: Use `daily`, `weekly`, or `custom` with `install-backup-cronjob.sh` +- **VM template**: Change the image URL in `create-cloud-init-template.sh` for Debian, Rocky, etc. +- **NFS/ZFS storage**: Uncomment the relevant sections in `setup-storage.sh` diff --git a/proxmox/backup/install-backup-cronjob.sh b/proxmox/backup/install-backup-cronjob.sh new file mode 100755 index 0000000..07d9acd --- /dev/null +++ b/proxmox/backup/install-backup-cronjob.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# install-backup-cronjob.sh — Install a cron job for automated VM backups +# +# Usage: sudo bash install-backup-cronjob.sh [SCHEDULE] +# +# SCHEDULE examples: +# daily — Every day at 02:00 (default) +# weekly — Every Sunday at 03:00 +# custom — Prompts for a custom cron expression + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +BACKUP_SCRIPT="$SCRIPT_DIR/vzdump-backup.sh" +CRON_FILE="/etc/cron.d/proxmox-backup" +SCHEDULE="${1:-daily}" + +echo "==> Installing backup cronjob" +echo " Schedule: $SCHEDULE" +echo " Script: $BACKUP_SCRIPT" + +# Ensure the backup script is executable +chmod +x "$BACKUP_SCRIPT" + +case "$SCHEDULE" in + daily) + CRON_EXPR="0 2 * * *" + ;; + weekly) + CRON_EXPR="0 3 * * 0" + ;; + custom) + read -rp "Enter cron expression (e.g., '0 4 * * 1,4'): " CRON_EXPR + ;; + *) + echo "ERROR: Unknown schedule '$SCHEDULE'. Use: daily, weekly, or custom." + exit 1 + ;; +esac + +# Write the cron file +cat > "$CRON_FILE" <> /var/log/proxmox-backup-cron.log 2>&1 +EOF + +chmod 644 "$CRON_FILE" + +echo "==> Cronjob installed: $CRON_FILE" +echo " Expression: $CRON_EXPR" +echo "" +echo "Verify with: cat $CRON_FILE" +echo "Check logs: tail -f /var/log/proxmox-backup-cron.log" diff --git a/proxmox/backup/vzdump-backup.sh b/proxmox/backup/vzdump-backup.sh new file mode 100755 index 0000000..84ab456 --- /dev/null +++ b/proxmox/backup/vzdump-backup.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# vzdump-backup.sh — Backup all VMs and containers using vzdump +# Designed to be called from a cronjob. +# +# Usage: sudo bash vzdump-backup.sh [STORAGE] [MODE] [MAX_BACKUPS] +# +# MODE: snapshot (default, no downtime), suspend, or stop +# MAX_BACKUPS: number of backups to keep per VM (prune older ones) + +set -euo pipefail + +STORAGE="${1:-local}" +MODE="${2:-snapshot}" +MAX_BACKUPS="${3:-3}" +LOGFILE="/var/log/vzdump-backup-$(date +%Y%m%d-%H%M%S).log" +MAILTO="${MAILTO:-root}" + +echo "==> Starting vzdump backup — $(date)" | tee "$LOGFILE" +echo " Storage: $STORAGE" | tee -a "$LOGFILE" +echo " Mode: $MODE" | tee -a "$LOGFILE" +echo " Max backups: $MAX_BACKUPS" | tee -a "$LOGFILE" + +# Backup all VMs and containers +vzdump --all \ + --storage "$STORAGE" \ + --mode "$MODE" \ + --compress zstd \ + --prune-backups keep-last="$MAX_BACKUPS" \ + --mailto "$MAILTO" \ + --quiet 2>&1 | tee -a "$LOGFILE" + +EXIT_CODE=${PIPESTATUS[0]} + +if [[ $EXIT_CODE -eq 0 ]]; then + echo "==> Backup completed successfully — $(date)" | tee -a "$LOGFILE" +else + echo "==> ERROR: Backup failed with exit code $EXIT_CODE — $(date)" | tee -a "$LOGFILE" +fi + +# Cleanup old log files (keep last 30) +find /var/log -name 'vzdump-backup-*.log' -mtime +30 -delete 2>/dev/null || true + +exit $EXIT_CODE diff --git a/proxmox/gpu/gpu-check.sh b/proxmox/gpu/gpu-check.sh new file mode 100755 index 0000000..16efb6e --- /dev/null +++ b/proxmox/gpu/gpu-check.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# gpu-check.sh — Diagnose GPU passthrough readiness on a Proxmox host +# Checks: IOMMU enabled, IOMMU groups, GPU detection, vfio-pci binding, NVIDIA VRAM. +# +# Usage: sudo bash gpu-check.sh + +set -euo pipefail + +PASS="\033[0;32m[PASS]\033[0m" +FAIL="\033[0;31m[FAIL]\033[0m" +WARN="\033[0;33m[WARN]\033[0m" +INFO="\033[0;34m[INFO]\033[0m" + +echo "============================================" +echo " GPU Passthrough Readiness Check" +echo "============================================" +echo "" + +# --- 1. Check IOMMU enabled in kernel --- +echo -e "$INFO Checking IOMMU kernel support..." +if dmesg | grep -qi 'IOMMU enabled\|DMAR.*IOMMU\|AMD-Vi.*enabled'; then + echo -e "$PASS IOMMU is enabled in the kernel." +else + echo -e "$FAIL IOMMU does not appear to be enabled." + echo " Add 'intel_iommu=on iommu=pt' (Intel) or 'amd_iommu=on iommu=pt' (AMD)" + echo " to GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub, then:" + echo " update-grub && reboot" +fi +echo "" + +# --- 2. Check for VT-d / AMD-Vi in dmesg --- +echo -e "$INFO Checking IOMMU dmesg output..." +dmesg | grep -iE 'DMAR|IOMMU|AMD-Vi' | head -10 +echo "" + +# --- 3. List IOMMU groups --- +echo -e "$INFO IOMMU Groups:" +IOMMU_BASE="/sys/kernel/iommu_groups" +if [[ -d "$IOMMU_BASE" ]] && [[ $(ls "$IOMMU_BASE" 2>/dev/null | wc -l) -gt 0 ]]; then + for g in "$IOMMU_BASE"/*/; do + GROUP_ID=$(basename "$g") + DEVICES="" + for d in "$g"devices/*/; do + if [[ -e "$d" ]]; then + DEV_ID=$(basename "$d") + DEV_DESC=$(lspci -nns "$DEV_ID" 2>/dev/null || echo "unknown") + DEVICES="$DEVICES\n $DEV_DESC" + fi + done + if echo -e "$DEVICES" | grep -qi 'vga\|3d\|display\|nvidia\|amd.*radeon'; then + echo -e " Group $GROUP_ID (GPU detected):$DEVICES" + fi + done + echo "" + echo -e "$PASS IOMMU groups are formed." +else + echo -e "$FAIL No IOMMU groups found. IOMMU may not be enabled." +fi +echo "" + +# --- 4. Detect GPUs via lspci --- +echo -e "$INFO Detected GPUs (lspci):" +GPU_LIST=$(lspci -nn | grep -iE 'vga|3d|display' || true) +if [[ -n "$GPU_LIST" ]]; then + echo "$GPU_LIST" + echo -e "$PASS GPU(s) detected." +else + echo -e "$WARN No discrete GPU detected via lspci." +fi +echo "" + +# --- 5. Check vfio-pci driver binding --- +echo -e "$INFO Checking vfio-pci driver bindings..." +VFIO_BOUND=false +for dev_path in /sys/bus/pci/drivers/vfio-pci/*/; do + if [[ -e "$dev_path" ]]; then + DEV=$(basename "$dev_path") + DESC=$(lspci -nns "$DEV" 2>/dev/null || echo "unknown") + echo " $DESC" + VFIO_BOUND=true + fi +done +if $VFIO_BOUND; then + echo -e "$PASS GPU(s) bound to vfio-pci driver." +else + echo -e "$WARN No devices bound to vfio-pci." + echo " To bind a GPU, add its PCI IDs to /etc/modprobe.d/vfio.conf:" + echo " options vfio-pci ids=10de:xxxx,10de:yyyy" + echo " Then: update-initramfs -u && reboot" +fi +echo "" + +# --- 6. Check NVIDIA VRAM (if nvidia-smi available) --- +echo -e "$INFO Checking NVIDIA GPU VRAM (nvidia-smi)..." +if command -v nvidia-smi &>/dev/null; then + nvidia-smi --query-gpu=index,name,memory.total,memory.free,driver_version \ + --format=csv,noheader,nounits 2>/dev/null | while IFS=',' read -r idx name mem_total mem_free driver; do + echo " GPU $idx: $name — VRAM: ${mem_total}MiB total, ${mem_free}MiB free — Driver: $driver" + done + echo -e "$PASS NVIDIA driver loaded and reporting." +else + echo -e "$INFO nvidia-smi not available (expected if GPU is passed through to VM)." +fi +echo "" + +# --- 7. Kernel modules check --- +echo -e "$INFO Loaded GPU-related kernel modules:" +lsmod | grep -iE 'vfio|nvidia|nouveau|radeon|amdgpu|i915' || echo " (none found)" +echo "" + +echo "============================================" +echo " GPU Check Complete" +echo "============================================" diff --git a/proxmox/network/harden-ssh.sh b/proxmox/network/harden-ssh.sh new file mode 100755 index 0000000..48d8d83 --- /dev/null +++ b/proxmox/network/harden-ssh.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# harden-ssh.sh — Harden the OpenSSH server on a Proxmox host +# - Disables root password login (key-only for root) +# - Disables password authentication globally +# - Optionally changes the SSH port +# +# Usage: sudo bash harden-ssh.sh [NEW_PORT] +# Example: sudo bash harden-ssh.sh 2222 + +set -euo pipefail + +SSHD_CONFIG="/etc/ssh/sshd_config" +SSHD_HARDENING="/etc/ssh/sshd_config.d/90-hardening.conf" +NEW_PORT="${1:-}" + +echo "==> Hardening SSH configuration" + +# Create a drop-in config for hardening (avoids modifying the main config directly) +mkdir -p /etc/ssh/sshd_config.d + +cat > "$SSHD_HARDENING" <> "$SSHD_HARDENING" + echo " SSH port changed to $NEW_PORT" +else + echo " SSH port left at default (22)" +fi + +echo " Hardening config written to $SSHD_HARDENING" + +# Ensure the main sshd_config includes drop-in configs +if ! grep -q '^Include /etc/ssh/sshd_config.d/\*.conf' "$SSHD_CONFIG" 2>/dev/null; then + echo 'Include /etc/ssh/sshd_config.d/*.conf' >> "$SSHD_CONFIG" + echo " Added Include directive to $SSHD_CONFIG" +fi + +# Validate configuration before restarting +if sshd -t; then + systemctl restart sshd + echo "==> SSH restarted with hardened configuration." +else + echo "==> ERROR: sshd config test failed. Reverting." + rm -f "$SSHD_HARDENING" + exit 1 +fi + +echo "" +echo "IMPORTANT: Ensure you have SSH key access before disconnecting!" +echo " Test with: ssh -p ${NEW_PORT:-22} root@" diff --git a/proxmox/network/setup-fail2ban.sh b/proxmox/network/setup-fail2ban.sh new file mode 100755 index 0000000..9fe13aa --- /dev/null +++ b/proxmox/network/setup-fail2ban.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# setup-fail2ban.sh — Install and configure Fail2Ban for Proxmox +# Protects SSH and the Proxmox Web GUI against brute-force attacks. +# +# Usage: sudo bash setup-fail2ban.sh [MAX_RETRY] [BAN_TIME] +# Example: sudo bash setup-fail2ban.sh 3 3600 + +set -euo pipefail + +MAX_RETRY="${1:-3}" +BAN_TIME="${2:-3600}" + +echo "==> Installing and configuring Fail2Ban" +echo " Max retries: $MAX_RETRY" +echo " Ban time: ${BAN_TIME}s" + +# Install fail2ban if not present +if ! command -v fail2ban-server &>/dev/null; then + apt-get update -qq + apt-get install -y -qq fail2ban +fi + +# --- Proxmox Web GUI filter --- +cat > /etc/fail2ban/filter.d/proxmox-webgui.conf <<'EOF' +[Definition] +failregex = pvedaemon\[.*authentication (verification )?failure; rhost= user=\S+ msg=.* +ignoreregex = +journalmatch = _SYSTEMD_UNIT=pvedaemon.service +EOF + +# --- Jail configuration --- +cat > /etc/fail2ban/jail.d/proxmox.conf < Fail2Ban status:" +fail2ban-client status +echo "" +echo "==> Fail2Ban configuration complete." +echo " Check jail status: fail2ban-client status sshd" +echo " Check jail status: fail2ban-client status proxmox-webgui" diff --git a/proxmox/network/setup-firewall.sh b/proxmox/network/setup-firewall.sh new file mode 100755 index 0000000..fc5922b --- /dev/null +++ b/proxmox/network/setup-firewall.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# setup-firewall.sh — Enable and configure the Proxmox VE Firewall +# Applies host-level rules via /etc/pve/local/host.fw and cluster-level +# defaults via /etc/pve/firewall/cluster.fw. +# +# Usage: sudo bash setup-firewall.sh + +set -euo pipefail + +CLUSTER_FW="/etc/pve/firewall/cluster.fw" +HOST_FW="/etc/pve/local/host.fw" + +echo "==> Configuring Proxmox VE Firewall" + +# --- Cluster-level firewall config --- +mkdir -p "$(dirname "$CLUSTER_FW")" +cat > "$CLUSTER_FW" <<'EOF' +[OPTIONS] +enable: 1 +policy_in: DROP +policy_out: ACCEPT +log_level_in: nolog +log_level_out: nolog + +[RULES] +# Allow ICMP (ping) +IN ACCEPT -p icmp +# Allow established/related traffic +IN ACCEPT -m conntrack --ctstate RELATED,ESTABLISHED +# Allow Proxmox Web GUI (port 8006) +IN ACCEPT -p tcp -dport 8006 +# Allow SSH +IN ACCEPT -p tcp -dport 22 +# Allow SPICE console +IN ACCEPT -p tcp -dport 3128 +# Allow VNC console range +IN ACCEPT -p tcp -dport 5900:5999 +# Allow Proxmox cluster communication (corosync) +IN ACCEPT -p udp -dport 5405:5412 +# Allow live migration +IN ACCEPT -p tcp -dport 60000:60050 +EOF + +echo " Cluster firewall written to $CLUSTER_FW" + +# --- Host-level firewall config --- +mkdir -p "$(dirname "$HOST_FW")" +cat > "$HOST_FW" <<'EOF' +[OPTIONS] +enable: 1 + +[RULES] +# Allow SSH from management network only (adjust subnet as needed) +IN ACCEPT -source 10.0.0.0/8 -p tcp -dport 22 +# Allow Web GUI from management network +IN ACCEPT -source 10.0.0.0/8 -p tcp -dport 8006 +EOF + +echo " Host firewall written to $HOST_FW" +echo "==> Firewall configuration complete." +echo " Verify in the PVE web UI under Datacenter > Firewall." diff --git a/proxmox/network/setup-nat.sh b/proxmox/network/setup-nat.sh new file mode 100755 index 0000000..27b35a1 --- /dev/null +++ b/proxmox/network/setup-nat.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# setup-nat.sh — Configure NAT (MASQUERADE) on a Proxmox host +# This enables VMs on an internal bridge (e.g. vmbr1) to reach the internet +# through the host's primary interface. +# +# Usage: sudo bash setup-nat.sh [WAN_IFACE] [INTERNAL_BRIDGE] [INTERNAL_SUBNET] + +set -euo pipefail + +WAN_IFACE="${1:-vmbr0}" +INTERNAL_BRIDGE="${2:-vmbr1}" +INTERNAL_SUBNET="${3:-10.10.10.0/24}" + +echo "==> Configuring NAT" +echo " WAN interface: $WAN_IFACE" +echo " Internal bridge: $INTERNAL_BRIDGE" +echo " Internal subnet: $INTERNAL_SUBNET" + +# Enable IP forwarding persistently +if ! grep -q '^net.ipv4.ip_forward=1' /etc/sysctl.conf; then + echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf +fi +sysctl -w net.ipv4.ip_forward=1 + +# Flush existing NAT rules for idempotency +iptables -t nat -D POSTROUTING -s "$INTERNAL_SUBNET" -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || true + +# Add MASQUERADE rule +iptables -t nat -A POSTROUTING -s "$INTERNAL_SUBNET" -o "$WAN_IFACE" -j MASQUERADE + +# Allow forwarding between internal bridge and WAN +iptables -D FORWARD -i "$INTERNAL_BRIDGE" -o "$WAN_IFACE" -j ACCEPT 2>/dev/null || true +iptables -D FORWARD -i "$WAN_IFACE" -o "$INTERNAL_BRIDGE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true +iptables -A FORWARD -i "$INTERNAL_BRIDGE" -o "$WAN_IFACE" -j ACCEPT +iptables -A FORWARD -i "$WAN_IFACE" -o "$INTERNAL_BRIDGE" -m state --state RELATED,ESTABLISHED -j ACCEPT + +# Persist rules (Debian/Proxmox uses iptables-persistent or /etc/network/interfaces post-up) +if command -v netfilter-persistent &>/dev/null; then + netfilter-persistent save + echo "==> Rules saved via netfilter-persistent" +else + echo "==> WARNING: netfilter-persistent not found." + echo " Install with: apt install iptables-persistent" + echo " Or add post-up rules to /etc/network/interfaces manually:" + echo " post-up iptables -t nat -A POSTROUTING -s $INTERNAL_SUBNET -o $WAN_IFACE -j MASQUERADE" +fi + +echo "==> NAT configuration complete." diff --git a/proxmox/vm-templates/create-cloud-init-template.sh b/proxmox/vm-templates/create-cloud-init-template.sh new file mode 100755 index 0000000..9304af4 --- /dev/null +++ b/proxmox/vm-templates/create-cloud-init-template.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# create-cloud-init-template.sh — Download a cloud image and create a +# Cloud-Init-enabled VM template on Proxmox VE. +# +# Usage: sudo bash create-cloud-init-template.sh [VMID] [TEMPLATE_NAME] [IMAGE_URL] +# +# Defaults to Ubuntu 22.04 (Jammy) cloud image. + +set -euo pipefail + +VMID="${1:-9000}" +TEMPLATE_NAME="${2:-ubuntu-2204-cloud}" +IMAGE_URL="${3:-https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img}" +STORAGE="local-lvm" +BRIDGE="vmbr0" +IMAGE_FILE="/tmp/cloud-image-$(basename "$IMAGE_URL")" + +echo "==> Creating Cloud-Init VM Template" +echo " VMID: $VMID" +echo " Name: $TEMPLATE_NAME" +echo " Image: $IMAGE_URL" +echo " Storage: $STORAGE" + +# Download cloud image +if [[ ! -f "$IMAGE_FILE" ]]; then + echo " Downloading cloud image..." + wget -q --show-progress -O "$IMAGE_FILE" "$IMAGE_URL" +else + echo " Cloud image already downloaded: $IMAGE_FILE" +fi + +# Destroy existing VM with same ID (if any) +if qm status "$VMID" &>/dev/null; then + echo " Destroying existing VM $VMID..." + qm destroy "$VMID" --purge +fi + +# Create VM +echo " Creating VM..." +qm create "$VMID" \ + --name "$TEMPLATE_NAME" \ + --ostype l26 \ + --memory 2048 \ + --cores 2 \ + --cpu host \ + --net0 "virtio,bridge=$BRIDGE" \ + --scsihw virtio-scsi-single \ + --agent enabled=1 + +# Import disk +echo " Importing disk image..." +qm set "$VMID" --scsi0 "$STORAGE:0,import-from=$IMAGE_FILE" + +# Add Cloud-Init drive +echo " Adding Cloud-Init drive..." +qm set "$VMID" --ide2 "$STORAGE:cloudinit" + +# Set boot order +qm set "$VMID" --boot order=scsi0 + +# Set serial console for cloud images +qm set "$VMID" --serial0 socket --vga serial0 + +# Set default Cloud-Init settings +qm set "$VMID" \ + --ciuser admin \ + --cipassword "changeme" \ + --ipconfig0 ip=dhcp + +echo " Resizing disk to 32G..." +qm disk resize "$VMID" scsi0 32G + +# Convert to template +echo " Converting to template..." +qm template "$VMID" + +echo "" +echo "==> Template '$TEMPLATE_NAME' (VMID $VMID) created successfully." +echo "" +echo "Clone with:" +echo " qm clone $VMID --name --full" +echo "" +echo "Customize Cloud-Init before first boot:" +echo " qm set --ciuser myuser --sshkeys ~/.ssh/id_rsa.pub --ipconfig0 ip=10.10.10.x/24,gw=10.10.10.1" diff --git a/proxmox/vm-templates/setup-storage.sh b/proxmox/vm-templates/setup-storage.sh new file mode 100755 index 0000000..550be51 --- /dev/null +++ b/proxmox/vm-templates/setup-storage.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# setup-storage.sh — Configure storage backends on Proxmox VE +# Creates commonly needed storage pools: local-lvm for VMs, and optionally +# a ZFS pool or NFS mount for ISOs and backups. +# +# Usage: sudo bash setup-storage.sh + +set -euo pipefail + +echo "==> Configuring Proxmox storage backends" + +# --- Ensure local-lvm exists and is properly configured --- +if pvesm status | grep -q 'local-lvm'; then + echo " local-lvm already exists" +else + echo " Creating local-lvm storage (thin LVM on pve/data)" + pvesm add lvmthin local-lvm \ + --vgname pve \ + --thinpool data \ + --content rootdir,images + echo " local-lvm created" +fi + +# --- ISO storage on local --- +# Ensure the local storage accepts ISO images and container templates +pvesm set local --content iso,vztmpl,backup,snippets +echo " local storage configured for iso,vztmpl,backup,snippets" + +# --- Create ISO directory if it doesn't exist --- +ISO_DIR="/var/lib/vz/template/iso" +mkdir -p "$ISO_DIR" +echo " ISO directory ready: $ISO_DIR" + +# --- Optional: NFS mount for shared storage --- +# Uncomment and adjust the following to add NFS storage: +# NFS_SERVER="192.168.1.100" +# NFS_EXPORT="/export/pve-storage" +# NFS_MOUNT="nfs-shared" +# +# if ! pvesm status | grep -q "$NFS_MOUNT"; then +# pvesm add nfs "$NFS_MOUNT" \ +# --server "$NFS_SERVER" \ +# --export "$NFS_EXPORT" \ +# --content images,iso,backup,vztmpl \ +# --options vers=4 +# echo " NFS storage '$NFS_MOUNT' added" +# fi + +# --- Optional: ZFS pool --- +# Uncomment to add a ZFS-backed storage: +# ZFS_POOL="rpool/data" +# ZFS_NAME="local-zfs" +# +# if ! pvesm status | grep -q "$ZFS_NAME"; then +# pvesm add zfspool "$ZFS_NAME" \ +# --pool "$ZFS_POOL" \ +# --content rootdir,images \ +# --sparse 1 +# echo " ZFS storage '$ZFS_NAME' added" +# fi + +echo "" +echo "==> Current storage status:" +pvesm status +echo "" +echo "==> Storage configuration complete." +echo " Upload ISOs to: $ISO_DIR" +echo " Or via Web UI: Datacenter > Storage > local > ISO Images" From 5e05bc6faacdad83c8a48b89ab38682439df0bcf Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Feb 2026 13:47:28 +0000 Subject: [PATCH 2/3] Add VM creation, GPU passthrough, remote access, and file sharing scripts Concrete topology: Host1 (192.168.16.2, RX 6800 XT), Host2 (192.168.16.3, GTX 1080), ThinkPad (192.168.16.10), VM-Netz (192.168.20.0/24 on vmbr1). - vm-windows: Windows VM with RX 6800 XT passthrough + vfio-pci setup - vm-linux-desktop: Linux Desktop VM with SPICE/QXL display - remote-access: RDP + SPICE connect scripts, ThinkPad route setup - fileserver: Samba server setup + ThinkPad mount helper - network: interface examples for both hosts, updated NAT defaults - docs: full topology diagram, 6-phase execution guide, architecture table https://claude.ai/code/session_01SWoB1YbMmuPwSv5964mbE8 --- docs/proxmox-setup.md | 194 +++++++++++++----- proxmox/fileserver/mount-share-thinkpad.sh | 59 ++++++ proxmox/fileserver/setup-samba.sh | 134 ++++++++++++ proxmox/network/interfaces-host1.example | 37 ++++ proxmox/network/interfaces-host2.example | 37 ++++ proxmox/network/setup-nat.sh | 12 +- proxmox/remote-access/connect-rdp.sh | 56 +++++ proxmox/remote-access/connect-spice.sh | 71 +++++++ .../remote-access/setup-thinkpad-routes.sh | 47 +++++ .../create-linux-desktop-vm.sh | 108 ++++++++++ proxmox/vm-windows/create-windows-vm.sh | 134 ++++++++++++ proxmox/vm-windows/setup-gpu-passthrough.sh | 119 +++++++++++ 12 files changed, 952 insertions(+), 56 deletions(-) create mode 100644 proxmox/fileserver/mount-share-thinkpad.sh create mode 100644 proxmox/fileserver/setup-samba.sh create mode 100644 proxmox/network/interfaces-host1.example create mode 100644 proxmox/network/interfaces-host2.example create mode 100644 proxmox/remote-access/connect-rdp.sh create mode 100644 proxmox/remote-access/connect-spice.sh create mode 100644 proxmox/remote-access/setup-thinkpad-routes.sh create mode 100644 proxmox/vm-linux-desktop/create-linux-desktop-vm.sh create mode 100644 proxmox/vm-windows/create-windows-vm.sh create mode 100644 proxmox/vm-windows/setup-gpu-passthrough.sh diff --git a/docs/proxmox-setup.md b/docs/proxmox-setup.md index 1cc5863..1ae2451 100644 --- a/docs/proxmox-setup.md +++ b/docs/proxmox-setup.md @@ -1,94 +1,182 @@ # Proxmox VE Setup Guide -This document describes the scripts in the `proxmox/` directory and how to use them to set up and harden a Proxmox VE host. +Complete infrastructure-as-code for a 2-host Proxmox cluster with GPU passthrough, VM management, remote access, and file sharing. + +## Network Topology + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ LAN: 192.168.16.0/24 │ +│ │ +│ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │ +│ │ Host 1 │ │ Host 2 │ │ ThinkPad │ │ +│ │ 192.168.16.2 │ │ 192.168.16.3 │ │ 192.168.16.10 │ │ +│ │ RX 6800 XT 16GB │ │ GTX 1080 8GB │ │ (Client) │ │ +│ │ │ │ │ │ │ │ +│ │ vmbr0 ─ LAN │ │ vmbr0 ─ LAN │ │ RDP ──────┐ │ │ +│ │ vmbr1 ─ VM-Netz │ │ vmbr1 ─ VM-Netz │ │ SPICE ──┐ │ │ │ +│ └────────┬─────────┘ └────────┬─────────┘ └──────────┼─┼──┘ │ +│ │ │ │ │ │ +└───────────┼─────────────────────┼───────────────────────┼─┼─────┘ + │ │ │ │ +┌───────────┼─────────────────────┼───────────────────────┼─┼─────┐ +│ VM-Netz: 192.168.20.0/24 │ │ │ │ +│ │ │ │ │ │ +│ ┌────────┴─────────┐ ┌───────┴──────────┐ │ │ │ +│ │ Windows VM │ │ Linux Desktop VM │ │ │ │ +│ │ 192.168.20.10 │ │ 192.168.20.20 │ │ │ │ +│ │ GPU: RX 6800 XT │ │ Display: SPICE │◄───────────┘ │ │ +│ │ Access: RDP ◄────┼──┼──────────────────┼──────────────┘ │ +│ └──────────────────┘ │ │ │ +│ │ Samba Server: │ │ +│ │ \\projects │ │ +│ │ \\documents │ │ +│ └──────────────────┘ │ +└────────────────────────────────────────────────────────────────┘ +``` ## Directory Structure ``` proxmox/ ├── network/ -│ ├── setup-nat.sh # NAT/MASQUERADE for internal VMs -│ ├── setup-firewall.sh # PVE firewall rules (cluster + host) -│ ├── harden-ssh.sh # SSH hardening (key-only, disable root pw) -│ └── setup-fail2ban.sh # Fail2Ban for SSH + PVE Web GUI +│ ├── setup-nat.sh # NAT/MASQUERADE (192.168.20.0/24 → WAN) +│ ├── setup-firewall.sh # PVE firewall rules (cluster + host) +│ ├── harden-ssh.sh # SSH hardening (key-only, no root pw) +│ ├── setup-fail2ban.sh # Fail2Ban for SSH + PVE Web GUI +│ ├── interfaces-host1.example # /etc/network/interfaces for Host 1 +│ └── interfaces-host2.example # /etc/network/interfaces for Host 2 +├── vm-windows/ +│ ├── setup-gpu-passthrough.sh # IOMMU + vfio-pci for RX 6800 XT +│ └── create-windows-vm.sh # Windows VM with GPU passthrough +├── vm-linux-desktop/ +│ └── create-linux-desktop-vm.sh # Linux Desktop VM (SPICE/QXL) ├── vm-templates/ -│ ├── setup-storage.sh # Storage backends (local-lvm, NFS, ZFS) -│ └── create-cloud-init-template.sh # Cloud-Init VM template +│ ├── setup-storage.sh # Storage backends (local-lvm, NFS, ZFS) +│ └── create-cloud-init-template.sh # Cloud-Init base template +├── remote-access/ +│ ├── connect-rdp.sh # RDP to Windows VM (from ThinkPad) +│ ├── connect-spice.sh # SPICE to Linux VM (from ThinkPad) +│ └── setup-thinkpad-routes.sh # Route 192.168.20.0/24 on ThinkPad +├── fileserver/ +│ ├── setup-samba.sh # Samba server in Linux VM +│ └── mount-share-thinkpad.sh # Mount shares on ThinkPad ├── backup/ -│ ├── vzdump-backup.sh # vzdump backup for all VMs/CTs -│ └── install-backup-cronjob.sh # Install cron job for automated backups +│ ├── vzdump-backup.sh # vzdump backup for all VMs/CTs +│ └── install-backup-cronjob.sh # Cron job for automated backups └── gpu/ - └── gpu-check.sh # GPU passthrough readiness check + └── gpu-check.sh # GPU passthrough readiness check ``` ## Prerequisites -- Proxmox VE 7.x or 8.x installed -- Root access to the Proxmox host -- SSH key pair for key-based authentication +- Proxmox VE 7.x or 8.x on both hosts +- Root access via SSH key +- Windows ISO + VirtIO drivers ISO uploaded to `/var/lib/vz/template/iso/` +- IOMMU enabled in BIOS (VT-d / AMD-Vi) -## Recommended Execution Order - -### 1. Network & Security +## Phase 1 — Network & Security (Both Hosts) ```bash -# a) Set up NAT for internal VM network -sudo bash proxmox/network/setup-nat.sh vmbr0 vmbr1 10.10.10.0/24 +# 1a) Set up NAT for VM network +sudo bash proxmox/network/setup-nat.sh vmbr0 vmbr1 192.168.20.0/24 -# b) Configure PVE firewall rules +# 1b) Configure PVE firewall sudo bash proxmox/network/setup-firewall.sh -# c) Harden SSH (optionally change port) -sudo bash proxmox/network/harden-ssh.sh 2222 +# 1c) Harden SSH +sudo bash proxmox/network/harden-ssh.sh # port 22 (default) +sudo bash proxmox/network/harden-ssh.sh 2222 # or custom port -# d) Install Fail2Ban (block after 3 failures, ban for 1 hour) +# 1d) Install Fail2Ban (block after 3 failures, 1h ban) sudo bash proxmox/network/setup-fail2ban.sh 3 3600 ``` -### 2. Storage & VM Templates +## Phase 2 — GPU Passthrough (Host 1 only) + +```bash +# 2a) Prepare RX 6800 XT passthrough (REQUIRES REBOOT) +sudo bash proxmox/vm-windows/setup-gpu-passthrough.sh +# → reboot Host 1 + +# 2b) Verify GPU passthrough +sudo bash proxmox/gpu/gpu-check.sh +``` + +## Phase 3 — VM Creation + +### Windows VM with GPU (Host 1) ```bash -# a) Configure storage backends +# 3a) Configure storage sudo bash proxmox/vm-templates/setup-storage.sh -# b) Create a Cloud-Init Ubuntu template (VMID 9000) -sudo bash proxmox/vm-templates/create-cloud-init-template.sh 9000 ubuntu-2204-cloud +# 3b) Create Windows VM (VMID 100, RX 6800 XT passthrough) +sudo bash proxmox/vm-windows/create-windows-vm.sh 100 win11-workstation -# Clone a VM from the template: -qm clone 9000 100 --name my-vm --full -qm set 100 --ciuser myuser --sshkeys ~/.ssh/id_rsa.pub --ipconfig0 ip=10.10.10.100/24,gw=10.10.10.1 -qm start 100 +# Inside Windows after install: +# - Install VirtIO guest tools +# - Install AMD GPU drivers +# - Enable Remote Desktop (Settings → System → Remote Desktop) +# - Set IP: 192.168.20.10/24, GW: 192.168.20.1, DNS: 192.168.16.1 ``` -### 3. Backups +### Linux Desktop VM (Host 1 or Host 2) ```bash -# a) Install daily backup cronjob (runs at 02:00) -sudo bash proxmox/backup/install-backup-cronjob.sh daily +# 3c) Create Linux Desktop VM (VMID 200, SPICE) +sudo bash proxmox/vm-linux-desktop/create-linux-desktop-vm.sh 200 linux-desktop -# b) Or run a manual backup -sudo bash proxmox/backup/vzdump-backup.sh local snapshot 3 +# After first boot (via SSH or console): +ssh admin@192.168.20.20 +sudo apt update && sudo apt install -y ubuntu-desktop spice-vdagent +sudo reboot ``` -### 4. GPU Passthrough +## Phase 4 — Remote Access (ThinkPad) ```bash -# Check GPU passthrough readiness -sudo bash proxmox/gpu/gpu-check.sh +# 4a) Set up routing on ThinkPad to reach VMs +sudo bash proxmox/remote-access/setup-thinkpad-routes.sh + +# 4b) Connect to Windows VM via RDP +bash proxmox/remote-access/connect-rdp.sh 192.168.20.10 + +# 4c) Connect to Linux Desktop VM via SPICE +bash proxmox/remote-access/connect-spice.sh 192.168.16.2 200 +``` + +## Phase 5 — File Sharing + +```bash +# 5a) Set up Samba in the Linux VM (run inside VM) +sudo bash proxmox/fileserver/setup-samba.sh smbuser /srv/projects + +# 5b) Mount shares on ThinkPad +sudo bash proxmox/fileserver/mount-share-thinkpad.sh 192.168.20.20 + +# Access from Windows VM (Explorer): +# \\192.168.20.20\projects +# \\192.168.20.20\documents +``` + +## Phase 6 — Backups + +```bash +# 6a) Install daily backup cronjob (02:00) +sudo bash proxmox/backup/install-backup-cronjob.sh daily + +# 6b) Manual backup +sudo bash proxmox/backup/vzdump-backup.sh local snapshot 3 ``` -The GPU check script verifies: -- IOMMU is enabled in the kernel -- IOMMU groups are correctly formed -- GPUs are detected via `lspci` -- vfio-pci driver bindings -- NVIDIA VRAM via `nvidia-smi` (if available) - -## Customization - -- **NAT subnet**: Change the third argument to `setup-nat.sh` -- **SSH port**: Pass desired port to `harden-ssh.sh` -- **Fail2Ban thresholds**: Adjust `MAX_RETRY` and `BAN_TIME` in `setup-fail2ban.sh` -- **Backup schedule**: Use `daily`, `weekly`, or `custom` with `install-backup-cronjob.sh` -- **VM template**: Change the image URL in `create-cloud-init-template.sh` for Debian, Rocky, etc. -- **NFS/ZFS storage**: Uncomment the relevant sections in `setup-storage.sh` +## Production Architecture Decision + +| Component | Host 1 (192.168.16.2) | Host 2 (192.168.16.3) | +|-----------|----------------------|----------------------| +| GPU | RX 6800 XT (16GB) | GTX 1080 (8GB) | +| Role | Windows Workstation + large AI models | Docker + Ollama + 24/7 services | +| VMs | win11-workstation (VMID 100) | Docker host, Linux services | +| Access | RDP from ThinkPad | SSH + Portainer | + +**Recommended separation:** Host 1 for interactive work (Windows + GPU), Host 2 for always-on services (Docker, Ollama, APIs). This keeps workstation reboots independent of service availability. diff --git a/proxmox/fileserver/mount-share-thinkpad.sh b/proxmox/fileserver/mount-share-thinkpad.sh new file mode 100644 index 0000000..657b7c8 --- /dev/null +++ b/proxmox/fileserver/mount-share-thinkpad.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# mount-share-thinkpad.sh — Mount Samba shares on the ThinkPad +# +# Run on ThinkPad (192.168.16.10) to mount shared folders from the +# Linux file server VM (192.168.20.20). +# +# Usage: sudo bash mount-share-thinkpad.sh [SERVER_IP] [SHARE_USER] + +set -euo pipefail + +SERVER_IP="${1:-192.168.20.20}" +SHARE_USER="${2:-smbuser}" + +echo "==> Mounting Samba shares from $SERVER_IP" + +# Install cifs-utils if needed +if ! command -v mount.cifs &>/dev/null; then + apt-get install -y -qq cifs-utils +fi + +# Create mount points +mkdir -p /mnt/{projects,documents,backups} + +# Create credentials file +CRED_FILE="/root/.smbcredentials" +if [[ ! -f "$CRED_FILE" ]]; then + read -rsp "Enter Samba password for $SHARE_USER: " SMB_PASS + echo "" + cat > "$CRED_FILE" < Shares mounted:" +df -h /mnt/projects /mnt/documents /mnt/backups 2>/dev/null + +echo "" +echo "==> To make persistent, add to /etc/fstab:" +for SHARE in projects documents backups; do + echo " //$SERVER_IP/$SHARE /mnt/$SHARE cifs credentials=$CRED_FILE,uid=$CURRENT_UID,gid=$CURRENT_GID,iocharset=utf8,_netdev 0 0" +done diff --git a/proxmox/fileserver/setup-samba.sh b/proxmox/fileserver/setup-samba.sh new file mode 100644 index 0000000..1744ffd --- /dev/null +++ b/proxmox/fileserver/setup-samba.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# setup-samba.sh — Set up a Samba file server in a Linux VM +# +# Creates shared directories accessible from Windows VMs and the ThinkPad. +# Run inside the Linux VM (e.g., 192.168.20.20). +# +# Access from Windows: \\192.168.20.20\projects +# Access from Linux: mount -t cifs //192.168.20.20/projects /mnt/projects -o user=smbuser +# +# Usage: sudo bash setup-samba.sh [SHARE_USER] [SHARE_DIR] + +set -euo pipefail + +SHARE_USER="${1:-smbuser}" +SHARE_DIR="${2:-/srv/projects}" + +echo "============================================" +echo " Samba File Server Setup" +echo "============================================" +echo " Share user: $SHARE_USER" +echo " Share dir: $SHARE_DIR" +echo "============================================" +echo "" + +# Install Samba +echo "==> Installing Samba..." +apt-get update -qq +apt-get install -y -qq samba samba-common + +# Create share directory +echo "==> Creating share directory: $SHARE_DIR" +mkdir -p "$SHARE_DIR"/{documents,projects,iso-images,backups} + +# Create the share user (system user + Samba user) +if ! id "$SHARE_USER" &>/dev/null; then + useradd -M -s /usr/sbin/nologin "$SHARE_USER" + echo " System user '$SHARE_USER' created" +fi + +echo "==> Set Samba password for '$SHARE_USER':" +smbpasswd -a "$SHARE_USER" +smbpasswd -e "$SHARE_USER" + +# Set ownership +chown -R "$SHARE_USER":"$SHARE_USER" "$SHARE_DIR" +chmod -R 2775 "$SHARE_DIR" + +# Backup existing config +cp /etc/samba/smb.conf /etc/samba/smb.conf.bak.$(date +%s) + +# Write Samba config +cat > /etc/samba/smb.conf < Testing Samba configuration..." +testparm -s /etc/samba/smb.conf + +# Enable and restart +systemctl enable smbd nmbd +systemctl restart smbd nmbd + +# Open firewall if ufw is active +if command -v ufw &>/dev/null && ufw status | grep -q 'active'; then + ufw allow samba + echo " UFW: Samba ports allowed" +fi + +echo "" +echo "============================================" +echo " Samba File Server Ready" +echo "============================================" +echo "" +echo " Shares:" +echo " \\\\$(hostname -I | awk '{print $1}')\\projects" +echo " \\\\$(hostname -I | awk '{print $1}')\\documents" +echo " \\\\$(hostname -I | awk '{print $1}')\\backups" +echo "" +echo " Connect from Windows (Explorer address bar):" +echo " \\\\192.168.20.20\\projects" +echo "" +echo " Connect from Linux (ThinkPad):" +echo " sudo mount -t cifs //192.168.20.20/projects /mnt/projects -o user=$SHARE_USER" +echo "" +echo " Or add to /etc/fstab:" +echo " //192.168.20.20/projects /mnt/projects cifs credentials=/root/.smbcredentials,uid=1000 0 0" +echo "" diff --git a/proxmox/network/interfaces-host1.example b/proxmox/network/interfaces-host1.example new file mode 100644 index 0000000..768c450 --- /dev/null +++ b/proxmox/network/interfaces-host1.example @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# interfaces-host1.example — /etc/network/interfaces for Host 1 (192.168.16.2) +# +# Network topology: +# vmbr0: 192.168.16.2/24 — Management / LAN bridge +# vmbr1: 192.168.20.1/24 — Internal VM network (NAT) +# +# Copy to /etc/network/interfaces and adjust as needed. + +cat <<'INTERFACES' +auto lo +iface lo inet loopback + +# Physical NIC (adjust eno1/enp6s0 to your actual interface) +auto eno1 +iface eno1 inet manual + +# vmbr0 — Management bridge (LAN) +auto vmbr0 +iface vmbr0 inet static + address 192.168.16.2/24 + gateway 192.168.16.1 + bridge-ports eno1 + bridge-stp off + bridge-fd 0 + +# vmbr1 — Internal VM network (no physical port, NAT) +auto vmbr1 +iface vmbr1 inet static + address 192.168.20.1/24 + bridge-ports none + bridge-stp off + bridge-fd 0 + post-up echo 1 > /proc/sys/net/ipv4/ip_forward + post-up iptables -t nat -A POSTROUTING -s 192.168.20.0/24 -o vmbr0 -j MASQUERADE + post-down iptables -t nat -D POSTROUTING -s 192.168.20.0/24 -o vmbr0 -j MASQUERADE +INTERFACES diff --git a/proxmox/network/interfaces-host2.example b/proxmox/network/interfaces-host2.example new file mode 100644 index 0000000..eaf04a2 --- /dev/null +++ b/proxmox/network/interfaces-host2.example @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# interfaces-host2.example — /etc/network/interfaces for Host 2 (192.168.16.3) +# +# Network topology: +# vmbr0: 192.168.16.3/24 — Management / LAN bridge +# vmbr1: 192.168.20.1/24 — Internal VM network (NAT) +# +# Copy to /etc/network/interfaces and adjust as needed. + +cat <<'INTERFACES' +auto lo +iface lo inet loopback + +# Physical NIC (adjust eno1/enp6s0 to your actual interface) +auto eno1 +iface eno1 inet manual + +# vmbr0 — Management bridge (LAN) +auto vmbr0 +iface vmbr0 inet static + address 192.168.16.3/24 + gateway 192.168.16.1 + bridge-ports eno1 + bridge-stp off + bridge-fd 0 + +# vmbr1 — Internal VM network (no physical port, NAT) +auto vmbr1 +iface vmbr1 inet static + address 192.168.20.1/24 + bridge-ports none + bridge-stp off + bridge-fd 0 + post-up echo 1 > /proc/sys/net/ipv4/ip_forward + post-up iptables -t nat -A POSTROUTING -s 192.168.20.0/24 -o vmbr0 -j MASQUERADE + post-down iptables -t nat -D POSTROUTING -s 192.168.20.0/24 -o vmbr0 -j MASQUERADE +INTERFACES diff --git a/proxmox/network/setup-nat.sh b/proxmox/network/setup-nat.sh index 27b35a1..e2a49ec 100755 --- a/proxmox/network/setup-nat.sh +++ b/proxmox/network/setup-nat.sh @@ -1,7 +1,13 @@ #!/usr/bin/env bash # setup-nat.sh — Configure NAT (MASQUERADE) on a Proxmox host -# This enables VMs on an internal bridge (e.g. vmbr1) to reach the internet -# through the host's primary interface. +# This enables VMs on the internal bridge (vmbr1 / 192.168.20.0/24) to reach +# the internet through the host's primary interface (vmbr0 / 192.168.16.0/24). +# +# Network topology: +# Host 1: 192.168.16.2 (RX 6800 XT 16GB) +# Host 2: 192.168.16.3 (GTX 1080 8GB) +# ThinkPad: 192.168.16.10 +# VM-Netz: 192.168.20.0/24 (vmbr1) # # Usage: sudo bash setup-nat.sh [WAN_IFACE] [INTERNAL_BRIDGE] [INTERNAL_SUBNET] @@ -9,7 +15,7 @@ set -euo pipefail WAN_IFACE="${1:-vmbr0}" INTERNAL_BRIDGE="${2:-vmbr1}" -INTERNAL_SUBNET="${3:-10.10.10.0/24}" +INTERNAL_SUBNET="${3:-192.168.20.0/24}" echo "==> Configuring NAT" echo " WAN interface: $WAN_IFACE" diff --git a/proxmox/remote-access/connect-rdp.sh b/proxmox/remote-access/connect-rdp.sh new file mode 100644 index 0000000..280ba25 --- /dev/null +++ b/proxmox/remote-access/connect-rdp.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# connect-rdp.sh — Connect to a Windows VM via RDP from the ThinkPad +# +# Requires: xfreerdp (freerdp2-x11) installed on ThinkPad +# sudo apt install freerdp2-x11 +# +# Usage: bash connect-rdp.sh [VM_IP] [USERNAME] [RESOLUTION] +# +# Network topology: +# ThinkPad (192.168.16.10) → Host 1 (192.168.16.2) → Windows VM (192.168.20.10) +# +# NOTE: ThinkPad needs a route to 192.168.20.0/24 via 192.168.16.2: +# sudo ip route add 192.168.20.0/24 via 192.168.16.2 + +set -euo pipefail + +VM_IP="${1:-192.168.20.10}" +USERNAME="${2:-Administrator}" +RESOLUTION="${3:-1920x1080}" + +echo "==> Connecting to Windows VM via RDP" +echo " Target: $VM_IP" +echo " User: $USERNAME" +echo " Resolution: $RESOLUTION" +echo "" + +# Check if xfreerdp is installed +if ! command -v xfreerdp &>/dev/null; then + echo "ERROR: xfreerdp not found." + echo "Install with: sudo apt install freerdp2-x11" + exit 1 +fi + +# Check connectivity +if ! ping -c1 -W2 "$VM_IP" &>/dev/null; then + echo "WARNING: Cannot reach $VM_IP" + echo "You may need to add a route:" + echo " sudo ip route add 192.168.20.0/24 via 192.168.16.2" + echo "" + echo "Attempting connection anyway..." +fi + +xfreerdp \ + /v:"$VM_IP" \ + /u:"$USERNAME" \ + /size:"$RESOLUTION" \ + /dynamic-resolution \ + /gfx:AVC444 \ + /network:lan \ + /sound:sys:pulse \ + /microphone:sys:pulse \ + /drive:shared,/home/"$(whoami)"/Shared \ + /clipboard \ + +auto-reconnect \ + /auto-reconnect-max-retries:5 \ + /cert:ignore diff --git a/proxmox/remote-access/connect-spice.sh b/proxmox/remote-access/connect-spice.sh new file mode 100644 index 0000000..6c9e06d --- /dev/null +++ b/proxmox/remote-access/connect-spice.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# connect-spice.sh — Connect to a Linux Desktop VM via SPICE +# +# Requires: virt-viewer installed on ThinkPad +# sudo apt install virt-viewer +# +# Usage: bash connect-spice.sh [PVE_HOST] [VMID] [PVE_USER] +# +# This script uses the Proxmox API to generate a SPICE connection file +# and opens it with remote-viewer. + +set -euo pipefail + +PVE_HOST="${1:-192.168.16.2}" +VMID="${2:-200}" +PVE_USER="${3:-root@pam}" +PVE_PORT="8006" + +echo "==> Connecting to VM $VMID via SPICE" +echo " PVE Host: $PVE_HOST" +echo " VMID: $VMID" +echo "" + +# Check remote-viewer +if ! command -v remote-viewer &>/dev/null; then + echo "ERROR: remote-viewer not found." + echo "Install with: sudo apt install virt-viewer" + exit 1 +fi + +# Prompt for password +read -rsp "Enter password for $PVE_USER: " PVE_PASS +echo "" + +# Get API ticket +echo "==> Authenticating..." +AUTH_RESPONSE=$(curl -s -k -d "username=$PVE_USER&password=$PVE_PASS" \ + "https://$PVE_HOST:$PVE_PORT/api2/json/access/ticket") + +TICKET=$(echo "$AUTH_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['ticket'])" 2>/dev/null) +CSRF=$(echo "$AUTH_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['CSRFPreventionToken'])" 2>/dev/null) + +if [[ -z "$TICKET" ]]; then + echo "ERROR: Authentication failed." + exit 1 +fi + +# Request SPICE proxy config +NODE=$(curl -s -k -b "PVEAuthCookie=$TICKET" \ + "https://$PVE_HOST:$PVE_PORT/api2/json/nodes" | \ + python3 -c "import sys,json; print(json.load(sys.stdin)['data'][0]['node'])" 2>/dev/null) + +echo "==> Requesting SPICE configuration from node '$NODE'..." +SPICE_RESPONSE=$(curl -s -k -b "PVEAuthCookie=$TICKET" \ + -H "CSRFPreventionToken: $CSRF" \ + -X POST \ + "https://$PVE_HOST:$PVE_PORT/api2/json/nodes/$NODE/qemu/$VMID/spiceproxy") + +# Create .vv file +VV_FILE="/tmp/pve-spice-$VMID.vv" +python3 -c " +import sys, json +data = json.loads('''$SPICE_RESPONSE''')['data'] +print('[virt-viewer]') +for k, v in data.items(): + print(f'{k}={v}') +" > "$VV_FILE" + +echo "==> Opening SPICE connection..." +remote-viewer "$VV_FILE" & +echo " Connection file: $VV_FILE" diff --git a/proxmox/remote-access/setup-thinkpad-routes.sh b/proxmox/remote-access/setup-thinkpad-routes.sh new file mode 100644 index 0000000..9eac449 --- /dev/null +++ b/proxmox/remote-access/setup-thinkpad-routes.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# setup-thinkpad-routes.sh — Configure routing on ThinkPad to reach VM network +# +# Run this on the ThinkPad (192.168.16.10) to enable access to VMs +# on the internal 192.168.20.0/24 network via the Proxmox hosts. +# +# Usage: sudo bash setup-thinkpad-routes.sh + +set -euo pipefail + +echo "==> Setting up routes on ThinkPad to reach VM network" +echo "" +echo " ThinkPad: 192.168.16.10" +echo " Host 1: 192.168.16.2 (gateway to VMs)" +echo " Host 2: 192.168.16.3 (gateway to VMs)" +echo " VM subnet: 192.168.20.0/24" +echo "" + +# Add route to VM subnet via Host 1 +# (Adjust to Host 2 if VMs are on that host) +if ip route show | grep -q '192.168.20.0/24'; then + echo " Route to 192.168.20.0/24 already exists:" + ip route show | grep '192.168.20.0/24' +else + ip route add 192.168.20.0/24 via 192.168.16.2 + echo " Added: 192.168.20.0/24 via 192.168.16.2" +fi + +# Verify connectivity +echo "" +echo "==> Testing connectivity..." +for IP in 192.168.20.10 192.168.20.20; do + if ping -c1 -W2 "$IP" &>/dev/null; then + echo " $IP — reachable" + else + echo " $IP — not reachable (VM may not be running)" + fi +done + +echo "" +echo "==> To make persistent, add to /etc/network/interfaces or NetworkManager:" +echo " # /etc/network/interfaces (if using):" +echo " up ip route add 192.168.20.0/24 via 192.168.16.2" +echo "" +echo " # Or via nmcli:" +echo " nmcli connection modify +ipv4.routes '192.168.20.0/24 192.168.16.2'" +echo " nmcli connection up " diff --git a/proxmox/vm-linux-desktop/create-linux-desktop-vm.sh b/proxmox/vm-linux-desktop/create-linux-desktop-vm.sh new file mode 100644 index 0000000..40846a5 --- /dev/null +++ b/proxmox/vm-linux-desktop/create-linux-desktop-vm.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# create-linux-desktop-vm.sh — Create a Linux Desktop VM with SPICE display +# +# Creates a lightweight Linux Desktop VM accessible via SPICE from the +# ThinkPad (192.168.16.10) or any machine on the network. +# +# Usage: sudo bash create-linux-desktop-vm.sh [VMID] [VM_NAME] [CLOUD_IMG_URL] + +set -euo pipefail + +VMID="${1:-200}" +VM_NAME="${2:-linux-desktop}" +IMAGE_URL="${3:-https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img}" +STORAGE="local-lvm" +BRIDGE="vmbr1" +VM_IP="192.168.20.20" + +echo "============================================" +echo " Linux Desktop VM (SPICE)" +echo "============================================" +echo " VMID: $VMID" +echo " Name: $VM_NAME" +echo " VM IP: $VM_IP (vmbr1)" +echo " Display: SPICE + QXL" +echo "============================================" +echo "" + +IMAGE_FILE="/tmp/cloud-image-$(basename "$IMAGE_URL")" + +# Download cloud image +if [[ ! -f "$IMAGE_FILE" ]]; then + echo "==> Downloading cloud image..." + wget -q --show-progress -O "$IMAGE_FILE" "$IMAGE_URL" +else + echo "==> Cloud image already cached: $IMAGE_FILE" +fi + +# Destroy existing VM +if qm status "$VMID" &>/dev/null; then + echo "==> Destroying existing VM $VMID..." + qm destroy "$VMID" --purge +fi + +# --- Create VM with SPICE display --- +echo "==> Creating VM..." +qm create "$VMID" \ + --name "$VM_NAME" \ + --ostype l26 \ + --machine q35 \ + --memory 4096 \ + --cores 4 \ + --cpu host \ + --scsihw virtio-scsi-single \ + --net0 "virtio,bridge=$BRIDGE" \ + --agent enabled=1 \ + --vga qxl \ + --tablet 1 + +# Import disk +echo "==> Importing disk..." +qm set "$VMID" --scsi0 "$STORAGE:0,import-from=$IMAGE_FILE,iothread=1,ssd=1,discard=on" + +# Cloud-Init drive +qm set "$VMID" --ide2 "$STORAGE:cloudinit" + +# Boot from disk +qm set "$VMID" --boot order=scsi0 + +# Resize disk to 50GB +echo "==> Resizing disk to 50G..." +qm disk resize "$VMID" scsi0 50G + +# SPICE enhancements +qm set "$VMID" --usb0 spice --usb1 spice --usb2 spice + +# Cloud-Init: set user, SSH key, network +qm set "$VMID" \ + --ciuser admin \ + --cipassword "changeme" \ + --ipconfig0 "ip=$VM_IP/24,gw=192.168.20.1" \ + --nameserver "192.168.16.1" \ + --searchdomain "local" + +echo "" +echo "============================================" +echo " VM $VMID ($VM_NAME) created" +echo "============================================" +echo "" +echo "NEXT STEPS:" +echo "" +echo "1. Start the VM:" +echo " qm start $VMID" +echo "" +echo "2. Install a desktop environment (via SSH or console):" +echo " ssh admin@$VM_IP" +echo " sudo apt update && sudo apt install -y ubuntu-desktop spice-vdagent" +echo " sudo reboot" +echo "" +echo "3. Connect via SPICE from ThinkPad:" +echo " # Option A: Proxmox Web UI → VM → Console → SPICE" +echo " # Option B: Download .vv file and open with virt-viewer:" +echo " remote-viewer spice://192.168.16.2:5900" +echo "" +echo "4. Or install xrdp for RDP access:" +echo " sudo apt install -y xrdp" +echo " sudo systemctl enable xrdp" +echo " xfreerdp /v:$VM_IP /u:admin" +echo "" diff --git a/proxmox/vm-windows/create-windows-vm.sh b/proxmox/vm-windows/create-windows-vm.sh new file mode 100644 index 0000000..53dd01f --- /dev/null +++ b/proxmox/vm-windows/create-windows-vm.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# create-windows-vm.sh — Create a Windows VM on Host 1 with RX 6800 XT passthrough +# +# This script creates a Windows 10/11 VM optimized for GPU passthrough. +# Run on Host 1 (192.168.16.2) where the RX 6800 XT is installed. +# +# Prerequisites: +# - Windows ISO uploaded to /var/lib/vz/template/iso/ +# - VirtIO drivers ISO: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/ +# - IOMMU enabled (intel_iommu=on / amd_iommu=on) +# - RX 6800 XT bound to vfio-pci (see setup-gpu-passthrough.sh) +# +# Usage: sudo bash create-windows-vm.sh [VMID] [VM_NAME] [WIN_ISO] [VIRTIO_ISO] + +set -euo pipefail + +VMID="${1:-100}" +VM_NAME="${2:-win11-workstation}" +WIN_ISO="${3:-local:iso/Win11_23H2_German_x64.iso}" +VIRTIO_ISO="${4:-local:iso/virtio-win.iso}" +STORAGE="local-lvm" +BRIDGE="vmbr1" +VM_IP="192.168.20.10" + +echo "============================================" +echo " Windows VM with GPU Passthrough" +echo "============================================" +echo " VMID: $VMID" +echo " Name: $VM_NAME" +echo " Host: 192.168.16.2 (Host 1)" +echo " VM IP: $VM_IP (vmbr1)" +echo " GPU: RX 6800 XT (passthrough)" +echo "============================================" +echo "" + +# Destroy existing VM with same ID +if qm status "$VMID" &>/dev/null; then + echo "==> Destroying existing VM $VMID..." + qm destroy "$VMID" --purge +fi + +# --- Detect RX 6800 XT PCI addresses --- +echo "==> Detecting RX 6800 XT PCI addresses..." +GPU_VGA=$(lspci -nn | grep -i 'navi 21' | grep -i 'vga\|display' | awk '{print $1}' | head -1) +GPU_AUDIO=$(lspci -nn | grep -i 'navi 21' | grep -i 'audio' | awk '{print $1}' | head -1) + +if [[ -z "$GPU_VGA" ]]; then + # Fallback: search for any RX 6800 + GPU_VGA=$(lspci -nn | grep -i '6800' | grep -i 'vga\|display' | awk '{print $1}' | head -1) + GPU_AUDIO=$(lspci -nn | grep -i '6800' | grep -i 'audio' | awk '{print $1}' | head -1) +fi + +if [[ -z "$GPU_VGA" ]]; then + echo "ERROR: Could not detect RX 6800 XT. Check lspci output." + echo " You can manually set GPU_VGA and GPU_AUDIO in this script." + exit 1 +fi + +echo " GPU VGA: $GPU_VGA" +echo " GPU Audio: $GPU_AUDIO" + +# --- Create VM --- +echo "==> Creating VM..." +qm create "$VMID" \ + --name "$VM_NAME" \ + --ostype win11 \ + --machine q35 \ + --bios ovmf \ + --efidisk0 "$STORAGE:1,efitype=4m,pre-enrolled-keys=1" \ + --tpmstate0 "$STORAGE:1,version=v2.0" \ + --memory 16384 \ + --balloon 0 \ + --cores 8 \ + --sockets 1 \ + --cpu host,hidden=1 \ + --scsihw virtio-scsi-single \ + --net0 "virtio,bridge=$BRIDGE" \ + --agent enabled=1 + +# --- Disks --- +echo "==> Adding disks..." +# OS disk (100GB SSD) +qm set "$VMID" --scsi0 "$STORAGE:100,iothread=1,ssd=1,discard=on" +# Windows ISO +qm set "$VMID" --ide0 "$WIN_ISO,media=cdrom" +# VirtIO drivers ISO +qm set "$VMID" --ide1 "$VIRTIO_ISO,media=cdrom" +# Boot order: CD first for installation, then disk +qm set "$VMID" --boot order="ide0;scsi0" + +# --- GPU Passthrough --- +echo "==> Configuring GPU passthrough..." +HOSTPCI_ARGS="$GPU_VGA,pcie=1,x-vga=1" +qm set "$VMID" --hostpci0 "$HOSTPCI_ARGS" +if [[ -n "$GPU_AUDIO" ]]; then + qm set "$VMID" --hostpci1 "$GPU_AUDIO,pcie=1" +fi + +# Disable default display (GPU takes over) +qm set "$VMID" --vga none + +# --- USB passthrough for keyboard/mouse (optional) --- +# Uncomment and adjust if needed: +# qm set "$VMID" --usb0 host=046d:c52b # Logitech receiver +# qm set "$VMID" --usb1 host=1532:006e # Razer keyboard + +echo "" +echo "============================================" +echo " VM $VMID ($VM_NAME) created successfully" +echo "============================================" +echo "" +echo "NEXT STEPS:" +echo "" +echo "1. Start VM and install Windows:" +echo " qm start $VMID" +echo " (Use Proxmox console for initial install — VGA output goes to GPU)" +echo "" +echo "2. During Windows install, load VirtIO drivers:" +echo " Browse to D:\\amd64\\w11\\ for storage driver" +echo " Browse to D:\\NetKVM\\w11\\amd64\\ for network driver" +echo "" +echo "3. After install, inside Windows:" +echo " a) Install VirtIO guest tools (D:\\virtio-win-guest-tools.exe)" +echo " b) Install AMD GPU drivers" +echo " c) Enable Remote Desktop:" +echo " Settings → System → Remote Desktop → ON" +echo " d) Set static IP: $VM_IP/24, Gateway: 192.168.20.1, DNS: 192.168.16.1" +echo "" +echo "4. Change boot order after install:" +echo " qm set $VMID --boot order=scsi0" +echo "" +echo "5. Connect from ThinkPad (192.168.16.10):" +echo " xfreerdp /v:$VM_IP /u:Administrator /dynamic-resolution" +echo "" diff --git a/proxmox/vm-windows/setup-gpu-passthrough.sh b/proxmox/vm-windows/setup-gpu-passthrough.sh new file mode 100644 index 0000000..8b99725 --- /dev/null +++ b/proxmox/vm-windows/setup-gpu-passthrough.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# setup-gpu-passthrough.sh — Prepare Host 1 for RX 6800 XT GPU passthrough +# +# This script: +# 1. Enables IOMMU in GRUB +# 2. Blacklists the amdgpu/radeon drivers on the host +# 3. Loads vfio-pci and binds the GPU to it +# 4. Updates initramfs +# +# Run on Host 1 (192.168.16.2) — requires reboot after execution. +# +# Usage: sudo bash setup-gpu-passthrough.sh [GPU_IDS] +# Example: sudo bash setup-gpu-passthrough.sh "1002:73bf,1002:ab28" + +set -euo pipefail + +echo "============================================" +echo " RX 6800 XT GPU Passthrough Setup" +echo " Host 1 — 192.168.16.2" +echo "============================================" +echo "" + +# --- Detect GPU PCI IDs --- +echo "==> Detecting RX 6800 XT PCI IDs..." +# Look for Navi 21 (RX 6800 XT) vendor:device IDs +GPU_IDS_DETECTED=$(lspci -nn | grep -i 'navi 21\|6800' | grep -oP '\[\K[0-9a-f]{4}:[0-9a-f]{4}' | sort -u | paste -sd',' -) +GPU_IDS="${1:-$GPU_IDS_DETECTED}" + +if [[ -z "$GPU_IDS" ]]; then + echo "ERROR: Could not detect GPU IDs. Provide them manually:" + echo " bash setup-gpu-passthrough.sh '1002:73bf,1002:ab28'" + echo "" + echo "Find IDs with: lspci -nn | grep -i VGA" + exit 1 +fi + +echo " GPU PCI IDs: $GPU_IDS" +echo "" + +# --- Detect CPU vendor for IOMMU --- +CPU_VENDOR=$(grep -m1 'vendor_id' /proc/cpuinfo | awk '{print $3}') +if [[ "$CPU_VENDOR" == "GenuineIntel" ]]; then + IOMMU_PARAM="intel_iommu=on" +elif [[ "$CPU_VENDOR" == "AuthenticAMD" ]]; then + IOMMU_PARAM="amd_iommu=on" +else + echo "WARNING: Unknown CPU vendor '$CPU_VENDOR'. Defaulting to intel_iommu=on" + IOMMU_PARAM="intel_iommu=on" +fi + +# --- 1. Enable IOMMU in GRUB --- +echo "==> Configuring GRUB for IOMMU..." +GRUB_FILE="/etc/default/grub" +GRUB_BACKUP="/etc/default/grub.bak.$(date +%s)" +cp "$GRUB_FILE" "$GRUB_BACKUP" + +CURRENT_CMDLINE=$(grep '^GRUB_CMDLINE_LINUX_DEFAULT=' "$GRUB_FILE" | sed 's/GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"/\1/') + +# Add IOMMU params if not already present +NEW_CMDLINE="$CURRENT_CMDLINE" +for param in "$IOMMU_PARAM" "iommu=pt"; do + if ! echo "$NEW_CMDLINE" | grep -q "$param"; then + NEW_CMDLINE="$NEW_CMDLINE $param" + fi +done +NEW_CMDLINE=$(echo "$NEW_CMDLINE" | sed 's/^ *//') + +sed -i "s|^GRUB_CMDLINE_LINUX_DEFAULT=.*|GRUB_CMDLINE_LINUX_DEFAULT=\"$NEW_CMDLINE\"|" "$GRUB_FILE" +echo " GRUB_CMDLINE_LINUX_DEFAULT=\"$NEW_CMDLINE\"" + +# --- 2. Load VFIO modules early --- +echo "==> Configuring VFIO modules..." +cat > /etc/modules-load.d/vfio.conf < Binding GPU IDs to vfio-pci..." +cat > /etc/modprobe.d/vfio.conf < Blacklisting AMD GPU drivers on host..." +cat > /etc/modprobe.d/blacklist-gpu.conf < Updating initramfs..." +update-initramfs -u -k all + +echo "==> Updating GRUB..." +update-grub + +echo "" +echo "============================================" +echo " GPU Passthrough Setup Complete" +echo "============================================" +echo "" +echo " GPU IDs: $GPU_IDS" +echo " IOMMU: $IOMMU_PARAM iommu=pt" +echo " Driver: vfio-pci" +echo " Blacklisted: amdgpu, radeon" +echo "" +echo " GRUB backup: $GRUB_BACKUP" +echo "" +echo " >>> REBOOT REQUIRED <<<" +echo " After reboot, verify with:" +echo " dmesg | grep -i vfio" +echo " lspci -nnk | grep -A3 'VGA'" +echo " bash proxmox/gpu/gpu-check.sh" +echo "" From b4787b7e71b2be930ed34a7a7af59734e7b174e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 27 Feb 2026 11:44:20 +0000 Subject: [PATCH 3/3] Add CLAUDE.md with comprehensive codebase guide for AI assistants Documents project architecture, tool registration pattern, all 13 MCP tools, authentication flow, code style conventions, git workflow, CI/CD pipelines, Docker build, and testing approach. --- CLAUDE.md | 239 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0b534ae --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,239 @@ +# CLAUDE.md — Docker Hub MCP Server + +This file provides guidance for AI assistants working in this codebase. + +## Project Overview + +The **Docker Hub MCP Server** is a TypeScript/Node.js implementation of the [Model Context Protocol (MCP)](https://modelcontextprotocol.io) that exposes Docker Hub APIs as MCP tools. LLM clients (Claude Desktop, VS Code, Docker Ask Gordon, etc.) connect to this server to search for images, manage repositories, inspect tags, and query Docker Scout for hardened images. + +- **License:** Apache 2.0 +- **Node.js requirement:** `>=22` +- **Primary language:** TypeScript (strict mode) +- **MCP transport options:** stdio (default) and HTTP (Streamable HTTP) + +--- + +## Repository Structure + +``` +hub-mcp/ +├── src/ # All TypeScript source code +│ ├── index.ts # CLI entry point — parses args, starts server +│ ├── server.ts # HubMCPServer class, Express routes, MCP setup +│ ├── asset.ts # Abstract base class for all API integrations +│ ├── repos.ts # Repository tools (CRUD, tags) — largest module +│ ├── accounts.ts # Namespace/account tools +│ ├── search.ts # Docker Hub search tools +│ ├── scout.ts # Docker Scout / Hardened Images tools +│ ├── types.ts # Shared Zod schemas (paginated responses) +│ ├── logger.ts # Winston logger config +│ └── scout/ +│ ├── client.ts # GraphQL client for Scout API +│ └── genql/ # Auto-generated GraphQL client (do not edit) +│ └── scripts/ +│ └── toolsList.ts # CLI script to validate/update tools.json +├── .github/ +│ ├── workflows/ +│ │ ├── lint.yml # ESLint + Prettier check on PRs +│ │ ├── release.yml # Docker multi-platform image build & push +│ │ ├── scorecard.yml # OpenSSF security scorecard +│ │ └── tools-list.yml # Validates tools.json consistency on PRs +│ └── pull_request_template.md +├── docs/ +│ └── proxmox-setup.md # Infrastructure documentation +├── proxmox/ # Proxmox VE infrastructure-as-code scripts +├── tools.json # Generated tool definitions (194 KB, do not edit manually) +├── tools.txt # Human-readable tools summary (generated) +├── Dockerfile # Multi-stage build: builder → slim runtime image +├── package.json +├── tsconfig.json +├── eslint.config.mjs +└── .prettierrc.json +``` + +--- + +## Development Commands + +```bash +# Install dependencies +npm ci + +# Build TypeScript → dist/ +npm run build + +# Start the server (stdio transport) +npm start + +# Start with HTTP transport on port 3000 +node dist/index.js --transport=http --port=3000 + +# Development: watch mode with MCP Inspector +npm run inspect + +# Linting and formatting +npm run lint +npm run format:check +npm run format:fix + +# Validate tools.json is up to date +npm run list-tools:check + +# Regenerate tools.json +npm run list-tools:update + +# Regenerate Scout GraphQL client +npm run genql.scout +``` + +--- + +## Architecture: How Tools Are Registered + +All API integrations follow a common pattern via the `Asset` abstract base class (`src/asset.ts`): + +1. **`Asset` base class** — provides `authFetch()`, `callAPI()`, and `authenticate()` methods. Handles both Bearer token and PAT (Personal Access Token with JWT) auth with automatic token refresh. + +2. **Concrete Asset subclasses** — `Repos`, `Accounts`, `Search`, `ScoutAPI` each extend `Asset` and implement `RegisterTools()` to register MCP tools with the `McpServer` instance. + +3. **`HubMCPServer`** (`src/server.ts`) — instantiates all four assets, calls `asset.RegisterTools()` for each, and manages transport (stdio or HTTP). + +4. **`index.ts`** — parses CLI flags (`--transport`, `--port`, `--username`) and the `HUB_PAT_TOKEN` env var, then calls `new HubMCPServer(username, patToken).run(port, transport)`. + +### Adding a New Tool + +1. Add your tool in the appropriate Asset class (or create a new one extending `Asset`). +2. Register it inside `RegisterTools()` using `this.server.tool(...)`. +3. Use `this.callAPI(url, options, outMsg, errMsg, unAuthMsg)` to make authenticated requests. +4. Define Zod schemas for inputs/outputs in `types.ts` or co-located in the module. +5. Run `npm run list-tools:update` to regenerate `tools.json` and `tools.txt`. +6. Update `README.md` with tool documentation and examples. + +--- + +## Available MCP Tools (13 total) + +| Tool | Asset class | Description | +|------|-------------|-------------| +| `listRepositoriesByNamespace` | Repos | List repos with pagination & filtering | +| `createRepository` | Repos | Create a new repository | +| `getRepositoryInfo` | Repos | Get repository details | +| `updateRepositoryInfo` | Repos | Update repository metadata | +| `checkRepository` | Repos | Check if a repository exists | +| `listRepositoryTags` | Repos | List tags with arch/OS filtering | +| `getRepositoryTag` | Repos | Get a specific tag | +| `checkRepositoryTag` | Repos | Check if a tag exists | +| `listNamespaces` | Accounts | List organizations with pagination | +| `getPersonalNamespace` | Accounts | Get user's personal namespace | +| `listAllNamespacesMemberOf` | Accounts | List all accessible namespaces | +| `search` | Search | Search Docker Hub (images, plugins, extensions) | +| `dockerHardenedImages` | ScoutAPI | Query mirrored Docker Hardened Images | + +--- + +## Authentication + +- **`HUB_PAT_TOKEN`** (env var) — Docker Hub Personal Access Token. Required for write operations and authenticated access. +- **`--username`** (CLI flag) — Docker Hub username. Required for PAT-based auth. +- **`Search` asset** — unauthenticated; does not require credentials. +- PAT authentication exchanges the token for a short-lived JWT via `POST /v2/users/login`, then caches it per-username with automatic expiry detection and re-authentication. + +--- + +## Code Style & Conventions + +- **TypeScript strict mode** — no implicit `any`, all types must be explicit. +- **Prettier** — enforced; settings in `.prettierrc.json`: 4-space tabs, single quotes, semi-colons, 100-char line width, LF line endings. +- **ESLint** — `eslint.config.mjs` with TypeScript-ESLint recommended rules. +- **Always run before committing:** + ```bash + npm run lint + npm run format:fix + ``` +- **Zod schemas** — use Zod for all API request/response validation; define schemas close to where they're used or in `types.ts` for shared schemas. +- **Logging** — use the `logger` from `src/logger.ts` (Winston). Never use `console.log` in production paths; `console.error` is used only in `authenticate()` for debug tracing. +- **Error handling** — `callAPI()` catches all errors and returns an MCP `CallToolResult` with `isError: true`. Do not throw from tool handlers. +- **User-Agent** — always include `'User-Agent': 'DockerHub-MCP-Server/1.0.0'` in API request headers (set in `authFetch()` by default). + +--- + +## Git & Pull Request Conventions + +- Branch names: `-short-description` (e.g., `42-add-webhooks-tool`) +- Commit messages: capitalized, imperative mood, max 50 chars summary, optional body after blank line +- Sign-off required: `git commit -s` (Developer Certificate of Origin) +- Squash commits into logical units before opening a PR (`git rebase -i`) +- Always rebase on base branch, never merge: `git rebase main` +- Include `Closes #XXXX` or `Fixes #XXXX` in PR descriptions +- Fill out the PR template (`.github/pull_request_template.md`) completely + +--- + +## CI/CD Workflows + +| Workflow | Trigger | What it does | +|----------|---------|--------------| +| `lint.yml` | Pull requests | `npm ci && npm run lint && npm run format:check` | +| `tools-list.yml` | Pull requests | `npm run list-tools:check` — validates tools.json is current | +| `release.yml` | Push to main / manual | Builds multi-platform Docker image (amd64 + arm64), pushes to Docker Hub | +| `scorecard.yml` | Scheduled | OpenSSF security scorecard analysis | + +**All CI checks must pass before merging a PR.** + +--- + +## Docker Build + +Multi-stage Dockerfile: +1. **Builder stage** — `node:current-alpine3.22`, runs `npm ci` + `npm run build` +2. **Runtime stage** — `node:current-alpine3.22`, copies `dist/` + production `node_modules`, runs as non-root `appuser` + +```bash +# Build image locally +docker build -t hub-mcp . + +# Run with stdio transport (for MCP clients) +docker run -e HUB_PAT_TOKEN= hub-mcp --username= + +# Run with HTTP transport +docker run -p 3000:3000 -e HUB_PAT_TOKEN= hub-mcp --transport=http --username= +``` + +--- + +## Key Files to Know + +| File | Purpose | +|------|---------| +| `src/asset.ts` | Base class — understand this before adding any new tools | +| `src/repos.ts` | Largest module (757 LOC) — reference for tool implementation patterns | +| `src/server.ts` | Entry point for transport setup and asset wiring | +| `tools.json` | **Generated** — never edit manually; regenerate with `npm run list-tools:update` | +| `src/scout/genql/` | **Generated** — never edit; regenerate with `npm run genql.scout` | + +--- + +## Testing + +There are no automated unit tests in this project. Testing is done via: + +- **MCP Inspector**: `npm run inspect` — starts the server in watch mode with the MCP Inspector UI +- **Manual testing** with MCP clients (Claude Desktop, VS Code, Docker Ask Gordon) +- **tools.json validation**: `npm run list-tools:check` — ensures tool definitions are consistent + +When contributing a new tool, include screenshots from the MCP Inspector or an MCP client in your PR. + +--- + +## Environment Variables + +| Variable | Required | Description | +|----------|----------|-------------| +| `HUB_PAT_TOKEN` | For auth | Docker Hub Personal Access Token | +| `NODE_ENV` | No | `production` enables file logging to `/app/logs`; otherwise console logging | + +--- + +## Proxmox Infrastructure + +The `proxmox/` directory contains infrastructure-as-code for a Proxmox VE homelab cluster (unrelated to the MCP server itself). See `docs/proxmox-setup.md` for details. These scripts manage VM creation, GPU passthrough, networking, file sharing, and automated backups.