Initial commit: OpenClaw Proxmox LXC setup script

Automated single-script setup that creates a Debian 13 LXC container
with OpenClaw AI gateway, XFCE desktop, VNC/noVNC remote access,
and Chromium browser — all configured with systemd services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
adadrag
2026-03-12 15:46:58 +00:00
commit c67d378521
4 changed files with 468 additions and 0 deletions

334
setup-openclaw-lxc.sh Normal file
View File

@@ -0,0 +1,334 @@
#!/usr/bin/env bash
#
# setup-openclaw-lxc.sh — Automated OpenClaw AI assistant setup in a Proxmox LXC container
#
# Run this script directly on the Proxmox host.
# It creates a Debian 13 LXC, installs OpenClaw + XFCE desktop + VNC/noVNC,
# and prints connection info when done.
#
set -euo pipefail
# ─── Colors ───────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
fatal() { err "$@"; exit 1; }
# ─── Pre-flight checks ───────────────────────────────────────────────────────
command -v pct >/dev/null 2>&1 || fatal "pct not found — this script must run on a Proxmox host."
command -v pvesh >/dev/null 2>&1 || fatal "pvesh not found — this script must run on a Proxmox host."
command -v pveam >/dev/null 2>&1 || fatal "pveam not found — this script must run on a Proxmox host."
[[ $(id -u) -eq 0 ]] || fatal "This script must be run as root."
# ─── User prompts ────────────────────────────────────────────────────────────
echo -e "${BOLD}╔═══════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ OpenClaw LXC Setup for Proxmox ║${NC}"
echo -e "${BOLD}╚═══════════════════════════════════════════════════╝${NC}"
echo
read -rp "Container password: " -s CT_PASSWORD
echo
[[ -n "$CT_PASSWORD" ]] || fatal "Password cannot be empty."
read -rp "Disk size in GB [32]: " DISK_SIZE
DISK_SIZE="${DISK_SIZE:-32}"
read -rp "Memory in MB [4096]: " MEMORY
MEMORY="${MEMORY:-4096}"
read -rp "CPU cores [4]: " CORES
CORES="${CORES:-4}"
read -rp "VNC resolution [1920x1080]: " VNC_RES
VNC_RES="${VNC_RES:-1920x1080}"
echo
# ─── Auto-detect next VMID ───────────────────────────────────────────────────
info "Detecting next available VMID..."
VMID=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null \
| python3 -c '
import json, sys
data = json.load(sys.stdin)
ids = [r["vmid"] for r in data if "vmid" in r]
print(max(ids) + 1 if ids else 100)
' 2>/dev/null || echo 100)
ok "Will use VMID: $VMID"
# ─── Auto-detect storage ─────────────────────────────────────────────────────
info "Detecting storage..."
# Find template storage (supports vztmpl content)
TMPL_STORAGE=$(pvesh get /storage --output-format json 2>/dev/null \
| python3 -c '
import json, sys
stores = json.load(sys.stdin)
for s in stores:
content = s.get("content", "")
if "vztmpl" in content:
print(s["storage"])
break
' 2>/dev/null || echo "local")
# Find rootdir storage (prefer local-lvm, then any with rootdir)
ROOT_STORAGE=$(pvesh get /storage --output-format json 2>/dev/null \
| python3 -c '
import json, sys
stores = json.load(sys.stdin)
candidates = []
for s in stores:
content = s.get("content", "")
if "rootdir" in content or "images" in content:
candidates.append(s["storage"])
if "local-lvm" in candidates:
print("local-lvm")
elif candidates:
print(candidates[0])
else:
print("local-lvm")
' 2>/dev/null || echo "local-lvm")
ok "Template storage: $TMPL_STORAGE"
ok "Rootfs storage: $ROOT_STORAGE"
# ─── Download Debian 13 template ─────────────────────────────────────────────
info "Checking for Debian 13 template..."
pveam update >/dev/null 2>&1 || true
TEMPLATE=$(pveam available --section system 2>/dev/null | grep -oP 'debian-13-standard_\S+' | head -1 || true)
if [[ -z "$TEMPLATE" ]]; then
TEMPLATE=$(pveam available 2>/dev/null | grep -oP 'debian-13-standard_\S+' | head -1 || true)
fi
[[ -n "$TEMPLATE" ]] || fatal "Could not find Debian 13 template. Check 'pveam available'."
if pveam list "$TMPL_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then
ok "Template already downloaded: $TEMPLATE"
else
info "Downloading $TEMPLATE to $TMPL_STORAGE..."
pveam download "$TMPL_STORAGE" "$TEMPLATE"
ok "Template downloaded."
fi
# ─── Create the LXC container ────────────────────────────────────────────────
info "Creating LXC container $VMID..."
pct create "$VMID" "${TMPL_STORAGE}:vztmpl/${TEMPLATE}" \
--hostname openclaw \
--password "$CT_PASSWORD" \
--rootfs "${ROOT_STORAGE}:${DISK_SIZE}" \
--memory "$MEMORY" \
--cores "$CORES" \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1 \
--features nesting=1 \
--start 0
ok "Container $VMID created."
info "Starting container $VMID..."
pct start "$VMID"
ok "Container started."
info "Waiting for container to boot..."
sleep 3
# Helper: run a command inside the container
ct_exec() {
pct exec "$VMID" -- bash -c "$1"
}
# ─── Wait for network ────────────────────────────────────────────────────────
info "Waiting for DHCP lease (up to 30s)..."
CT_IP=""
for i in $(seq 1 30); do
CT_IP=$(ct_exec "hostname -I 2>/dev/null" | awk '{print $1}' || true)
if [[ -n "$CT_IP" && "$CT_IP" != "127.0.0.1" ]]; then
break
fi
CT_IP=""
sleep 1
done
[[ -n "$CT_IP" ]] || warn "Could not detect container IP — continuing anyway."
ok "Container IP: ${CT_IP:-unknown}"
# ─── Install packages inside the container ────────────────────────────────────
info "Updating packages and installing prerequisites..."
ct_exec "
export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get upgrade -y
apt-get install -y curl ca-certificates gnupg git
"
ok "Prerequisites installed."
info "Installing Node.js 22..."
ct_exec "
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs
"
ok "Node.js installed."
info "Installing OpenClaw..."
ct_exec "npm install -g openclaw@latest"
ok "OpenClaw installed."
info "Installing XFCE4, TigerVNC, noVNC, Chromium, dbus-x11..."
ct_exec "
export DEBIAN_FRONTEND=noninteractive
apt-get install -y xfce4 xfce4-terminal tigervnc-standalone-server novnc websockify chromium dbus-x11
"
ok "Desktop environment installed."
# ─── Configure OpenClaw ──────────────────────────────────────────────────────
info "Configuring OpenClaw..."
AUTH_TOKEN=$(openssl rand -hex 16)
ct_exec "
openclaw config set gateway.mode local
openclaw config set gateway.bind lan
openclaw config set gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback true
openclaw config set gateway.auth.token '${AUTH_TOKEN}'
"
ok "OpenClaw configured (token: $AUTH_TOKEN)."
# ─── Configure VNC ───────────────────────────────────────────────────────────
info "Configuring VNC..."
ct_exec "
mkdir -p /root/.vnc
echo '${CT_PASSWORD}' | vncpasswd -f > /root/.vnc/passwd
chmod 600 /root/.vnc/passwd
"
ct_exec "cat > /root/.vnc/xstartup << 'XSTARTUP'
#!/bin/bash
unset SESSION_MANAGER
unset DBUS_SESSION_BUS_ADDRESS
exec startxfce4
XSTARTUP
chmod +x /root/.vnc/xstartup"
ok "VNC configured."
# ─── Fix Chromium for LXC (--no-sandbox) ─────────────────────────────────────
info "Patching Chromium for LXC..."
ct_exec "
sed -i 's|Exec=/usr/bin/chromium|Exec=/usr/bin/chromium --no-sandbox|g' /usr/share/applications/chromium.desktop
cat > /usr/local/bin/chromium-browser << 'WRAPPER'
#!/bin/bash
exec /usr/bin/chromium --no-sandbox \"\$@\"
WRAPPER
chmod +x /usr/local/bin/chromium-browser
mkdir -p /root/Desktop
cp /usr/share/applications/chromium.desktop /root/Desktop/
chmod +x /root/Desktop/chromium.desktop
"
ok "Chromium patched."
# ─── Create systemd services ─────────────────────────────────────────────────
info "Creating systemd services..."
ct_exec "cat > /etc/systemd/system/openclaw-gateway.service << 'SVC'
[Unit]
Description=OpenClaw Gateway
After=network.target
[Service]
Type=simple
ExecStart=/bin/openclaw gateway run --bind lan --auth token
Restart=always
RestartSec=5
Environment=NODE_ENV=production
WorkingDirectory=/root
[Install]
WantedBy=multi-user.target
SVC"
ct_exec "cat > /etc/systemd/system/vncserver.service << SVC
[Unit]
Description=TigerVNC Server
After=network.target
[Service]
Type=forking
ExecStartPre=/bin/sh -c \"/usr/bin/vncserver -kill :1 > /dev/null 2>&1 || :\"
ExecStart=/usr/bin/vncserver :1 -geometry ${VNC_RES} -depth 24 -localhost yes
ExecStop=/usr/bin/vncserver -kill :1
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
SVC"
ct_exec "cat > /etc/systemd/system/novnc.service << 'SVC'
[Unit]
Description=noVNC WebSocket Proxy
After=vncserver.service
Requires=vncserver.service
[Service]
Type=simple
ExecStart=/bin/websockify --web=/usr/share/novnc/ 6080 localhost:5901
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
SVC"
ct_exec "
systemctl daemon-reload
systemctl enable --now openclaw-gateway.service
systemctl enable --now vncserver.service
systemctl enable --now novnc.service
"
ok "All services enabled and started."
sleep 3
# ─── Verify services ─────────────────────────────────────────────────────────
info "Verifying services..."
for svc in openclaw-gateway vncserver novnc; do
if ct_exec "systemctl is-active --quiet $svc"; then
ok "$svc is running."
else
warn "$svc failed to start. Check with: pct exec $VMID -- systemctl status $svc"
fi
done
# Re-detect IP in case it changed
CT_IP=$(ct_exec "hostname -I 2>/dev/null" | awk '{print $1}' || echo "unknown")
# ─── Print connection info ────────────────────────────────────────────────────
echo
echo -e "${BOLD}╔═══════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ OpenClaw LXC Setup Complete! ║${NC}"
echo -e "${BOLD}╚═══════════════════════════════════════════════════╝${NC}"
echo
echo -e " ${BOLD}Container ID:${NC} $VMID"
echo -e " ${BOLD}Container IP:${NC} $CT_IP"
echo
echo -e " ${BOLD}OpenClaw Dashboard:${NC}"
echo -e " http://${CT_IP}:18789/"
echo -e " Auth token: ${CYAN}${AUTH_TOKEN}${NC}"
echo
echo -e " ${BOLD}noVNC (remote desktop):${NC}"
echo -e " http://${CT_IP}:6080/vnc.html"
echo -e " VNC password: (same as container password)"
echo
echo -e " ${BOLD}SSH:${NC}"
echo -e " ssh root@${CT_IP}"
echo
echo -e " ${BOLD}Manage container:${NC}"
echo -e " pct enter $VMID"
echo -e " pct stop $VMID"
echo -e " pct start $VMID"
echo