From c67d3785213c1aac3bb1180c6714569e7b36c6a8 Mon Sep 17 00:00:00 2001 From: adadrag Date: Thu, 12 Mar 2026 15:46:58 +0000 Subject: [PATCH] Initial commit: OpenClaw Proxmox LXC setup script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .gitignore | 14 ++ LICENSE | 21 +++ README.md | 99 +++++++++++++ setup-openclaw-lxc.sh | 334 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 468 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 setup-openclaw-lxc.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e35eef --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# OS +.DS_Store +Thumbs.db + +# Editor +*.swp +*.swo +*~ +.vscode/ +.idea/ + +# Env +.env +*.log diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9247c96 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..26555e7 --- /dev/null +++ b/README.md @@ -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://:18789/` +- **Remote Desktop (noVNC)**: `http://:6080/vnc.html` +- **SSH**: `ssh root@` + +## Container Management + +```bash +pct enter # Shell into the container +pct stop # Stop the container +pct start # Start the container +pct status # Check container status +``` + +## Troubleshooting + +**Service not starting?** +```bash +pct exec -- systemctl status openclaw-gateway +pct exec -- systemctl status vncserver +pct exec -- 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. diff --git a/setup-openclaw-lxc.sh b/setup-openclaw-lxc.sh new file mode 100644 index 0000000..fb351ac --- /dev/null +++ b/setup-openclaw-lxc.sh @@ -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