qalatra/docs/Remote instances

Remote instances & headless server

Run Qalatra Server on a Linux box and connect desktop, web, and mobile clients over HTTPS.

Remote instances and headless Linux server installs are available in the current public beta. If your desktop app does not show Settings → Instances, update Qalatra first.

Qalatra can run as a server/client system. The desktop app still feels local, but the data path can go through Qalatra Server: an authenticated HTTP API that owns SQLite, MCP, background workers, agent jobs, attachments, backups, tokens, and encryption key access.

What changes

  • Local desktop: Electron starts a local Qalatra Server and connects to it over the same API path remote clients use.
  • Remote server: a Linux user service runs Qalatra without the Electron UI.
  • Instances: Settings → Instances stores server URLs and bearer tokens so the desktop app can switch between local and remote data sources. Each backend keeps its own sidebar layout (Settings → Sidebar) and remembers the tab you last had open, so switching between several servers returns you to where you left off on each.
  • MCP: the server starts MCP locally, but MCP is not exposed publicly.

Trust model

Use separate channels for operator access and the Qalatra API.

ChannelExampleCloudflare AccessAuth owner
Operator accessSSH, noVNC, admin desktopOnCloudflare Access + SSH keys
Qalatra APIapi.example.comOffQalatra bearer tokens
Do not put the Qalatra API hostname behind a Cloudflare Access login wall. Electron and web and mobile clients expect JSON API responses and authenticate with Qalatra bearer tokens.

Linux headless install

Run the installer as the normal Linux user that should own Qalatra. Do not run it with sudo.

export QALATRA_TUNNEL_HOSTNAME=api.example.com

curl -fsSL https://raw.githubusercontent.com/pirateandfox/qalatra/develop/scripts/bootstrap-linux-server.sh | bash

It installs the develop branch by default. To pin a release, set QALATRA_REF to its tag (for example v1.9.63) or to a full 40-character commit SHA before running it; a short SHA is refused. Re-running the script with a new tag moves an existing install to it (1.9.63+).

The bootstrap script:

  • clones or updates Qalatra in ~/qalatra
  • installs dependencies without Electron postinstall rebuilds
  • rebuilds native modules for system Node
  • installs and starts qalatra-server.service
  • creates or reuses a Cloudflare Tunnel named qalatra-api
  • routes the API hostname to 127.0.0.1:3456
  • installs and starts qalatra-cloudflared.service

Reboot survival

loginctl enable-linger "$USER"

systemctl --user status qalatra-server.service --no-pager -l
systemctl --user status qalatra-cloudflared.service --no-pager -l

Terminals across restarts

Before Qalatra 1.9.32, restarting qalatra-server.service — including the automatic restart on every auto-update — killed every open remote terminal, because the tmux server backing them ran inside the service's own control group and systemd tore it down on stop. From 1.9.32 onward, tmux runs in its own transient scope, so sessions survive a restart and the browser reattaches with full scrollback.

The unit also carries KillMode=process as a backstop. Fresh installs get it from the installer, and the auto-updater patches it into existing units on the next update. To avoid one last round of lost sessions on a box updating to 1.9.32, add it by hand first:

# under [Service] in ~/.config/systemd/user/qalatra-server.service
KillMode=process

systemctl --user daemon-reload

Agent jobs across restarts

From Qalatra 1.9.60, restarting the server no longer stops running agent jobs. Each job writes its output and exit status to files under the data directory, so the next server process picks up a job that is still running, keeping its original timeout, and finishes one that ended while the server was down. Only a job whose process is gone is marked orphaned, and it keeps its session id so the next job on the task resumes it. Updates and deploys no longer need to wait for running jobs.

While no server is running, nothing enforces job timeouts, and systemctl --user stop qalatra-server leaves jobs running until they finish. Set "killAgentsOnShutdown": true in settings to stop them with the server instead. The update that installs 1.9.60 still stops the jobs running at that moment, because the previous version kills them on its way down.

Smoke tests

On the Linux server:

curl -fsS http://127.0.0.1:3456/health

TOKEN=$(cat ~/.local/share/qalatra/db/admin-token.txt)

curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  http://127.0.0.1:3456/api/instance

From another machine, test the public API hostname:

curl -fsS \
  -H "Authorization: Bearer $TOKEN" \
  https://api.example.com/api/instance

Connect desktop to a remote server

Settings -> Instances -> Add Server

URL:   https://api.example.com
Token: contents of ~/.local/share/qalatra/db/admin-token.txt from the Linux server

Prefer creating a dedicated expiring token for each client, then revoking the bootstrap token when it is no longer needed.

