diff --git a/.gitignore b/.gitignore index 5e35eef..c520d8d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ Thumbs.db # Env .env *.log + +# Claude Code +.claude/ diff --git a/CLAUDE.md b/CLAUDE.md index 137126b..98520ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,6 +2,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## Language + +All communication, responses, code comments, commit messages, and documentation **must be in English only**, regardless of what language the user writes in. + ## Project Overview OpenClaw on Proxmox is a single-script deployment tool (`setup-openclaw-lxc.sh`) that automates creating a Proxmox LXC container with a fully configured OpenClaw AI assistant, LXQt desktop, Google Chrome, and VNC/noVNC remote access. It targets Proxmox VE 8.x+ hosts running as root. @@ -43,6 +47,10 @@ bash setup-openclaw-lxc.sh Verify by checking: container creation, all three systemd services running, noVNC web access, and OpenClaw dashboard reachability. +## Changelog + +All notable changes are documented in [`docs/changelog.md`](docs/changelog.md). Update it with every meaningful change to the script (date, short description, what changed and why). + ## Shell Style - Bash with `set -euo pipefail` diff --git a/README.md b/README.md index eb55273..d24b7e3 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ SSH into your Proxmox host and run: bash <(curl -fsSL https://raw.githubusercontent.com/adadrag/Openclaw-Proxmox/main/setup-openclaw-lxc.sh) ``` -That's it. The script will ask for a password, pick sensible defaults for everything else, and print your connection URLs when done. +That's it. The script will ask for a password, admin username, and SSH public key, then pick sensible defaults for everything else and print your connection URLs when done. ### Alternative install methods @@ -47,8 +47,8 @@ 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` | -| **SSH** | `ssh root@` | +| **Remote Desktop** | `https:///vnc.html` (after HTTPS setup) | +| **SSH** | `ssh @` (key auth only) | The script prints all of this (including the token) at the end. @@ -73,11 +73,13 @@ The script prompts you interactively (all optional except password): | Option | Default | Description | |--------|---------|-------------| -| Password | *(required)* | Container root password (also used for VNC) | +| Password | *(required)* | Container root + openclaw user password (also used for VNC) | | 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 | +| Admin username | *(required)* | Linux user with full passwordless sudo (separate from `openclaw` service account) | +| SSH public key | *(required)* | Public key for the admin user; password SSH login is disabled | Everything else is auto-detected — VMID, storage, networking (DHCP). @@ -85,7 +87,7 @@ Everything else is auto-detected — VMID, storage, networking (DHCP). All inside the container (nothing is installed on the Proxmox host): -- **Debian 13** LXC (privileged) +- **Debian 13** LXC (unprivileged, nesting enabled) - **Node.js 22** + **OpenClaw** - **Homebrew** (for OpenClaw skills) - **LXQt** desktop + **TigerVNC** + **noVNC** @@ -93,6 +95,138 @@ All inside the container (nothing is installed on the Proxmox host): - **Noto Color Emoji** font (for proper terminal rendering) - Three **systemd services**: `openclaw-gateway`, `vncserver`, `novnc` +## HTTPS Setup (recommended) + +By default noVNC runs over plain HTTP. Modern browsers block clipboard access over HTTP, so **HTTPS is required** for full clipboard support. Follow these steps to set it up. + +### 1. Generate an SSL certificate + +If you have your own CA, generate a certificate signed by it: + +```bash +mkdir -p ~/certs && cd ~/certs + +# Generate private key and CSR +openssl genrsa -out openclaw.home.key 2048 +openssl req -new -key openclaw.home.key \ + -out openclaw.home.csr \ + -subj "/CN=openclaw.home" + +# Sign with your CA (adjust paths to your rootCA files) +openssl x509 -req -in openclaw.home.csr \ + -CA /path/to/rootCA.crt \ + -CAkey /path/to/rootCA.key \ + -CAcreateserial \ + -out openclaw.home.crt \ + -days 825 \ + -extfile <(printf "subjectAltName=DNS:openclaw.home,IP:") +``` + +Alternatively, generate a self-signed certificate (you will need to accept the browser warning or add it to your trusted roots manually): + +```bash +openssl req -x509 -nodes -days 825 -newkey rsa:2048 \ + -keyout ~/certs/openclaw.home.key \ + -out ~/certs/openclaw.home.crt \ + -subj "/CN=openclaw.home" \ + -addext "subjectAltName=DNS:openclaw.home,IP:" +``` + +### 2. Configure nginx as a reverse proxy + +Install nginx if it isn't already installed: + +```bash +sudo apt install nginx -y +``` + +Create a site configuration: + +```bash +sudo nano /etc/nginx/sites-available/novnc +``` + +Paste the following (adjust certificate paths if needed): + +```nginx +server { + listen 443 ssl; + server_name openclaw.home; + + ssl_certificate /home//certs/openclaw.home.crt; + ssl_certificate_key /home//certs/openclaw.home.key; + + location / { + proxy_pass http://localhost:6080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } +} +``` + +Enable the site and restart nginx: + +```bash +sudo ln -s /etc/nginx/sites-available/novnc /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl restart nginx +``` + +The remote desktop is now available at `https://openclaw.home/vnc.html` (or by IP). + +### 3. Trust the certificate on Windows + +If you used your own CA, install `rootCA.crt` into Windows **Trusted Root Certification Authorities**: + +1. Copy `rootCA.crt` to your Windows machine +2. Double-click the file → **Install Certificate** +3. Select **Local Machine** → **Place all certificates in the following store** +4. Browse → **Trusted Root Certification Authorities** → Finish + +After installing, restart your browser. + +## Firewall Setup + +Lock down the container so only SSH and HTTPS are reachable from the network: + +```bash +sudo apt install ufw -y +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow 22 # SSH +sudo ufw allow 443 # HTTPS (nginx → noVNC) +sudo ufw enable +sudo ufw status +``` + +> **Note:** After enabling the firewall, the OpenClaw dashboard on port 18789 and raw noVNC on port 6080 are no longer directly accessible. Access the dashboard through the nginx proxy or via an SSH tunnel if needed. + +## Clipboard Usage in noVNC + +Browser security restricts direct clipboard access. There are two ways to copy and paste between your local machine and the remote desktop. + +### Option A — noVNC clipboard panel (always works) + +The noVNC interface has a built-in clipboard panel in the left-side control bar: + +1. Click the **arrow icon** on the left edge of the screen to open the control bar +2. Click the **clipboard icon** (looks like a notepad) +3. To **paste into the remote desktop**: type or paste text into the clipboard panel, then right-click inside the remote desktop and choose Paste +4. To **copy from the remote desktop**: select text inside the remote desktop, then open the clipboard panel — the selected text appears there and can be copied to your local clipboard + +### Option B — automatic clipboard sync (requires HTTPS) + +When accessing noVNC over HTTPS with a trusted certificate, the browser can sync the clipboard automatically: + +1. Open the noVNC control bar (left arrow) +2. Open **Settings** → enable **Clipboard** if it is not already on +3. The browser will ask for clipboard permission — click **Allow** +4. Copy/paste now works directly without using the clipboard panel + +> **Tip:** If the browser does not ask for clipboard permission, make sure you are accessing noVNC over `https://` and that the certificate is trusted. HTTP blocks clipboard API entirely in modern browsers. + ## Container Management ```bash @@ -121,9 +255,15 @@ pct exec -- systemctl status vncserver pct exec -- systemctl status novnc ``` -**Chrome extension not loading?** -Open Chrome, navigate to `chrome://extensions`, enable **Developer Mode** (top right toggle), then click **Load unpacked** and select the OpenClaw extension directory. +**Clipboard not working?** +Make sure you are accessing noVNC over HTTPS with a trusted certificate. HTTP blocks clipboard API in all modern browsers. See the [HTTPS Setup](#https-setup-recommended) and [Clipboard Usage](#clipboard-usage-in-novnc) sections above. + +**nginx not starting?** +```bash +sudo nginx -t # Check config syntax +sudo journalctl -u nginx --no-pager -n 50 +``` ## License -MIT License. See [LICENSE](LICENSE) for details. +MIT License. See [LICENSE](LICENSE) for details. \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..7295f3c --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,22 @@ +# Changelog + +All notable changes to `setup-openclaw-lxc.sh` are documented here. + +--- + +## 2026-04-05 + +### Fixed +- **Desktop shortcuts now launch without prompts** — PCManFM-Qt 2.1.0 (Debian 13) checks `metadata::trust` gvfs metadata (not the executable bit, not `user.trusted` xattr, not `metadata::trusted`). Set via `dbus-launch gio set metadata::trust true` run as the openclaw user during installation. `dbus-launch` creates a temporary D-Bus daemon and auto-activates gvfsd-metadata via D-Bus service activation, so no running VNC session is needed. + +### Added +- **Admin user creation** — installer now asks for a Linux username to create with full passwordless sudo rights; this user is separate from the `openclaw` service account +- **SSH public key setup** — installer asks for an SSH public key for the admin user; key is written to `~/.ssh/authorized_keys` +- **SSH hardening** — password authentication is disabled in `sshd_config`; only key-based login is allowed + +### Changed +- **Removed `--no-sandbox` from Chrome** — Chrome's sandbox relies on Linux user namespaces. On this Proxmox host, unprivileged user namespaces are enabled (`/proc/sys/kernel/unprivileged_userns_clone = 1`, `max_user_namespaces = 127591`), and the LXC container is created with `nesting=1` which passes these through to the container. Chrome runs as the non-root `openclaw` user, so it can create its own user namespace for sandboxing without needing `--no-sandbox`. The flag was removed from the `.desktop` file, CLI wrapper, OpenClaw `browser.noSandbox` config, and dashboard shortcut. + +### Previous history + +Earlier changes tracked in git commit history. diff --git a/setup-openclaw-lxc.sh b/setup-openclaw-lxc.sh index 23994ca..bf7402c 100644 --- a/setup-openclaw-lxc.sh +++ b/setup-openclaw-lxc.sh @@ -1,10 +1,19 @@ #!/usr/bin/env bash # -# setup-openclaw-lxc.sh — Automated OpenClaw AI assistant setup in a Proxmox LXC container +# setup-openclaw-lxc.sh — Automated OpenClaw AI assistant setup in an unprivileged 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, -# and prints connection info when done. +# It creates an UNPRIVILEGED Debian 13 LXC with nesting, installs OpenClaw + LXQt desktop + +# Google Chrome + VNC/noVNC, and prints connection info when done. +# +# Changes vs original: +# - unprivileged=1 + nesting=1 +# - openclaw runs as dedicated non-root user 'openclaw' +# - Chrome runs as 'openclaw' user (no root, no --no-sandbox needed) +# - VNC runs as 'openclaw' user +# - gateway systemd service runs as 'openclaw' with DISPLAY=:1 +# - brewuser sudo scope limited to brew binary only +# - removed broken 'openclaw browser extension install' call # set -euo pipefail @@ -31,11 +40,11 @@ command -v pveam >/dev/null 2>&1 || fatal "pveam not found — this script must # ─── User prompts ──────────────────────────────────────────────────────────── echo -e "${BOLD}╔═══════════════════════════════════════════════════╗${NC}" -echo -e "${BOLD}║ OpenClaw LXC Setup for Proxmox ║${NC}" +echo -e "${BOLD}║ OpenClaw LXC Setup for Proxmox (unprivileged) ║${NC}" echo -e "${BOLD}╚═══════════════════════════════════════════════════╝${NC}" echo -read -rp "Container password: " -s CT_PASSWORD +read -rp "Container password (for root + openclaw user): " -s CT_PASSWORD echo [[ -n "$CT_PASSWORD" ]] || fatal "Password cannot be empty." @@ -51,6 +60,12 @@ CORES="${CORES:-4}" read -rp "VNC resolution [1920x1080]: " VNC_RES VNC_RES="${VNC_RES:-1920x1080}" +read -rp "Admin Linux username (full sudo, SSH key login): " ADMIN_USER +[[ -n "$ADMIN_USER" ]] || fatal "Admin username cannot be empty." + +read -rp "SSH public key for ${ADMIN_USER} (paste full public key): " ADMIN_SSH_KEY +[[ -n "$ADMIN_SSH_KEY" ]] || fatal "SSH public key cannot be empty." + echo # ─── Auto-detect next VMID ─────────────────────────────────────────────────── @@ -67,7 +82,6 @@ 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 @@ -79,7 +93,6 @@ for s in stores: 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 @@ -119,7 +132,7 @@ else fi # ─── Create the LXC container ──────────────────────────────────────────────── -info "Creating LXC container $VMID..." +info "Creating unprivileged LXC container $VMID with nesting..." pct create "$VMID" "${TMPL_STORAGE}:vztmpl/${TEMPLATE}" \ --hostname openclaw \ --password "$CT_PASSWORD" \ @@ -127,22 +140,28 @@ pct create "$VMID" "${TMPL_STORAGE}:vztmpl/${TEMPLATE}" \ --memory "$MEMORY" \ --cores "$CORES" \ --net0 name=eth0,bridge=vmbr0,ip=dhcp \ - --unprivileged 0 \ + --unprivileged 1 \ + --features nesting=1 \ --start 0 -ok "Container $VMID created." +ok "Container $VMID created (unprivileged, nesting enabled)." info "Starting container $VMID..." pct start "$VMID" ok "Container started." info "Waiting for container to boot..." -sleep 3 +sleep 5 -# Helper: run a command inside the container +# Helper: run a command inside the container as root ct_exec() { pct exec "$VMID" -- bash -c "$1" } +# Helper: run a command as the openclaw user +ct_exec_user() { + pct exec "$VMID" -- su - openclaw -c "$1" +} + # ─── Wait for network ──────────────────────────────────────────────────────── info "Waiting for DHCP lease (up to 30s)..." CT_IP="" @@ -162,18 +181,65 @@ 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' + apt-get update && apt-get install -y locales openssh-server 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 2>&1 | grep -v 'Failed to write' + apt-get install -y curl ca-certificates gnupg git sudo 2>&1 | grep -v 'Failed to write' " ok "Prerequisites installed." +# ─── Create openclaw user ──────────────────────────────────────────────────── +info "Creating 'openclaw' user..." +ct_exec " + useradd -m -s /bin/bash openclaw 2>/dev/null || true + echo 'openclaw:${CT_PASSWORD}' | chpasswd + + # Allow openclaw to run specific commands via sudo (no full root) + cat > /etc/sudoers.d/openclaw << 'SUDOERS' +openclaw ALL=(ALL) NOPASSWD: /usr/bin/apt-get, /usr/sbin/service, /bin/systemctl +SUDOERS + chmod 440 /etc/sudoers.d/openclaw +" +ok "User 'openclaw' created." + +# ─── Create admin user ─────────────────────────────────────────────────────── +info "Creating admin user '${ADMIN_USER}'..." +ct_exec " + useradd -m -s /bin/bash '${ADMIN_USER}' 2>/dev/null || true + echo '${ADMIN_USER}:${CT_PASSWORD}' | chpasswd + + # Full passwordless sudo + cat > /etc/sudoers.d/${ADMIN_USER} << SUDOERS +${ADMIN_USER} ALL=(ALL) NOPASSWD: ALL +SUDOERS + chmod 440 /etc/sudoers.d/${ADMIN_USER} + + # SSH public key + mkdir -p /home/${ADMIN_USER}/.ssh + chmod 700 /home/${ADMIN_USER}/.ssh + printf '%s\n' '${ADMIN_SSH_KEY}' > /home/${ADMIN_USER}/.ssh/authorized_keys + chmod 600 /home/${ADMIN_USER}/.ssh/authorized_keys + chown -R ${ADMIN_USER}:${ADMIN_USER} /home/${ADMIN_USER}/.ssh +" +ok "Admin user '${ADMIN_USER}' created with full sudo." + +# ─── Configure SSH ─────────────────────────────────────────────────────────── +info "Configuring SSH (disable password auth, enable key auth)..." +ct_exec " + sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config + sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config + # Ensure the directive exists if not already present + grep -q '^PasswordAuthentication' /etc/ssh/sshd_config || echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config + grep -q '^PubkeyAuthentication' /etc/ssh/sshd_config || echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config + systemctl enable ssh + systemctl restart ssh +" +ok "SSH configured (password login disabled)." + info "Installing Node.js 22..." ct_exec " export DEBIAN_FRONTEND=noninteractive @@ -187,14 +253,17 @@ ct_exec " export DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential procps file 2>&1 | grep -v -E 'Failed to write|Permission denied' - # Homebrew must be installed as a non-root user + # Create brewuser with sudo limited to brew binary only useradd -m -s /bin/bash brewuser 2>/dev/null || true - echo 'brewuser ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/brewuser + cat > /etc/sudoers.d/brewuser << 'SUDOERS' +brewuser ALL=(ALL) NOPASSWD: /home/linuxbrew/.linuxbrew/bin/brew +SUDOERS + chmod 440 /etc/sudoers.d/brewuser - # 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 + # Make brew available system-wide + echo '[ -x /home/linuxbrew/.linuxbrew/bin/brew ] && eval \"\$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)\"' >> /home/openclaw/.bashrc 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 " @@ -202,6 +271,8 @@ ok "Homebrew installed." info "Installing OpenClaw..." ct_exec "npm install -g openclaw@latest 2>&1 | tail -5" +# Make openclaw binary accessible to openclaw user +ct_exec "ln -sf \$(which openclaw) /usr/local/bin/openclaw 2>/dev/null || true" ok "OpenClaw installed." info "Installing LXQt, TigerVNC, noVNC (this takes a few minutes)..." @@ -222,11 +293,32 @@ ct_exec " " ok "Google Chrome installed." +# ─── Configure Google Chrome ───────────────────────────────────────────────── +info "Configuring Google Chrome..." +ct_exec " + mkdir -p /home/openclaw/Desktop + cp /usr/share/applications/google-chrome.desktop /home/openclaw/Desktop/ + chown openclaw:openclaw /home/openclaw/Desktop/google-chrome.desktop + + # Make google-chrome available as a command (points to stable binary) + ln -sf /usr/bin/google-chrome-stable /usr/local/bin/google-chrome 2>/dev/null || true + + 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 +" +ok "Google Chrome configured." + # ─── Configure OpenClaw ────────────────────────────────────────────────────── info "Configuring OpenClaw..." AUTH_TOKEN=$(openssl rand -hex 16) +# OpenClaw state lives in openclaw user's home ct_exec " + mkdir -p /home/openclaw/.openclaw + chown -R openclaw:openclaw /home/openclaw/.openclaw +" + +ct_exec_user " openclaw config set gateway.mode local openclaw config set gateway.bind lan openclaw config set gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback true @@ -234,30 +326,28 @@ ct_exec " " ok "OpenClaw configured (token: $AUTH_TOKEN)." -info "Installing OpenClaw browser extension..." -ct_exec "openclaw browser extension install 2>&1 | tail -5 || true" -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 - chmod 600 /root/.config/tigervnc/passwd + mkdir -p /home/openclaw/.config/tigervnc + echo '${CT_PASSWORD}' | vncpasswd -f > /home/openclaw/.config/tigervnc/passwd + chmod 600 /home/openclaw/.config/tigervnc/passwd + chown -R openclaw:openclaw /home/openclaw/.config/tigervnc " -ct_exec "cat > /root/.config/tigervnc/xstartup << 'XSTARTUP' +ct_exec "cat > /home/openclaw/.config/tigervnc/xstartup << 'XSTARTUP' #!/bin/bash unset SESSION_MANAGER unset DBUS_SESSION_BUS_ADDRESS exec dbus-launch --exit-with-session startlxqt XSTARTUP -chmod +x /root/.config/tigervnc/xstartup" +chmod +x /home/openclaw/.config/tigervnc/xstartup +chown openclaw:openclaw /home/openclaw/.config/tigervnc/xstartup" # Configure lxterminal with dark theme ct_exec " - mkdir -p /root/.config/lxterminal - cat > /root/.config/lxterminal/lxterminal.conf << 'CONF' + mkdir -p /home/openclaw/.config/lxterminal + cat > /home/openclaw/.config/lxterminal/lxterminal.conf << 'CONF' [general] fontname=Monospace 12 bgcolor=#1e1e2e @@ -280,63 +370,38 @@ palette_color_14=#94e2d5 palette_color_15=#a6adc8 scrollback=10000 CONF + chown -R openclaw:openclaw /home/openclaw/.config/lxterminal " # 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)." +ct_exec "sed -i \"s/UI.initSetting('resize', 'off')/UI.initSetting('resize', 'scale')/\" /usr/share/novnc/app/ui.js 2>/dev/null || true" +ok "VNC configured." # ─── 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' + mkdir -p /home/openclaw/.config/autostart + cat > /home/openclaw/.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' + cat > /home/openclaw/.config/autostart/lxqt-xscreensaver-autostart.desktop << 'NOAUTO' [Desktop Entry] Type=Application Name=LXQt Screen Saver Hidden=true NOAUTO + chown -R openclaw:openclaw /home/openclaw/.config/autostart " 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 - - 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 - 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' + mkdir -p /home/openclaw/.config/lxqt + cat > /home/openclaw/.config/lxqt/session.conf << 'CONF' [General] __userfile__=true @@ -346,14 +411,17 @@ TERM=xterm-256color [Preferred Applications] terminal_emulator=lxterminal CONF + chown -R openclaw:openclaw /home/openclaw/.config/lxqt " ok "Default terminal configured." # ─── Create desktop shortcuts ──────────────────────────────────────────────── info "Creating desktop shortcuts..." -# Terminal shortcut -ct_exec "cat > /root/Desktop/terminal.desktop << 'SHORTCUT' +ct_exec " + mkdir -p /home/openclaw/Desktop + + cat > /home/openclaw/Desktop/terminal.desktop << 'SHORTCUT' [Desktop Entry] Version=1.0 Type=Application @@ -365,10 +433,8 @@ Terminal=false Categories=System;TerminalEmulator; StartupNotify=true SHORTCUT -chmod +x /root/Desktop/terminal.desktop" -# OpenClaw Onboarding wizard (runs in terminal) -ct_exec "cat > /root/Desktop/openclaw-onboard.desktop << 'SHORTCUT' + cat > /home/openclaw/Desktop/openclaw-onboard.desktop << 'SHORTCUT' [Desktop Entry] Version=1.0 Type=Application @@ -380,45 +446,59 @@ Terminal=false Categories=Utility; StartupNotify=true SHORTCUT -chmod +x /root/Desktop/openclaw-onboard.desktop" -# OpenClaw Dashboard (opens in Chrome with token) -ct_exec "cat > /root/Desktop/openclaw-dashboard.desktop << SHORTCUT + chown -R openclaw:openclaw /home/openclaw/Desktop + chmod +x /home/openclaw/Desktop/*.desktop +" + +# Dashboard shortcut (AUTH_TOKEN interpolated on host side) +ct_exec "cat > /home/openclaw/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} +Exec=google-chrome 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" +chmod +x /home/openclaw/Desktop/openclaw-dashboard.desktop +chown openclaw:openclaw /home/openclaw/Desktop/openclaw-dashboard.desktop" -ok "Desktop shortcuts created." +# Mark desktop files as trusted — dbus-launch spawns a temporary bus and auto-activates +# gvfsd-metadata, so this works at install time without a running VNC session +ct_exec "su - openclaw -c 'for f in /home/openclaw/Desktop/*.desktop; do dbus-launch gio set \"\$f\" metadata::trust true; done' 2>/dev/null || true" +ok "Desktop shortcuts created and marked trusted." # ─── Create systemd services ───────────────────────────────────────────────── info "Creating systemd services..." +# Gateway service — runs as openclaw user, DISPLAY=:1 set for browser support ct_exec "cat > /etc/systemd/system/openclaw-gateway.service << 'SVC' [Unit] Description=OpenClaw Gateway -After=network.target +After=network.target vncserver.service +Wants=vncserver.service [Service] Type=simple -ExecStart=/bin/openclaw gateway run --bind lan --auth token +User=openclaw +Group=openclaw +Environment=NODE_ENV=production +Environment=DISPLAY=:1 +Environment=HOME=/home/openclaw +WorkingDirectory=/home/openclaw +ExecStart=/usr/local/bin/openclaw gateway run --bind lan --auth token Restart=always RestartSec=5 -Environment=NODE_ENV=production -WorkingDirectory=/root [Install] WantedBy=multi-user.target SVC" +# VNC service — runs as openclaw user ct_exec "cat > /etc/systemd/system/vncserver.service << SVC [Unit] Description=TigerVNC Server @@ -426,7 +506,9 @@ After=network.target [Service] Type=forking -Environment=HOME=/root +User=openclaw +Group=openclaw +Environment=HOME=/home/openclaw 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 @@ -455,17 +537,18 @@ SVC" ct_exec " systemctl daemon-reload - systemctl enable --now openclaw-gateway.service systemctl enable --now vncserver.service + sleep 3 + systemctl enable --now openclaw-gateway.service systemctl enable --now novnc.service " ok "All services enabled and started." -sleep 3 +sleep 5 # ─── Verify services ───────────────────────────────────────────────────────── info "Verifying services..." -for svc in openclaw-gateway vncserver novnc; do +for svc in vncserver openclaw-gateway novnc; do if ct_exec "systemctl is-active --quiet $svc"; then ok "$svc is running." else @@ -473,13 +556,14 @@ for svc in openclaw-gateway vncserver novnc; do fi done -# Re-detect IP in case it changed +# Re-detect IP 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}║ OpenClaw LXC Setup Complete! ║${NC}" +echo -e "${BOLD}║ (unprivileged container) ║${NC}" echo -e "${BOLD}╚═══════════════════════════════════════════════════╝${NC}" echo echo -e " ${BOLD}Container ID:${NC} $VMID" @@ -492,16 +576,21 @@ 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}Browser Extension:${NC}" -echo -e " Open Chrome → chrome://extensions → Enable Developer Mode" -echo -e " Click 'Load unpacked' and select the OpenClaw extension directory" +echo -e " User: openclaw (non-root)" echo echo -e " ${BOLD}SSH:${NC}" -echo -e " ssh root@${CT_IP}" +echo -e " ssh ${ADMIN_USER}@${CT_IP} (key auth only, password login disabled)" echo echo -e " ${BOLD}Manage container:${NC}" echo -e " pct enter $VMID" echo -e " pct stop $VMID" echo -e " pct start $VMID" echo +echo -e " ${BOLD}Security notes:${NC}" +echo -e " - Container is unprivileged (nesting=1)" +echo -e " - OpenClaw runs as user 'openclaw', not root" +echo -e " - Chrome runs as 'openclaw' user (no --no-sandbox needed)" +echo -e " - SSH password login disabled, key auth only" +echo -e " - Admin user '${ADMIN_USER}' has full sudo" +echo -e " - brewuser sudo limited to brew binary only" +echo