Architecture
Node-Only Daemon
Synced from github.com/CoWork-OS/CoWork-OS/docs
Goal: run CoWork OS on Linux servers (VPS/headless) as a Node.js daemon with no desktop window and no Xvfb.
This is an alternative to the Linux “headless Electron” mode. It’s designed for:
- packaged Linux server releases
- VPS/systemd installs
- headless Docker installs
- a CLI/web-dashboard driven workflow (no desktop UI required)
Important naming detail: “Node-only” describes the process entrypoint (node bin/coworkd-node.js) and the absence of a desktop UI/Xvfb. The packaged server tarball can still include the electron npm package as a compatibility dependency while shared runtime helpers are being decoupled; users do not launch Electron from this package.
What It Runs
The Node daemon (coworkd-node) wires up:
- SQLite database + secure settings storage
- provider factories (LLM/search) + env import (optional)
- agent daemon + task execution
- WebSocket Control Plane + minimal HTTP UI (
/+/health) - optional channel gateway (Telegram/Discord/Slack/etc)
- optional MCP + cron (best-effort)
- access-profile resolution and fail-closed enforcement shared with the desktop runtime
Recommended Install (Packaged Server Release)
For production VPS installs, use the GitHub release tarball documented in Linux VPS:
version=<version>
curl -LO "https://github.com/CoWork-OS/CoWork-OS/releases/download/v${version}/cowork-os-server-linux-x64-v${version}.tar.gz"
curl -LO "https://github.com/CoWork-OS/CoWork-OS/releases/download/v${version}/cowork-os-server-linux-x64-v${version}.tar.gz.sha256"
sha256sum --check "cowork-os-server-linux-x64-v${version}.tar.gz.sha256"
sudo mkdir -p /opt/cowork-os
sudo tar -xzf "cowork-os-server-linux-x64-v${version}.tar.gz" -C /opt/cowork-os --strip-components=1
The package includes built daemon assets, runtime dependencies, resources, connectors, and systemd templates.
Source Install
npm ci
npm run build:daemon
npm run build:connectors
# Start the daemon (Control Plane on 127.0.0.1:18789 by default)
node bin/coworkd-node.js --print-control-plane-token
Notes:
bin/coworkd-node.jswill rebuildbetter-sqlite3for the current Node ABI if needed.- By default the Control Plane binds to loopback (
127.0.0.1) for safety. Use SSH tunnel/Tailscale for remote access. - Managed/headless startup blocks public Control Plane binds (
0.0.0.0/::) unless Tailscale is enabled,COWORK_CONTROL_PLANE_BIND_CONTEXT=containeris set for a privately published container, orCOWORK_CONTROL_PLANE_ALLOW_INSECURE_PUBLIC_BIND=1is set as an explicit break-glass override. - Reverse-proxied dashboards should set
COWORK_CONTROL_PLANE_ALLOWED_ORIGINSto the public HTTPS origin. Only setCOWORK_CONTROL_PLANE_TRUST_PROXY=1behind a proxy you control.
Remote Use (No Desktop Required)
- SSH tunnel from your laptop:
ssh -N -L 18789:127.0.0.1:18789 user@your-vps
- Open the minimal dashboard:
http://127.0.0.1:18789/
- Or use the CLI:
export COWORK_CONTROL_PLANE_URL=ws://127.0.0.1:18789
export COWORK_CONTROL_PLANE_TOKEN=... # printed on first token generation or via --print-control-plane-token
node bin/coworkctl.js call config.get
node bin/coworkctl.js call llm.configure '{"providerType":"openai","apiKey":"sk-...","model":"gpt-4o-mini"}'
node bin/coworkctl.js call llm.configure '{"providerType":"openrouter","apiKey":"sk-or-...","model":"openrouter/pareto-code","settings":{"paretoMinCodingScore":0.8}}'
node bin/coworkctl.js call workspace.create '{"name":"main","path":"/srv/cowork/workspace"}'
node bin/coworkctl.js call task.create '{"workspaceId":"...","title":"Test","prompt":"Say hi","agentConfig":{"accessProfileId":"ask_for_approval"}}'
node bin/coworkctl.js watch --event task.event
Headless tasks use the same access profiles as desktop tasks. Pass
agentConfig.accessProfileId for a task-specific choice, or configure the default profile through
the target node's settings. The daemon resolves the profile on the target, applies its sandbox,
command-tool, filesystem, network, domain, and approval boundaries, and fails closed if a named
profile is missing or invalid. shellAccess and legacy permissionMode values remain compatibility
inputs only.
Ordinary work inside a named profile's granted boundary does not create an approval request. The
local daemon also runs without popup approvals by default: an unresolved ask becomes an
assistant message and durable inline Deny / Allow once input card for interactive tasks.
This covers network access, credentials, exports, MCP/external side effects, eligible outside-
workspace paths, and explicit no-auto-approve requests. Automated tasks without a human-input
channel fail closed. Set COWORK_APPROVAL_PROMPTS=on to restore the legacy queue for diagnostics.
Bounded shell execution still requires an available OS sandbox, and hard denials, administrator
policy, protected paths, and explicit tool opt-outs remain enforced. approval: never suppresses
requests while retaining the profile's filesystem and network restrictions. Pending approval
state fails closed across restart.
Headless Limitations (Expected)
Some tools are desktop-only (clipboard, screenshot capture, opening files in Finder/Explorer, etc). In the Node daemon these will return a clear error instead of trying to use Electron APIs.