Remote instances & headless server
Run Qalatra Server on a Linux box and connect desktop, web, and mobile clients over HTTPS.
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.
| Channel | Example | Cloudflare Access | Auth owner |
|---|---|---|---|
| Operator access | SSH, noVNC, admin desktop | On | Cloudflare Access + SSH keys |
| Qalatra API | api.example.com | Off | 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:8080on 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, including
fetch('/api/...'),XMLHttpRequest,EventSource, andnavigator.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.
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