To create a client token, select the server in Qalatra Desktop and open Settings → Instances → Access Tokens. Give it a device name, choose an expiration, and select Create full-access token. Copy the token while it is shown. Creating another token does not replace existing ones; each can be revoked separately. From 1.9.52, revoked tokens are kept under View revoked tokens, while expired tokens remain in the main list.

Connect the web or mobile app

Open app.qalatra.com or the Qalatra mobile app and sign in with your qalatra.com account. Your account needs an assigned active Connect seat or an included Cloud seat. Organization administrators purchase and assign seats on qalatra.com; platform administrators can also grant complimentary Connect access.

After signing in, connect your Qalatra Server using its HTTPS URL and a dedicated bearer token. Your account login controls access to the client; the server token controls access to your tasks. The mobile app signs in with an existing qalatra.com account and connects to your server the same way; creating an account and managing billing stay on qalatra.com. The desktop app remains free.

If no server is connected, the web and mobile apps show setup guidance and fields for the server URL and access token. Connect tests the credentials before saving them and opening your tasks. For a Cloud server, find its Server URL and Access token under Connection credentials in the account portal’s Servers page.

Embed private box web tools

Qalatra 1.9.8+ can add one sidebar item per remote instance for a private web app running on that box. This is useful for a tools root page, dashboard, email console, or any small web surface your agents maintain beside their work.

  • The server proxies only http://127.0.0.1:8080 on the active remote box.
  • The desktop app creates a short-lived Box Web session through the existing authenticated API connection and embeds the proxied /api/box-web/proxy/<ticket>/... path for that session.
  • Root-relative HTML links/assets and CSS url(/...) references are rewritten for the proxy.
  • Qalatra 1.9.9+ also rewrites common runtime API calls from inside the iframe, includingfetch('/api/...'), XMLHttpRequest,EventSource, and navigator.sendBeacon.
  • Qalatra 1.9.20+ uses a single reconnect action that creates a fresh session and remaps the current proxied path, avoiding stale-ticket 401s after expired sessions or server restarts.
  • Qalatra 1.9.21+ hardens unattended server runtime paths: terminal WebSocket drops, agent completion write failures, and auto-attachment errors are logged without taking down the server.
Settings -> Instances -> Box Web Apps

Check "Show for <box>"
Label: Tools

Serve the tools page on the remote box:

cd ~/www
python3 -m http.server 8080 --bind 127.0.0.1

A public tools hostname remains optional for access when you are away from Qalatra Desktop. It is not required for the in-app sidebar because the proxy uses the existing Qalatra API connection and bearer token.

Agent concurrency and draining

From 1.9.56, the server setting maxConcurrentJobs controls how many agent jobs can be admitted at once. It defaults to 3. Lowering it leaves current runs alive; setting it to 0 pauses new admissions while those runs finish. Restore a positive value to resume on the next normal scheduling pass, within about 30 seconds.

Use the authenticated operator API with a full_access token.PATCH /api/v1/settings with the following body updates only this property, preserving other settings:

{ "maxConcurrentJobs": 2 }

GET /api/v1/settings/worker reports the effective limit, admitted count and paused state. Admitted jobs include worktree preparation. During a drain, the count can remain above the new limit until existing runs finish. The setting accepts nonnegative safe integers; invalid writes are rejected and invalid values already on disk log a diagnostic and use the default of 3.

Settings PUT and import replace the whole document. Use PATCH for a one-property change. On versions before 1.9.60, drain before restarting and wait for the admitted count to reach zero; from 1.9.60 a restart leaves running jobs going. Older versions do not support this control, even if they accept an unknown settings property.

MCP stays local

Qalatra Server starts MCP locally at http://127.0.0.1:3457/mcp. If Claude Code is running on the Linux machine, configure that local URL. Do not expose port 3457 through Cloudflare Tunnel.

Troubleshooting

API is not healthy

systemctl --user status qalatra-server.service --no-pager -l
journalctl --user -u qalatra-server.service -n 160 --no-pager

Server exited while unattended

Qalatra 1.9.21+ writes uncaught server errors to ~/.local/share/qalatra/db/server-crash.log.

tail -n 120 ~/.local/share/qalatra/db/server-crash.log
journalctl --user -u qalatra-server.service -n 160 --no-pager

Tunnel routes to the wrong service

cat ~/.config/qalatra/cloudflared/config.yml
cloudflared tunnel info qalatra-api
dig +short CNAME api.example.com

Native module ABI errors

cd ~/qalatra
npm run rebuild:node
systemctl --user restart qalatra-server.service