Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.sh text eol=lf
*.service text eol=lf
*.service.template text eol=lf
75 changes: 75 additions & 0 deletions .github/workflows/linux-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Linux CI

on:
push:
branches: [main, "feat/**"]
pull_request:

permissions:
contents: read

env:
DESKTOP_COMMANDER_DISABLE_TELEMETRY: "true"
DESKTOP_COMMANDER_HEADLESS: "true"

jobs:
ubuntu-matrix:
name: Ubuntu ${{ matrix.os }} / Node ${{ matrix.node }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
node: [20, 22]

steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y procps ripgrep
- run: npm ci --ignore-scripts --no-audit --no-fund
- run: npm run build
- run: node dist/npm-scripts/verify-ripgrep.js
- run: node test/test-linux-platform.js
- run: node test/test-linux-service-installer.js

debian-12:
name: Debian 12 / Node 20
runs-on: ubuntu-24.04
container: node:20-bookworm

steps:
- name: Install system dependencies
run: apt-get update && apt-get install -y --no-install-recommends git procps ripgrep bash ca-certificates systemd
- uses: actions/checkout@v4
with:
persist-credentials: false
- run: npm ci --ignore-scripts --no-audit --no-fund
- run: npm run build
- run: node dist/npm-scripts/verify-ripgrep.js
- run: node test/test-linux-platform.js
- run: node test/test-linux-service-installer.js
- name: Validate systemd installer syntax
run: bash -n scripts/install-linux-service.sh

full-linux-suite:
name: Full Linux test suite
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y python3 procps ripgrep
- run: npm ci --ignore-scripts --no-audit --no-fund
- run: npm test
109 changes: 109 additions & 0 deletions LINUX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Desktop Commander on Linux

Desktop Commander supports desktop Linux and headless Ubuntu/Debian servers.
The Linux runtime uses native shells, `ps`, filesystem permissions, signals,
and systemd rather than Windows-specific process APIs.

## Supported baseline

- Ubuntu 22.04 and 24.04
- Debian 12
- Node.js 20 and 22
- Bash, POSIX `sh`, Zsh, and Fish
- Desktop sessions using X11 or Wayland
- Headless VPS and server environments

## Local development

```bash
git clone https://github.com/wonderwhy-er/DesktopCommanderMCP.git
cd DesktopCommanderMCP
npm ci
npm run build
node test/test-linux-platform.js
```

Run the MCP server directly:

```bash
node dist/index.js
```

## Headless remote device

A Linux host without `DISPLAY`, `WAYLAND_DISPLAY`, or `MIR_SOCKET` is detected
as headless. Browser launch is skipped and the device authorization URL and code
remain visible in the terminal or systemd journal.

You can force the runtime mode:

```bash
export DESKTOP_COMMANDER_HEADLESS=true
```

Start a remote device manually:

```bash
desktop-commander remote --persist-session --disable-no-sleep
```

The persisted device session is stored under:

```text
~/.desktop-commander-device/device.json
```

The file is created with mode `0600`.

## systemd installation

Install the package globally first, then run:

```bash
sudo desktop-commander linux-service --user "$USER"
```

The installer refuses to create a root-owned remote agent by default. A root
service requires the explicit `--allow-root` flag and grants the connected AI
full root-level host access, so it is not recommended.

For a non-standard executable path:

```bash
sudo desktop-commander linux-service \
--user "$USER" \
--bin "$HOME/.npm-global/bin/desktop-commander"
```

From a source checkout, the equivalent installer is
`sudo ./scripts/install-linux-service.sh --user "$USER"`.

The installer enables the service but does not start it by default. Start it and
watch the first authorization flow with:

```bash
sudo systemctl start desktop-commander-device
sudo journalctl -u desktop-commander-device -f
```

Use `--start` to start it immediately during installation.

## Service management

```bash
systemctl status desktop-commander-device
sudo systemctl restart desktop-commander-device
sudo systemctl stop desktop-commander-device
sudo journalctl -u desktop-commander-device --since today
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

The unit uses `KillMode=control-group`, so child shells and long-running commands
are stopped with the service. It also enables `NoNewPrivileges`, `PrivateTmp`,
and read-only protection for system directories.

## Security boundary

Run the service as a dedicated non-root user. Desktop Commander can execute
arbitrary commands with that user's permissions. Directory allowlists and the
command blocklist reduce mistakes but do not provide sandbox isolation. Use a
container or VM when the connected AI must not reach the wider host.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ Execute long-running terminal commands on your computer and manage processes thr
- Command blocklist for accidental execution
- [Docker isolation](#option-6-docker-installation--auto-updates-no-nodejs-required) for complete isolation

## Linux support

Linux desktop and headless Ubuntu/Debian deployments are documented in [LINUX.md](LINUX.md), including systemd installation and runtime behavior.

## How to install

### Install in Claude Desktop
Expand Down
27 changes: 27 additions & 0 deletions deploy/linux/desktop-commander-device.service.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[Unit]
Description=Desktop Commander Remote Device
Documentation=https://github.com/wonderwhy-er/DesktopCommanderMCP
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=__USER__
Group=__GROUP__
WorkingDirectory=__HOME__
Environment="HOME=__HOME__"
Environment=NODE_ENV=production
Environment=DESKTOP_COMMANDER_HEADLESS=true
Environment="PATH=__PATH__"
ExecStart="__BINARY__" remote --persist-session --disable-no-sleep
Restart=on-failure
RestartSec=5
TimeoutStopSec=15
KillMode=control-group
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full

[Install]
WantedBy=multi-user.target
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"bugs": "https://github.com/wonderwhy-er/DesktopCommanderMCP/issues",
"type": "module",
"engines": {
"node": ">=18.0.0"
"node": ">=20.0.0"
},
"bin": {
"desktop-commander": "dist/index.js",
Expand All @@ -35,7 +35,7 @@
"bump": "node scripts/sync-version.js --bump",
"bump:minor": "node scripts/sync-version.js --bump --minor",
"bump:major": "node scripts/sync-version.js --bump --major",
"build": "tsc && shx cp setup-claude-server.js uninstall-claude-server.js track-installation.js dist/ && shx chmod +x dist/*.js && shx mkdir -p dist/data && shx cp src/data/onboarding-prompts.json dist/data/ && shx mkdir -p dist/remote-device/scripts && shx cp src/remote-device/scripts/blocking-offline-update.js dist/remote-device/scripts/ && node scripts/build-ui-runtime.cjs",
"build": "tsc && shx cp setup-claude-server.js uninstall-claude-server.js track-installation.js dist/ && shx chmod +x dist/*.js && shx mkdir -p dist/data && shx cp src/data/onboarding-prompts.json dist/data/ && shx mkdir -p dist/remote-device/scripts && shx cp src/remote-device/scripts/blocking-offline-update.js dist/remote-device/scripts/ && node scripts/build-ui-runtime.cjs && shx mkdir -p dist/linux && shx cp scripts/install-linux-service.sh deploy/linux/desktop-commander-device.service.template dist/linux/ && shx chmod +x dist/linux/install-linux-service.sh",
"watch": "tsc --watch",
"start": "node dist/index.js",
"start:debug": "node --inspect-brk=9229 dist/index.js",
Expand All @@ -46,6 +46,7 @@
"clean": "shx rm -rf dist",
"test": "npm run build && node test/run-all-tests.js",
"test:integration": "npm run build && node test/integration/run-all-integration-tests.js",
"test:linux": "npm run build && node test/test-linux-platform.js && node test/test-linux-service-installer.js",
"test:debug": "node --inspect test/run-all-tests.js",
"validate:tools": "npm run build && node scripts/validate-tools-sync.js",
"link:local": "npm run build && npm link",
Expand Down
Loading