- Fix Homebrew installation: install sudo, set sudoers permissions, and add transparent root wrapper - Add Debian 12 fallback when Debian 13 template is unavailable - Ensure proper OpenClaw token authentication configuration - Dynamically resolve binary paths in systemd services - Configure SSH root password login - Clean stale VNC locks on startup and prevent screen-blanking - Add --skip-daemon flag to onboarding shortcut to avoid service conflict - Enable nesting and onboot autostart for container - Prompt for VMID and storage pools with auto-detected defaults
597 lines
23 KiB
Bash
597 lines
23 KiB
Bash
#!/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 as root.
|
|
# It creates a Debian 13/12 LXC, installs OpenClaw + LXQt desktop + Google Chrome + 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; }
|
|
|
|
# Trap unexpected errors to give actionable advice
|
|
trap 'err "An error occurred on line $LINENO. Container VMID: ${VMID:-none}. To clean up: pct destroy ${VMID:-<vmid>}"' ERR
|
|
|
|
# ─── 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."
|
|
[[ $(uname -m) == "x86_64" ]] || fatal "This script currently supports x86_64 (amd64) architecture only."
|
|
|
|
# ─── Auto-detection ──────────────────────────────────────────────────────────
|
|
info "Detecting next available VMID and storage pools..."
|
|
|
|
# Detect next available VMID
|
|
DETECTED_VMID=$(pvesh get /cluster/nextid 2>/dev/null || true)
|
|
if [[ -z "$DETECTED_VMID" || ! "$DETECTED_VMID" =~ ^[0-9]+$ ]]; then
|
|
DETECTED_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)
|
|
fi
|
|
|
|
# Find template storage (enabled and supports vztmpl content)
|
|
DETECTED_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:
|
|
if s.get("disable", 0) != 1 and s.get("enabled", 1) == 1:
|
|
if "vztmpl" in s.get("content", ""):
|
|
print(s["storage"])
|
|
break
|
|
' 2>/dev/null || echo "local")
|
|
|
|
# Find rootdir storage (prefer local-lvm, local-zfs, local-btrfs, then any with rootdir/images)
|
|
DETECTED_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:
|
|
if s.get("disable", 0) != 1 and s.get("enabled", 1) == 1:
|
|
content = s.get("content", "")
|
|
if "rootdir" in content or "images" in content:
|
|
candidates.append(s["storage"])
|
|
for preferred in ["local-lvm", "local-zfs", "local-btrfs"]:
|
|
if preferred in candidates:
|
|
print(preferred)
|
|
sys.exit(0)
|
|
if candidates:
|
|
print(candidates[0])
|
|
else:
|
|
print("local-lvm")
|
|
' 2>/dev/null || echo "local-lvm")
|
|
|
|
# ─── User prompts ────────────────────────────────────────────────────────────
|
|
echo -e "${BOLD}╔═══════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${BOLD}║ OpenClaw LXC Setup for Proxmox ║${NC}"
|
|
echo -e "${BOLD}╚═══════════════════════════════════════════════════╝${NC}"
|
|
echo
|
|
|
|
read -rp "Container VMID [${DETECTED_VMID}]: " VMID_INPUT
|
|
VMID="${VMID_INPUT:-$DETECTED_VMID}"
|
|
[[ "$VMID" =~ ^[0-9]+$ ]] || fatal "VMID must be an integer."
|
|
|
|
if pct status "$VMID" >/dev/null 2>&1 || qm status "$VMID" >/dev/null 2>&1; then
|
|
fatal "VMID $VMID is already in use by another container or VM!"
|
|
fi
|
|
|
|
read -rp "Container password: " -s CT_PASSWORD
|
|
echo
|
|
[[ -n "$CT_PASSWORD" ]] || fatal "Password cannot be empty."
|
|
|
|
read -rp "Rootfs storage [${DETECTED_ROOT_STORAGE}]: " ROOT_STORAGE_INPUT
|
|
ROOT_STORAGE="${ROOT_STORAGE_INPUT:-$DETECTED_ROOT_STORAGE}"
|
|
|
|
read -rp "Template storage [${DETECTED_TMPL_STORAGE}]: " TMPL_STORAGE_INPUT
|
|
TMPL_STORAGE="${TMPL_STORAGE_INPUT:-$DETECTED_TMPL_STORAGE}"
|
|
|
|
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
|
|
ok "Will create container $VMID on storage $ROOT_STORAGE (template on $TMPL_STORAGE)"
|
|
|
|
# ─── Download Debian template (Debian 13 or 12) ───────────────────────────────
|
|
info "Checking for Debian template..."
|
|
pveam update >/dev/null 2>&1 || true
|
|
|
|
# Check if an existing Debian 13 or 12 template is already downloaded on storage
|
|
TEMPLATE=$(pveam list "$TMPL_STORAGE" 2>/dev/null | grep -oP 'debian-(13|12)-standard_\S+' | head -1 || true)
|
|
|
|
if [[ -n "$TEMPLATE" ]]; then
|
|
ok "Found locally downloaded template: $TEMPLATE"
|
|
else
|
|
# Search available templates, prioritizing Debian 13 then Debian 12
|
|
TEMPLATE=$(pveam available --section system 2>/dev/null | grep -oP 'debian-13-standard_\S+' | head -1 || true)
|
|
if [[ -z "$TEMPLATE" ]]; then
|
|
TEMPLATE=$(pveam available --section system 2>/dev/null | grep -oP 'debian-12-standard_\S+' | head -1 || true)
|
|
fi
|
|
if [[ -z "$TEMPLATE" ]]; then
|
|
TEMPLATE=$(pveam available 2>/dev/null | grep -oP 'debian-(13|12)-standard_\S+' | head -1 || true)
|
|
fi
|
|
[[ -n "$TEMPLATE" ]] || fatal "Could not find Debian 13 or Debian 12 template in 'pveam available'."
|
|
|
|
info "Downloading $TEMPLATE to $TMPL_STORAGE..."
|
|
pveam download "$TMPL_STORAGE" "$TEMPLATE"
|
|
ok "Template downloaded: $TEMPLATE"
|
|
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 0 \
|
|
--features nesting=1 \
|
|
--onboot 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" | grep -oP '\b(?:\d{1,3}\.){3}\d{1,3}\b' | grep -v '^127\.' | head -n1 || true)
|
|
if [[ -n "$CT_IP" ]]; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
[[ -n "$CT_IP" ]] || warn "Could not detect container IPv4 — continuing anyway."
|
|
ok "Container IP: ${CT_IP:-unknown}"
|
|
|
|
info "Verifying internet connectivity inside container..."
|
|
for i in $(seq 1 15); do
|
|
if ct_exec "curl -s --connect-timeout 2 https://deb.debian.org >/dev/null 2>&1 || ping -c 1 -W 2 1.1.1.1 >/dev/null 2>&1"; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# ─── Install packages inside the container ────────────────────────────────────
|
|
info "Updating packages and installing prerequisites..."
|
|
ct_exec "
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
# Fix locale warnings
|
|
apt-get update && apt-get install -y locales 2>&1 | grep -v 'Failed to write'
|
|
sed -i 's/^# *en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen
|
|
locale-gen en_US.UTF-8 >/dev/null 2>&1
|
|
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
|
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
|
|
|
apt-get upgrade -y 2>&1 | grep -v 'Failed to write'
|
|
apt-get install -y curl ca-certificates gnupg git sudo openssh-server 2>&1 | grep -v 'Failed to write'
|
|
"
|
|
ok "Prerequisites installed."
|
|
|
|
# ─── Configure SSH for root access ───────────────────────────────────────────
|
|
info "Configuring SSH for root password login..."
|
|
ct_exec "
|
|
mkdir -p /etc/ssh/sshd_config.d
|
|
echo 'PermitRootLogin yes' > /etc/ssh/sshd_config.d/permit-root-login.conf
|
|
systemctl restart ssh 2>/dev/null || systemctl restart sshd 2>/dev/null || true
|
|
"
|
|
ok "SSH configured."
|
|
|
|
info "Installing Node.js 22..."
|
|
ct_exec "
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - 2>&1 | tail -3
|
|
apt-get install -y nodejs 2>&1 | grep -v 'Failed to write'
|
|
"
|
|
ok "Node.js installed."
|
|
|
|
info "Installing Homebrew (required for OpenClaw skills)..."
|
|
ct_exec "
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get install -y build-essential procps file sudo 2>&1 | grep -v -E 'Failed to write|Permission denied'
|
|
|
|
# Create dedicated brewuser and configure passwordless sudo
|
|
useradd -m -s /bin/bash brewuser 2>/dev/null || true
|
|
mkdir -p /etc/sudoers.d
|
|
echo 'brewuser ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/brewuser
|
|
chmod 0440 /etc/sudoers.d/brewuser
|
|
|
|
# Pre-create /home/linuxbrew with correct ownership
|
|
mkdir -p /home/linuxbrew/.linuxbrew
|
|
chown -R brewuser:brewuser /home/linuxbrew
|
|
|
|
# Install Homebrew as brewuser
|
|
su - brewuser -c 'NONINTERACTIVE=1 /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"' 2>&1 | tail -5
|
|
|
|
# Configure brew environment for brewuser
|
|
echo 'eval \"\$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)\"' >> /home/brewuser/.bashrc
|
|
echo 'eval \"\$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)\"' >> /home/brewuser/.profile
|
|
|
|
# Configure brew environment for root
|
|
echo '[ -x /home/linuxbrew/.linuxbrew/bin/brew ] && eval \"\$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)\"' >> /root/.bashrc
|
|
|
|
# Safe wrapper so root and OpenClaw can seamlessly execute brew without root errors
|
|
cat > /usr/local/bin/brew << 'BREWWRAPPER'
|
|
#!/bin/bash
|
|
if [[ \$(id -u) -eq 0 ]]; then
|
|
exec sudo -u brewuser -i /home/linuxbrew/.linuxbrew/bin/brew \"\$@\"
|
|
else
|
|
exec /home/linuxbrew/.linuxbrew/bin/brew \"\$@\"
|
|
fi
|
|
BREWWRAPPER
|
|
chmod +x /usr/local/bin/brew
|
|
"
|
|
ok "Homebrew installed."
|
|
|
|
info "Installing OpenClaw..."
|
|
ct_exec "npm install -g openclaw@latest 2>&1 | tail -5"
|
|
ok "OpenClaw installed."
|
|
|
|
info "Installing LXQt, TigerVNC, noVNC (this takes a few minutes)..."
|
|
ct_exec "
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
|
apt-get install -y lxqt openbox lxterminal tigervnc-standalone-server tigervnc-common novnc websockify dbus-x11 fonts-noto-color-emoji x11-xserver-utils 2>&1 \
|
|
| grep -v -E 'Failed to write|Failed to send reload|Permission denied|Cannot set LC_'
|
|
"
|
|
ok "Desktop environment installed."
|
|
|
|
info "Installing Google Chrome..."
|
|
ct_exec "
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
curl -fsSL https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -o /tmp/chrome.deb
|
|
apt-get install -y /tmp/chrome.deb 2>&1 | grep -v -E 'Failed to write|Permission denied'
|
|
rm -f /tmp/chrome.deb
|
|
"
|
|
ok "Google Chrome 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.mode token
|
|
openclaw config set gateway.auth.token '${AUTH_TOKEN}'
|
|
"
|
|
ok "OpenClaw configured (token: $AUTH_TOKEN)."
|
|
|
|
info "Installing OpenClaw browser extension..."
|
|
ct_exec "openclaw browser extension install 2>&1 | tail -5 || true"
|
|
EXTENSION_DIR=$(ct_exec "openclaw browser extension path 2>/dev/null" || echo "/root/.openclaw/browser/extension")
|
|
ok "OpenClaw browser extension installed."
|
|
|
|
# ─── Configure VNC ───────────────────────────────────────────────────────────
|
|
info "Configuring VNC..."
|
|
|
|
# Safely set VNC password via stdin to prevent shell escape issues with special characters
|
|
printf '%s\n' "$CT_PASSWORD" | pct exec "$VMID" -- sh -c '
|
|
mkdir -p /root/.config/tigervnc /root/.vnc /etc/tigervnc
|
|
vncpasswd -f > /root/.config/tigervnc/passwd
|
|
chmod 600 /root/.config/tigervnc/passwd
|
|
ln -sf /root/.config/tigervnc/passwd /root/.vnc/passwd
|
|
echo ":1=root" > /etc/tigervnc/vncserver.users
|
|
'
|
|
|
|
ct_exec "cat > /root/.config/tigervnc/xstartup << 'XSTARTUP'
|
|
#!/bin/bash
|
|
unset SESSION_MANAGER
|
|
unset DBUS_SESSION_BUS_ADDRESS
|
|
xset s off
|
|
xset s noblank
|
|
xset -dpms
|
|
exec dbus-launch --exit-with-session startlxqt
|
|
XSTARTUP
|
|
chmod +x /root/.config/tigervnc/xstartup
|
|
ln -sf /root/.config/tigervnc/xstartup /root/.vnc/xstartup"
|
|
|
|
# Configure lxterminal with dark theme
|
|
ct_exec "
|
|
mkdir -p /root/.config/lxterminal
|
|
cat > /root/.config/lxterminal/lxterminal.conf << 'CONF'
|
|
[general]
|
|
fontname=Monospace 12
|
|
bgcolor=#1e1e2e
|
|
fgcolor=#cdd6f4
|
|
palette_color_0=#45475a
|
|
palette_color_1=#f38ba8
|
|
palette_color_2=#a6e3a1
|
|
palette_color_3=#f9e2af
|
|
palette_color_4=#89b4fa
|
|
palette_color_5=#f5c2e7
|
|
palette_color_6=#94e2d5
|
|
palette_color_7=#bac2de
|
|
palette_color_8=#585b70
|
|
palette_color_9=#f38ba8
|
|
palette_color_10=#a6e3a1
|
|
palette_color_11=#f9e2af
|
|
palette_color_12=#89b4fa
|
|
palette_color_13=#f5c2e7
|
|
palette_color_14=#94e2d5
|
|
palette_color_15=#a6adc8
|
|
scrollback=10000
|
|
CONF
|
|
"
|
|
|
|
# Set noVNC scaling to auto by default
|
|
ct_exec "sed -i \"s/UI.initSetting('resize', 'off')/UI.initSetting('resize', 'scale')/\" /usr/share/novnc/app/ui.js"
|
|
ok "VNC configured (auto-scaling enabled)."
|
|
|
|
# ─── Disable LXC-incompatible LXQt components ───────────────────────────────
|
|
info "Disabling LXC-incompatible LXQt components..."
|
|
ct_exec "
|
|
mkdir -p /root/.config/autostart
|
|
# Disable power management (no hardware in LXC)
|
|
cat > /root/.config/autostart/lxqt-powermanagement.desktop << 'NOAUTO'
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=LXQt Power Management
|
|
Hidden=true
|
|
NOAUTO
|
|
# Disable screen saver (not needed in VNC)
|
|
cat > /root/.config/autostart/lxqt-xscreensaver-autostart.desktop << 'NOAUTO'
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=LXQt Screen Saver
|
|
Hidden=true
|
|
NOAUTO
|
|
"
|
|
ok "LXC-incompatible components disabled."
|
|
|
|
# ─── Configure Google Chrome for root user ───────────────────────────────────
|
|
info "Configuring Google Chrome..."
|
|
ct_exec "
|
|
# Chrome requires --no-sandbox when running as root
|
|
sed -i 's|Exec=/usr/bin/google-chrome-stable|Exec=/usr/bin/google-chrome-stable --no-sandbox|g' /usr/share/applications/google-chrome.desktop
|
|
|
|
mkdir -p /root/Desktop
|
|
cp /usr/share/applications/google-chrome.desktop /root/Desktop/
|
|
chmod +x /root/Desktop/google-chrome.desktop
|
|
|
|
# Wrapper so CLI calls also get --no-sandbox
|
|
cat > /usr/local/bin/google-chrome << 'WRAPPER'
|
|
#!/bin/bash
|
|
exec /usr/bin/google-chrome-stable --no-sandbox \"\$@\"
|
|
WRAPPER
|
|
chmod +x /usr/local/bin/google-chrome
|
|
ln -sf /usr/local/bin/google-chrome /usr/local/bin/google-chrome-stable
|
|
|
|
update-alternatives --install /usr/bin/x-www-browser x-www-browser /usr/local/bin/google-chrome 200 2>/dev/null || true
|
|
update-alternatives --set x-www-browser /usr/local/bin/google-chrome 2>/dev/null || true
|
|
update-alternatives --set gnome-www-browser /usr/local/bin/google-chrome 2>/dev/null || true
|
|
xdg-mime default google-chrome.desktop x-scheme-handler/http 2>/dev/null || true
|
|
xdg-mime default google-chrome.desktop x-scheme-handler/https 2>/dev/null || true
|
|
xdg-mime default google-chrome.desktop text/html 2>/dev/null || true
|
|
"
|
|
ok "Google Chrome configured."
|
|
|
|
# ─── Configure LXQt default terminal ─────────────────────────────────────────
|
|
info "Setting lxterminal as default terminal..."
|
|
ct_exec "
|
|
mkdir -p /root/.config/lxqt
|
|
cat > /root/.config/lxqt/session.conf << 'CONF'
|
|
[General]
|
|
__userfile__=true
|
|
|
|
[Environment]
|
|
TERM=xterm-256color
|
|
|
|
[Preferred Applications]
|
|
terminal_emulator=lxterminal
|
|
CONF
|
|
"
|
|
ok "Default terminal configured."
|
|
|
|
# ─── Create desktop shortcuts ────────────────────────────────────────────────
|
|
info "Creating desktop shortcuts..."
|
|
|
|
# Terminal shortcut
|
|
ct_exec "cat > /root/Desktop/terminal.desktop << 'SHORTCUT'
|
|
[Desktop Entry]
|
|
Version=1.0
|
|
Type=Application
|
|
Name=Terminal
|
|
Comment=Open a terminal
|
|
Exec=lxterminal
|
|
Icon=utilities-terminal
|
|
Terminal=false
|
|
Categories=System;TerminalEmulator;
|
|
StartupNotify=true
|
|
SHORTCUT
|
|
chmod +x /root/Desktop/terminal.desktop"
|
|
|
|
# OpenClaw Onboarding wizard (runs in terminal with --skip-daemon)
|
|
ct_exec "cat > /root/Desktop/openclaw-onboard.desktop << 'SHORTCUT'
|
|
[Desktop Entry]
|
|
Version=1.0
|
|
Type=Application
|
|
Name=OpenClaw Setup Wizard
|
|
Comment=Run the OpenClaw onboarding wizard to configure your AI assistant
|
|
Exec=lxterminal --title='OpenClaw Onboarding' -e sh -c 'openclaw onboard --skip-daemon; exec bash'
|
|
Icon=utilities-terminal
|
|
Terminal=false
|
|
Categories=Utility;
|
|
StartupNotify=true
|
|
SHORTCUT
|
|
chmod +x /root/Desktop/openclaw-onboard.desktop"
|
|
|
|
# OpenClaw Dashboard (opens in Chrome with token pre-filled)
|
|
ct_exec "cat > /root/Desktop/openclaw-dashboard.desktop << SHORTCUT
|
|
[Desktop Entry]
|
|
Version=1.0
|
|
Type=Application
|
|
Name=OpenClaw Dashboard
|
|
Comment=Open the OpenClaw Control UI in Google Chrome
|
|
Exec=google-chrome-stable --no-sandbox http://127.0.0.1:18789/#token=${AUTH_TOKEN}
|
|
Icon=web-browser
|
|
Terminal=false
|
|
Categories=Network;WebBrowser;
|
|
StartupNotify=true
|
|
SHORTCUT
|
|
chmod +x /root/Desktop/openclaw-dashboard.desktop"
|
|
|
|
ok "Desktop shortcuts created."
|
|
|
|
# ─── Create systemd services ─────────────────────────────────────────────────
|
|
info "Creating systemd services..."
|
|
|
|
OPENCLAW_BIN=$(ct_exec "command -v openclaw" || echo "/usr/bin/openclaw")
|
|
WEBSOCKIFY_BIN=$(ct_exec "command -v websockify" || echo "/usr/bin/websockify")
|
|
VNC_BIN=$(ct_exec "command -v vncserver" || echo "/usr/bin/vncserver")
|
|
|
|
ct_exec "cat > /etc/systemd/system/openclaw-gateway.service << SVC
|
|
[Unit]
|
|
Description=OpenClaw Gateway
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=${OPENCLAW_BIN} gateway run --bind lan --auth token --token ${AUTH_TOKEN}
|
|
Restart=always
|
|
RestartSec=5
|
|
Environment=HOME=/root
|
|
Environment=NODE_ENV=production
|
|
Environment=OPENCLAW_GATEWAY_TOKEN=${AUTH_TOKEN}
|
|
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
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
|
|
Environment=HOME=/root
|
|
ExecStartPre=/bin/sh -c \"${VNC_BIN} -kill :1 >/dev/null 2>&1 || :; rm -f /tmp/.X1-lock /tmp/.X11-unix/X1; mkdir -p /tmp/.X11-unix; chmod 1777 /tmp/.X11-unix\"
|
|
ExecStart=${VNC_BIN} :1 -geometry ${VNC_RES} -depth 24 -localhost yes
|
|
ExecStop=${VNC_BIN} -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=${WEBSOCKIFY_BIN} --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" | grep -oP '\b(?:\d{1,3}\.){3}\d{1,3}\b' | grep -v '^127\.' | head -n1 || 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 (Direct Link):${NC}"
|
|
echo -e " ${CYAN}http://${CT_IP}:18789/#token=${AUTH_TOKEN}${NC}"
|
|
echo -e " Auth token: ${AUTH_TOKEN}"
|
|
echo
|
|
echo -e " ${BOLD}noVNC Remote Desktop (Auto-connect):${NC}"
|
|
echo -e " ${CYAN}http://${CT_IP}:6080/vnc.html?autoconnect=true&resize=scale${NC}"
|
|
if [[ ${#CT_PASSWORD} -gt 8 ]]; then
|
|
echo -e " ${YELLOW}Note on VNC password:${NC} VNC protocol uses first 8 characters: ${BOLD}${CT_PASSWORD:0:8}${NC}"
|
|
else
|
|
echo -e " VNC password: (same as container password)"
|
|
fi
|
|
echo
|
|
echo -e " ${BOLD}Browser Extension:${NC}"
|
|
echo -e " Directory: ${CYAN}${EXTENSION_DIR}${NC}"
|
|
echo -e " Open Chrome → chrome://extensions → Enable Developer Mode"
|
|
echo -e " Click 'Load unpacked' and select the directory above"
|
|
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
|