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

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
# OS
.DS_Store
Thumbs.db
# Editor
*.swp
*.swo
*~
.vscode/
.idea/
# Env
.env
*.log

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 adadrag
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

99
README.md Normal file
View File

@@ -0,0 +1,99 @@
# OpenClaw Proxmox LXC Setup
Automated setup script that deploys an [OpenClaw](https://openclaw.ai/) AI assistant inside a Proxmox LXC container, complete with a remote desktop (noVNC) and Chromium browser.
## What it does
A single Bash script that runs on your **Proxmox host** and:
1. Creates a Debian 13 unprivileged LXC container
2. Installs Node.js 22 and OpenClaw
3. Installs XFCE4 desktop with TigerVNC and noVNC
4. Installs Chromium (patched for LXC `--no-sandbox`)
5. Configures systemd services for everything (auto-start on boot)
6. Prints connection URLs when done
## Quick Start
```bash
# On your Proxmox host:
wget -O setup-openclaw-lxc.sh https://raw.githubusercontent.com/adadrag/Openclaw-Proxmox/main/setup-openclaw-lxc.sh
chmod +x setup-openclaw-lxc.sh
bash setup-openclaw-lxc.sh
```
## Requirements
- Proxmox VE 8.x+
- Root access on the Proxmox host
- Internet connectivity (for downloading packages)
- Available storage for container rootfs
## Configuration Options
The script prompts for the following (with defaults):
| Option | Default | Description |
|--------|---------|-------------|
| Password | *(required)* | Container root password (also used for VNC) |
| Disk size | 32 GB | Container root filesystem size |
| Memory | 4096 MB | Container RAM allocation |
| CPU cores | 4 | Number of CPU cores |
| VNC resolution | 1920x1080 | Remote desktop resolution |
Everything else is auto-detected:
- **VMID** — next available ID in the cluster
- **Storage** — auto-selects template and rootfs storage
- **Networking** — DHCP on `vmbr0`
## Services
The script creates and enables three systemd services inside the container:
| Service | Port | Description |
|---------|------|-------------|
| `openclaw-gateway` | 18789 | OpenClaw AI gateway (token auth) |
| `vncserver` | 5901 | TigerVNC server (localhost only) |
| `novnc` | 6080 | noVNC web proxy (exposes VNC via browser) |
## Access Points
After setup, the script prints all connection details:
- **OpenClaw Dashboard**: `http://<container-ip>:18789/`
- **Remote Desktop (noVNC)**: `http://<container-ip>:6080/vnc.html`
- **SSH**: `ssh root@<container-ip>`
## Container Management
```bash
pct enter <VMID> # Shell into the container
pct stop <VMID> # Stop the container
pct start <VMID> # Start the container
pct status <VMID> # Check container status
```
## Troubleshooting
**Service not starting?**
```bash
pct exec <VMID> -- systemctl status openclaw-gateway
pct exec <VMID> -- systemctl status vncserver
pct exec <VMID> -- systemctl status novnc
```
**Chromium won't launch?**
Chromium needs `--no-sandbox` inside LXC containers. The script patches this automatically, but if you installed Chromium separately, run:
```bash
sed -i 's|Exec=/usr/bin/chromium|Exec=/usr/bin/chromium --no-sandbox|g' /usr/share/applications/chromium.desktop
```
**"Unable to contact settings server" in XFCE?**
This means `dbus-x11` is missing. The script installs it, but if you see this error:
```bash
apt-get install -y dbus-x11
```
## License
MIT License. See [LICENSE](LICENSE) for details.

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