diff --git a/CLAUDE.md b/CLAUDE.md index 137126b..4daef80 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,28 +8,27 @@ OpenClaw on Proxmox is a single-script deployment tool (`setup-openclaw-lxc.sh`) ## Architecture -The entire project is one ~460-line Bash script with this flow: +The entire project is one Bash script with this flow: -1. **Pre-flight** — Verify Proxmox CLI tools (`pct`, `pvesh`, `pveam`) and root access -2. **User prompts** — Collect container password, disk/memory/CPU/resolution settings -3. **Auto-detection** — Find next VMID and storage backends via `pvesh` JSON + Python parsing -4. **Template management** — Download Debian 13 template if missing -5. **Container creation** — Privileged LXC, DHCP networking -6. **Package installation** — Node.js 22, Homebrew (as non-root `brewuser`), OpenClaw (npm global), LXQt, TigerVNC, noVNC, Google Chrome, OpenClaw browser extension -7. **Configuration** — OpenClaw gateway settings, VNC password/xstartup, noVNC auto-scaling patch, Chrome as default browser -8. **Desktop integration** — Desktop shortcuts for onboarding wizard and dashboard -9. **Systemd services** — Three auto-starting services: `openclaw-gateway` (port 18789), `vncserver` (:1/5901), `novnc` (port 6080) -10. **Verification** — Check all services are active, print connection info +1. **Pre-flight** — Verify Proxmox CLI tools (`pct`, `pvesh`, `pveam`), root access, and x86_64 architecture +2. **Auto-detection & User prompts** — Detect next VMID and available storages, prompt user with smart defaults +3. **Template management** — Check local storage, download Debian 13 or fallback to Debian 12 template +4. **Container creation** — Privileged LXC with nesting enabled, DHCP networking, onboot autostart +5. **Package installation** — Node.js 22, Homebrew (as non-root `brewuser`), OpenClaw (npm global), LXQt, TigerVNC, noVNC, Google Chrome, OpenClaw browser extension, OpenSSH server +6. **Configuration** — OpenClaw gateway settings (`gateway.auth.mode token`), VNC password/xstartup (no blanking), noVNC auto-scaling patch, Chrome as default browser (`--no-sandbox` wrapper), SSH root login +7. **Desktop integration** — Desktop shortcuts for onboarding wizard (`--skip-daemon`) and dashboard (pre-filled token) +8. **Systemd services** — Three auto-starting services with dynamically detected binaries: `openclaw-gateway` (port 18789), `vncserver` (:1/5901), `novnc` (port 6080) +9. **Verification** — Check all services are active, print connection info (direct one-click URLs) Key helper: `ct_exec()` wraps `pct exec $VMID -- bash -c` for all in-container commands. ## Development Notes - No build system, test suite, or linter — this is a standalone Bash script -- Uses `set -euo pipefail` for strict error handling +- Uses `set -euo pipefail` and ERR trap for strict error handling and cleanup tips - All in-container operations use heredoc-embedded config files (systemd units, desktop files, wrapper scripts) - Colorized output via `info()`, `ok()`, `warn()`, `err()`, `fatal()` helper functions -- Homebrew is installed under a dedicated `brewuser` account (non-root requirement) with a symlink at `/usr/local/bin/brew` for root access +- Homebrew is installed under a dedicated `brewuser` account (non-root requirement) with passwordless sudo (`0440` permission) and a transparent wrapper script at `/usr/local/bin/brew` so root and OpenClaw can run `brew` commands - The noVNC auto-scaling patch modifies `/usr/share/novnc/app/ui.js` in-place via `sed` - Auth token is generated with `openssl rand -hex 16` diff --git a/README.md b/README.md index eb55273..439ec46 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,10 @@ After the script finishes, you'll have a fully configured LXC container with: | What | How to access | |------|---------------| | **OpenClaw Dashboard** | `http://:18789/#token=` | -| **Remote Desktop** | `http://:6080/vnc.html` | +| **Remote Desktop** | `http://:6080/vnc.html?autoconnect=true&resize=scale` | | **SSH** | `ssh root@` | -The script prints all of this (including the token) at the end. +The script prints all of this (including the direct token link) at the end. ## Desktop Shortcuts @@ -58,38 +58,41 @@ Once you open the remote desktop via noVNC, you'll find two shortcuts ready to g | Shortcut | What it does | |----------|-------------| -| **OpenClaw Setup Wizard** | Opens the interactive onboarding wizard (`openclaw onboard`) to configure your channels, workspace, and skills | +| **OpenClaw Setup Wizard** | Opens the interactive onboarding wizard (`openclaw onboard --skip-daemon`) to configure your channels, workspace, and skills | | **OpenClaw Dashboard** | Opens Chrome with the dashboard URL and auth token pre-filled — no manual token entry needed | ## Requirements -- Proxmox VE 8.x+ +- Proxmox VE 8.x+ (x86_64 / amd64) - Root access on the Proxmox host - Internet connectivity (for downloading packages) ## Configuration -The script prompts you interactively (all optional except password): +The script prompts you interactively (all optional with smart auto-detection except password): | Option | Default | Description | |--------|---------|-------------| -| Password | *(required)* | Container root password (also used for VNC) | +| VMID | *(next free)* | Container ID (auto-detected, customizable) | +| Password | *(required)* | Container root password (VNC uses first 8 characters) | +| Rootfs storage | *(auto-detected)* | Storage pool for container rootfs (`local-lvm`, `local-zfs`, etc.) | +| Template storage | *(auto-detected)* | Storage pool for template cache (`local`, etc.) | | Disk size | 32 GB | Container root filesystem size | | Memory | 4096 MB | RAM allocation | | CPU cores | 4 | Number of CPU cores | | VNC resolution | 1920x1080 | Remote desktop resolution | -Everything else is auto-detected — VMID, storage, networking (DHCP). +Everything else is auto-detected — networking (DHCP bridge `vmbr0`), nesting, and onboot autostart. ## What the script installs All inside the container (nothing is installed on the Proxmox host): -- **Debian 13** LXC (privileged) +- **Debian 13 or 12** LXC (privileged, nested, onboot) - **Node.js 22** + **OpenClaw** -- **Homebrew** (for OpenClaw skills) -- **LXQt** desktop + **TigerVNC** + **noVNC** -- **Google Chrome** (with OpenClaw browser extension) +- **Homebrew** (preconfigured with non-root user + transparent root wrapper for OpenClaw skills) +- **LXQt** desktop + **TigerVNC** + **noVNC** (with auto-scaling & no screen-blanking) +- **Google Chrome** (configured for root with `--no-sandbox` & OpenClaw browser extension) - **Noto Color Emoji** font (for proper terminal rendering) - Three **systemd services**: `openclaw-gateway`, `vncserver`, `novnc` @@ -104,11 +107,10 @@ pct status # Check container status ## Notes on the onboarding wizard -When you run the **OpenClaw Setup Wizard** from the desktop shortcut, you may see two messages that look like errors but are perfectly normal: +When you run the **OpenClaw Setup Wizard** from the desktop shortcut: -**"Gateway service install failed"** — The wizard tries to create its own systemd service for the gateway, but our script already set up `openclaw-gateway.service` and it's already running. This is safe to ignore. - -**"SECURITY ERROR: ws:// to a non-loopback address"** — This is a warning that the gateway uses unencrypted WebSocket over LAN. This is expected for a local setup. If you're on a trusted home or office network, it's completely fine. +- The shortcut automatically includes `--skip-daemon` so the wizard won't attempt to reinstall or overwrite the existing systemd service. +- **"SECURITY ERROR: ws:// to a non-loopback address"** — This is a standard warning that the gateway uses unencrypted WebSocket over LAN. This is expected for a local setup. If you're on a trusted home or office network, it's completely fine. As long as you see your **channels connected**, a **Control UI URL**, and an **agent workspace** listed at the end of the wizard, everything worked. diff --git a/setup-openclaw-lxc.sh b/setup-openclaw-lxc.sh index 23994ca..6ab4c0a 100644 --- a/setup-openclaw-lxc.sh +++ b/setup-openclaw-lxc.sh @@ -2,8 +2,8 @@ # # 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 + LXQt desktop + Google Chrome + VNC/noVNC, +# 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. # @@ -23,11 +23,63 @@ 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:-}"' 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}" @@ -35,10 +87,24 @@ 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}" @@ -52,70 +118,31 @@ 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)" -# ─── 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..." +# ─── Download Debian template (Debian 13 or 12) ─────────────────────────────── +info "Checking for Debian 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'." +# 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 pveam list "$TMPL_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then - ok "Template already downloaded: $TEMPLATE" +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." + ok "Template downloaded: $TEMPLATE" fi # ─── Create the LXC container ──────────────────────────────────────────────── @@ -128,6 +155,8 @@ pct create "$VMID" "${TMPL_STORAGE}:vztmpl/${TEMPLATE}" \ --cores "$CORES" \ --net0 name=eth0,bridge=vmbr0,ip=dhcp \ --unprivileged 0 \ + --features nesting=1 \ + --onboot 1 \ --start 0 ok "Container $VMID created." @@ -147,16 +176,23 @@ ct_exec() { 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 + 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 - CT_IP="" sleep 1 done -[[ -n "$CT_IP" ]] || warn "Could not detect container IP — continuing anyway." +[[ -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 " @@ -170,10 +206,19 @@ ct_exec " 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 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 @@ -185,18 +230,38 @@ 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 2>&1 | grep -v -E 'Failed to write|Permission denied' + apt-get install -y build-essential procps file sudo 2>&1 | grep -v -E 'Failed to write|Permission denied' - # Homebrew must be installed as a non-root user + # 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 - # Make brew available system-wide for root + # 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 - ln -sf /home/linuxbrew/.linuxbrew/bin/brew /usr/local/bin/brew + + # 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." @@ -208,7 +273,7 @@ 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 novnc websockify dbus-x11 fonts-noto-color-emoji 2>&1 \ + 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." @@ -230,29 +295,39 @@ 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..." -ct_exec " - mkdir -p /root/.config/tigervnc - echo '${CT_PASSWORD}' | vncpasswd -f > /root/.config/tigervnc/passwd + +# 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" +chmod +x /root/.config/tigervnc/xstartup +ln -sf /root/.config/tigervnc/xstartup /root/.vnc/xstartup" # Configure lxterminal with dark theme ct_exec " @@ -323,9 +398,11 @@ ct_exec " 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 --set x-www-browser /usr/bin/google-chrome-stable 2>/dev/null || true - update-alternatives --set gnome-www-browser /usr/bin/google-chrome-stable 2>/dev/null || true + 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 @@ -367,14 +444,14 @@ StartupNotify=true SHORTCUT chmod +x /root/Desktop/terminal.desktop" -# OpenClaw Onboarding wizard (runs in terminal) +# 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; exec bash' +Exec=lxterminal --title='OpenClaw Onboarding' -e sh -c 'openclaw onboard --skip-daemon; exec bash' Icon=utilities-terminal Terminal=false Categories=Utility; @@ -382,7 +459,7 @@ StartupNotify=true SHORTCUT chmod +x /root/Desktop/openclaw-onboard.desktop" -# OpenClaw Dashboard (opens in Chrome with token) +# OpenClaw Dashboard (opens in Chrome with token pre-filled) ct_exec "cat > /root/Desktop/openclaw-dashboard.desktop << SHORTCUT [Desktop Entry] Version=1.0 @@ -402,17 +479,24 @@ ok "Desktop shortcuts created." # ─── Create systemd services ───────────────────────────────────────────────── info "Creating systemd services..." -ct_exec "cat > /etc/systemd/system/openclaw-gateway.service << 'SVC' +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=/bin/openclaw gateway run --bind lan --auth token +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] @@ -427,9 +511,9 @@ After=network.target [Service] Type=forking Environment=HOME=/root -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 +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 @@ -437,7 +521,7 @@ RestartSec=5 WantedBy=multi-user.target SVC" -ct_exec "cat > /etc/systemd/system/novnc.service << 'SVC' +ct_exec "cat > /etc/systemd/system/novnc.service << SVC [Unit] Description=noVNC WebSocket Proxy After=vncserver.service @@ -445,7 +529,7 @@ Requires=vncserver.service [Service] Type=simple -ExecStart=/bin/websockify --web=/usr/share/novnc/ 6080 localhost:5901 +ExecStart=${WEBSOCKIFY_BIN} --web=/usr/share/novnc/ 6080 localhost:5901 Restart=on-failure RestartSec=5 @@ -474,7 +558,7 @@ for svc in openclaw-gateway vncserver novnc; do done # Re-detect IP in case it changed -CT_IP=$(ct_exec "hostname -I 2>/dev/null" | awk '{print $1}' || echo "unknown") +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 @@ -485,17 +569,22 @@ 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 -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):${NC}" -echo -e " http://${CT_IP}:6080/vnc.html" +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 OpenClaw extension directory" +echo -e " Click 'Load unpacked' and select the directory above" echo echo -e " ${BOLD}SSH:${NC}" echo -e " ssh root@${CT_IP}"