Files
hermes-skills/devops/proxmox-ve-administration/references/proxmox-lxc-gpu/lxc-gpu-autosetup-background-script.md
T

6.0 KiB

LXC GPU Autosetup — Background Script Pattern

Problem

Provisioning GPU-accelerated LXC containers involves long-running operations:

  1. apt update && apt install inside LXC (first setup)
  2. amdgpu-install --usecase=rocm --no-dkms (30-90 min, 100+ packages)
  3. pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.2 (~4 GB download, 10-20 min)
  4. pip install -r requirements.txt for ComfyUI/Ollama/vLLM

Running any of these in the foreground via pct exec is brittle:

  • SSH timeout kills the session
  • pct exec double-shell-quoting breaks complex commands
  • No way to resume if interrupted

Solution: Background Script on Host

Write a bash script on the Proxmox host, run it via nohup, and poll completion markers.

Step 1: Write the Script

# /tmp/ctNNN_autosetup.sh — runs ON the Proxmox host, orchestrates INSIDE the LXC
CTID=${1:-204}
LOG=/tmp/ct${CTID}_autosetup.log
DONE=/tmp/ct${CTID}_autosetup_done

echo "$(date): Starting auto-setup for CT ${CTID}..." > "$LOG"

# --- Wait for dpkg lock inside CT ---
while pct exec "$CTID" -- fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do
    echo "$(date): dpkg still locked..." >> "$LOG"
    sleep 30
done
echo "$(date): dpkg free." >> "$LOG"

# --- Install ROCm (inside CT) ---
pct exec "$CTID" -- bash -c '
    export DEBIAN_FRONTEND=noninteractive
    export PATH=/opt/rocm/bin:/usr/local/bin:$PATH
    dpkg --configure -a 2>&1
    apt-get -f install -y -qq 2>&1
    cd /tmp
    wget -q https://repo.radeon.com/amdgpu-install/6.3.3/ubuntu/noble/amdgpu-install_6.3.60303-1_all.deb
    dpkg -i amdgpu-install_6.3.60303-1_all.deb 2>&1 | tail -3
    apt-get update -qq
    amdgpu-install --usecase=rocm --no-dkms -y 2>&1 | tail -10
' >> "$LOG" 2>&1

# --- Verify ROCm ---
echo "$(date): Verifying ROCm..." >> "$LOG"
pct exec "$CTID" -- bash -c 'export PATH=/opt/rocm/bin:$PATH; rocminfo 2>&1 | grep -E "Name:" | head -5' >> "$LOG" 2>&1
pct exec "$CTID" -- bash -c 'export PATH=/opt/rocm/bin:$PATH; rocm-smi 2>&1 | head -5' >> "$LOG" 2>&1

# --- PyTorch ROCm ---
echo "$(date): Installing PyTorch ROCm..." >> "$LOG"
pct exec "$CTID" -- bash -c 'pip3 install --break-system-packages torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.2 2>&1 | tail -10' >> "$LOG" 2>&1

# --- ComfyUI / App ---
echo "$(date): Installing app..." >> "$LOG"
pct exec "$CTID" -- bash -c '
    cd /opt
    git clone https://github.com/comfyanonymous/ComfyUI.git 2>&1 | tail -3
    cd ComfyUI
    pip3 install --break-system-packages -r requirements.txt 2>&1 | tail -10
' >> "$LOG" 2>&1

# --- systemd service ---
pct exec "$CTID" -- bash -c 'cat > /etc/systemd/system/comfyui.service << "EOF"
[Unit]
Description=ComfyUI
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/ComfyUI
ExecStart=/usr/bin/python3 main.py --listen 0.0.0.0 --port 8188
Restart=always
RestartSec=10
Environment=HIP_VISIBLE_DEVICES=0
Environment=PATH=/opt/rocm/bin:/usr/local/bin:/usr/bin:/bin

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable comfyui
' >> "$LOG" 2>&1

pct exec "$CTID" -- systemctl start comfyui >> "$LOG" 2>&1
sleep 5
pct exec "$CTID" -- ss -tlnp | grep 8188 >> "$LOG" 2>&1

echo "$(date): Setup complete." >> "$LOG"
echo "DONE" >> "$DONE"

Step 2: Launch on Host

chmod +x /tmp/ct204_autosetup.sh
nohup bash /tmp/ct204_autosetup.sh > /dev/null 2>&1 &

Step 3: Poll from Client

# Check completion
cat /tmp/ct204_autosetup_done 2>/dev/null || echo NOT_YET

# Check progress
tail -20 /tmp/ct204_autosetup.log

Python Paramiko Polling Pattern

When automating from a remote client (not the Proxmox host):

import paramiko, time

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('10.0.20.91', username='root', password='28acaneltO!#', timeout=15)

for attempt in range(20):  # ~10 minutes
    stdin, stdout, stderr = client.exec_command(
        "cat /tmp/ct204_autosetup_done 2>/dev/null || echo NOT_YET"
    )
    status = stdout.read().decode().strip()
    if status == "DONE":
        print("Setup complete!")
        break
    
    stdin2, stdout2, stderr2 = client.exec_command(
        "tail -5 /tmp/ct204_autosetup.log"
    )
    print(f"Progress: {stdout2.read().decode().strip()[-200:]}")
    time.sleep(30)

client.close()

PEP 668 Fix for Ubuntu 24.04+ Containers

Ubuntu 24.04 Noble enforces externally-managed Python environment. Inside LXC:

# WRONG — fails with PEP 668
pip3 install torch

# RIGHT — bypass inside disposable container
pip3 install --break-system-packages torch torchvision torchaudio \
  --index-url https://download.pytorch.org/whl/rocm6.2

Alternative: Create a venv inside the container (overkill for single-purpose inference boxes).

ROCm PATH Gotcha

ROCm 6.3 does NOT add /opt/rocm/bin to PATH by default. Every pct exec ROCm call needs:

export PATH=/opt/rocm/bin:/usr/local/bin:$PATH
rocminfo
rocm-smi

Or set it globally:

pct exec 204 -- bash -c 'echo "export PATH=/opt/rocm/bin:\$PATH" > /etc/profile.d/rocm.sh'

Quoting Rule for pct exec

pct exec always adds a shell layer. Complex commands should be written to a file inside the container, then executed:

# BAD — quoting nightmare, easy breakage
pct exec 204 -- python3 -c "import torch; print('HIP:', torch.cuda.is_available())"

# GOOD — write script, execute
pct exec 204 -- bash -c 'cat > /tmp/verify.py << "EOF"
import torch
print("HIP available:", torch.cuda.is_available())
print("Device:", torch.cuda.get_device_name(0))
EOF
python3 /tmp/verify.py'

Monitoring via Cronjob

Set up a cronjob that polls the DONE marker and reports status:

# ~/.hermes/cron/ct204_monitor.sh
if [ -f /tmp/ct204_autosetup_done ]; then
    echo "CT 204 SETUP COMPLETE"
    tail -10 /tmp/ct204_autosetup.log
    pct exec 204 -- python3 -c 'import torch; print(torch.cuda.is_available())'
else
    echo "CT 204 SETUP RUNNING"
    tail -5 /tmp/ct204_autosetup.log
fi