Files
openclaw/docs/platforms/linux.md
Peter Steinberger b363d5a293 feat(linux): canvas UI via CLI-node + Tauri app IPC bridge (#107633)
* feat(linux): canvas via CLI-node + Tauri app IPC bridge

* refactor: extract gateway helper modules

* build(linux-canvas): register plugin package in lockfile

* fix(linux-canvas): move canvas advertise test out of core, regen docs/protocol/deadcode

* fix(gateway): break node-catalog/registry import cycle via leaf normalize module; add canvas glossary term

* style: oxfmt invoke.ts and runtime.ts after buildNodeEventParams extraction

* fix(linux): load Canvas WebView via dedicated data_directory context

Wry's Linux/WebKitGTK incognito mode discards Tauri's registered
WebContext (wry webkitgtk/mod.rs), so the Canvas window got a fresh
ephemeral context without the openclaw-canvas:// scheme handler — the
bundled A2UI page never committed (stayed about:blank) and every A2UI
command timed out. Use an isolated cache-backed data_directory instead,
which keeps the protocol handler while still isolating Canvas storage
from the dashboard window.

* fix(linux): keep Canvas WebView ephemeral via incognito + data_directory

Autoreview flagged that a dedicated data_directory alone persists Canvas
browser state (cookies, localStorage, IndexedDB, service workers) across
restarts, so an agent that navigates Canvas to a site could leak an
authenticated session into a later session. iOS uses a non-persistent
store; Linux should match.

Add .incognito(true) alongside .data_directory(): the distinct directory
gives Tauri a fresh WebContext key so it still attaches the
openclaw-canvas:// protocol closure, and incognito makes Wry swap in a
fresh *ephemeral* context carrying those protocols. Live-verified on a
Wayland/WebKitGTK box: the bundled page still loads
(location.href=openclaw-canvas://localhost/index.html, openclawA2UI
present, A2UI renders) and the canvas-webview dir holds no persistent
cookie/storage files.
2026-07-14 16:05:14 -07:00

218 lines
8.7 KiB
Markdown

---
summary: "Linux support + companion app status"
read_when:
- Looking for Linux companion app status
- Enabling camera, location, or notifications on a Linux node host
- Planning platform coverage or contributions
- Debugging Linux OOM kills or exit 137 on a VPS or container
title: "Linux app"
---
The Gateway is fully supported on Linux and requires Node. Bun can still be used
as a dependency installer or package-script runner, but it cannot run OpenClaw
because it does not provide `node:sqlite`.
## Desktop companion
The OpenClaw Linux companion is a Tauri desktop app for a local Gateway. It:
- installs the OpenClaw CLI and managed Node runtime when they are missing
- attaches to a healthy Gateway before attempting service changes
- delegates install, start, stop, and restart operations to the CLI-managed systemd user service
- opens the Gateway-served Control UI with its resolved authentication URL
- renders agent-driven Canvas and bundled A2UI content for a colocated CLI node host
- remains available from the system tray when its window is closed
Stable releases built from `main` ship `.deb` and AppImage bundles as assets on the
[GitHub release](https://github.com/openclaw/openclaw/releases) for the tag,
named `OpenClaw-<version>-amd64.deb` and `OpenClaw-<version>-amd64.AppImage`,
with a `SHA256SUMS.linux-app.txt` checksum file next to them. Download the
`.deb` and install it with `sudo apt install ./OpenClaw-<version>-amd64.deb`,
or mark the AppImage executable and run it directly. The AppImage runtime
needs FUSE 2 (`sudo apt install libfuse2`, or `libfuse2t64` on Ubuntu 24.04+);
without it, run the AppImage with `APPIMAGE_EXTRACT_AND_RUN=1`.
You can also build the same bundles from a source checkout:
```bash
cd apps/linux/src-tauri
pnpm dlx @tauri-apps/cli@2.11.4 build --bundles deb,appimage
```
The `Linux App` CI workflow uploads the same bundles as the
`openclaw-linux-companion` artifact for pull requests touching the app and for
manual runs. See `apps/linux/README.md` in the repository for Linux build
dependencies and development commands.
### Canvas
Linux Canvas uses two cooperating processes. `openclaw node run` remains the single Gateway node connection; the bundled `linux-canvas` plugin forwards `canvas.*` calls to the running desktop app over a user-only Unix socket. The app owns one on-demand WebView window, including the bundled A2UI renderer and action bridge back to the agent.
The plugin is enabled by default. It advertises Canvas only when the desktop socket exists at `$XDG_RUNTIME_DIR/openclaw-canvas.sock`, or `/tmp/openclaw-canvas-$UID.sock` when `XDG_RUNTIME_DIR` is unavailable. Disable it with `plugins.entries.linux-canvas.enabled: false`. On a headless Linux server without the desktop app, Canvas is not advertised.
Linux v1 uses one Canvas window. HTTP and HTTPS pages are renderable, but A2UI actions are accepted only from the bundled renderer.
## CLI and SSH alternative
The CLI remains the simplest option for a headless server, a VPS, or a remote Gateway:
1. Install Node 24.15+ (recommended), Node 22.22.3+ (LTS), or Node 25.9+.
2. `npm i -g openclaw@latest`
3. `openclaw onboard --install-daemon`
4. From your laptop: `ssh -N -L 18789:127.0.0.1:18789 <user>@<host>`
5. Open `http://127.0.0.1:18789/` and authenticate with the configured shared
secret (token by default; password if `gateway.auth.mode` is `"password"`).
Full server guide: [Linux Server](/vps). Step-by-step VPS example:
[exe.dev](/install/exe-dev).
## Node capabilities
The bundled Linux Node plugin gives the CLI `openclaw node` service device capabilities without requiring the desktop app. Commands are advertised to the Gateway only when their capability is enabled and the required local tool exists.
| Capability | Default | Requirement |
| --------------------------------------- | ------- | --------------------------------------------------------------------- |
| Desktop notifications (`system.notify`) | On | `notify-send` from libnotify and a desktop notification session |
| Camera photos and clips (`camera.*`) | Off | FFmpeg, V4L2 camera access, and PulseAudio or PipeWire for clip audio |
| Location (`location.get`) | Off | GeoClue2 and its `where-am-i` demo |
Configure the plugin in `openclaw.json`:
```json5
{
plugins: {
entries: {
"linux-node": {
config: {
notify: { enabled: true },
camera: { enabled: true },
location: { enabled: true },
},
},
},
},
}
```
Restart the node service after changing these settings. Availability is determined once per process and the node advertisement is rebuilt on restart.
The Gateway approves the node's command and capability surface separately from device pairing. On first start, or after enabling more capabilities, approve the pending surface:
```bash
openclaw nodes pending
openclaw nodes approve <requestId>
```
A node can be connected and device-paired while its effective `caps` and `commands` remain empty until this approval completes.
Camera devices must be readable by the service user, commonly through the `video` group. Camera clips use the default PulseAudio or PipeWire source when `includeAudio` is true; microphone audio exists only as that clip track, not as a standalone command. Location requires the node-service user to be permitted by the host's GeoClue policy.
`camera.snap` and `camera.clip` also require explicit Gateway arming through `gateway.nodes.allowCommands`. See [Camera capture](/nodes/camera) and [Location command](/nodes/location-command) for payloads, limits, and errors.
## Install
- [Getting Started](/start/getting-started)
- [Install & updates](/install/updating)
- Optional: [Bun package workflow](/install/bun), [Nix](/install/nix), [Docker](/install/docker)
## Gateway service (systemd)
Install with one of:
```bash
openclaw onboard --install-daemon
openclaw gateway install
openclaw configure # select "Gateway service" when prompted
```
Repair or migrate an existing install:
```bash
openclaw doctor
```
`openclaw gateway install` renders a systemd **user** unit by default. Full
service guidance, including the **system**-level unit variant for shared or
always-on hosts, lives in the [Gateway runbook](/gateway#supervision-and-service-lifecycle).
Write a unit by hand only for a custom setup. Minimal user-unit example
(`~/.config/systemd/user/openclaw-gateway[-<profile>].service`):
```ini
[Unit]
Description=OpenClaw Gateway (profile: <profile>, v<version>)
After=network-online.target
Wants=network-online.target
StartLimitBurst=5
StartLimitIntervalSec=60
[Service]
ExecStart=/usr/local/bin/openclaw gateway --port 18789
Restart=always
RestartSec=5
RestartPreventExitStatus=78
TimeoutStopSec=30
TimeoutStartSec=30
SuccessExitStatus=0 143
OOMPolicy=continue
KillMode=control-group
[Install]
WantedBy=default.target
```
Enable it:
```bash
systemctl --user enable --now openclaw-gateway[-<profile>].service
```
## Memory pressure and OOM kills
On Linux, the kernel picks an OOM victim when a host, VM, or container cgroup
runs out of memory. The Gateway is a poor victim because it owns long-lived
sessions and channel connections, so OpenClaw biases transient child
processes to be killed first when possible.
For eligible Linux child spawns, OpenClaw wraps the command in a short
`/bin/sh` shim that raises the child's own `oom_score_adj` to `1000`, then
`exec`s the real command. This is unprivileged: a process may always raise
its own OOM score.
Covered child process surfaces:
- Supervisor-managed command children
- PTY shell children
- MCP stdio server children
- OpenClaw-launched browser/Chrome processes (via the plugin SDK process runtime)
The wrapper is Linux-only and skipped when `/bin/sh` is unavailable, or when
the child env sets `OPENCLAW_CHILD_OOM_SCORE_ADJ` to `0`, `false`, `no`, or
`off`.
Verify a child process:
```bash
cat /proc/<child-pid>/oom_score_adj
```
Expected value for covered children is `1000`; the Gateway process itself
keeps its normal score (usually `0`).
The systemd unit's `OOMPolicy=continue` keeps the Gateway service alive when
a transient child is selected by the OOM killer instead of marking the whole
unit failed and restarting all channels; the failed child/session reports its
own error.
This does not replace normal memory tuning. If a VPS or container repeatedly
kills children, raise the memory limit, reduce concurrency, or add stronger
resource controls (systemd `MemoryMax=`, container memory limits).
## Related
- [Install overview](/install)
- [Linux server](/vps)
- [Raspberry Pi](/platforms/raspberry-pi)
- [Gateway runbook](/gateway)
- [Gateway configuration](/gateway/configuration)