Documentation · Cockpit

Cockpit reference

Last updated: 2026-09-21License: MIT

Everything the desktop app reads from your repository or writes to your machine: the internal cockpit CLI that agents use to drive tabs, the .ckp pane layouts, the .cockpit/tasks.json Task Run file, the theme format, the documents that open as tabs, the .env.cockpit that feeds your terminals, and the harness hooks behind the turn status. For the product tour, see the Cockpit page; for the mesh, daemons, and the relay, see the Remote Pi docs.

Install

Cockpit ships for macOS, Windows, and Linux. macOS builds are signed and notarized, and every platform that supports it gets in-app updates. Grab a build from the download page — .dmg, .exe, .deb and .rpm (x64 and arm64), each with a published SHA-256.

Cockpit is a terminal first: it needs no account, no cloud and nothing else installed to be useful. Agents run as ordinary processes in its tabs, so the harness you already use (Claude Code, Codex CLI, Pi, OpenCode) is the one that runs here.

Working on another machine is SSH plus cockpit-server, described in Remote hosts & VPS. That is unrelated to the Remote Pi relay: the mesh, the phone app and the 24/7 daemons belong to the Remote Pi project and are not part of Cockpit.

The cockpit CLI

Cockpit materializes a small binary at ~/.cockpit/bin/cockpit and puts that folder on the PATH of the terminals it spawns — and only those. So the CLI exists for anything running inside Cockpit (you, a script, an agent) and does not leak into the rest of your shell environment. It talks to the app over a local socket: a Unix socket on macOS and Linux, a loopback TCP port plus a token on Windows.

This is what makes Cockpit an agentic multiplexer: an agent in one tab can open another tab, type into it, read what it printed, run a project task, or query a database — the same verbs a human uses, with no screen scraping.

Targets & ids

The unit the CLI addresses is a tab (one terminal or agent session). A pane is the split leaf that groups tabs and is not addressable — list-panes and read-pane survive as legacy aliases.

FlagWhat it does
--tab-id <id>Target another tab. Defaults to $COCKPIT_TAB_ID (the current tab; legacy fallback $COCKPIT_PANE_ID).
--focusedTarget whatever tab you are looking at, resolved by the app. Works from outside a Cockpit terminal too (dictation tools, scripts): with no env inherited the CLI finds the app through ~/.cockpit/status.sock. Wins over --tab-id.
--enter(send only) press Enter right after the text, as a separate keystroke — a send plus a send-key Enter in one call.
Tab ids reset on boot

Ids (t0, t1…) are assigned per app boot, so never hardcode one. Discover them with cockpit list-tabs, or give a tab a stable label (double-click the tab, or new-tab --title) and address it by name. Labels persist across boots; workspace ids are opaque UUIDs, so use workspacePath / path when you need the folder on disk.

Command reference

CommandWhat it does
send [--tab-id id] [--enter] <text>Type text into a tab.
send-key [--tab-id id] <Key>…Press named keys: Enter Tab Escape Space BSpace Up Down Left Right Home End PageUp PageDown Delete and C-<letter> (e.g. C-c).
open <file>Open a file in the app's viewer. Bare cockpit <file>is a shortcut for it, relative to the tab's cwd.
new-tab [--cwd dir] [--title name] [--split h|v]Open a terminal tab and print its id. h/right splits side by side, v/down stacks; omit to open as a tab in the same pane. Anchored at the emitting tab's pane.
read-tab [label|tab-id]Read a tab's rendered output. No target = the current tab. Alias: read-pane.
read-task <task-id>Read a task's output, even with no tab open for it.
list-tabs [--json]List active tabs (alias: list-panes). The JSON carries label, workspacePath, working and, for task output tabs, taskId.
list-workspaces [--json]List workspaces (projects) and their paths.
list-tasks [--json]List this workspace's tasks. [output] marks tasks whose output read-task can read (ran this boot); ● marks tasks running right now.
db <list|schema|query|run|execute>SQL databases registered in the workspace — see Databases.
redis [browse] --db connRun a Redis command, or open the key table for a human.
mongo [browse] --db conn [--database name]Run a MongoDB command, or open the collection browser.
orchestrate <file.ckp> [--json]Apply a pane layout — see .ckp layouts.
install-skill [--force]Install the Claude Code skill that teaches this CLI.
Cockpit terminal
$
# open a worker tab beside you, then drive it
id=$(cockpit new-tab --cwd ~/proj --title Worker --split h)
cockpit send --tab-id "$id" --enter "npm test"

# read what it printed
cockpit read-tab Worker --lines 50

# run and follow a project task
cockpit list-tasks
cockpit read-task npm:dev --lines 80

# open a file in the viewer, query a database
cockpit open ~/.gitconfig
cockpit db query --db dev-local --sql "SELECT * FROM orders LIMIT 5"

Reading output

read-tab and read-task share a windowing model. Output is always chronological (top to bottom); the flags only pick which window you get.

FlagDefaultWhat it does
--lines N100How many lines to return (server cap: 2000).
--offset N0Skip N lines from the anchor — this is your pagination.
--from-startoffAnchor at the start of the buffer instead of the tail.

Task ids are stable per workspace: npm:<script> from package.json, flutter:run / flutter:test, and json:<label> from .cockpit/tasks.json.

.ckp pane layouts

A .ckp file is a versionable YAML that describes the terminals to open in a workspace — the equivalent of a tmuxinator layout. One file is one layout, and the file name is the layout name (dev.ckp→ layout “dev”). Commit it, and a teammate gets your working geometry on clone.

There are three ways to apply one:

  • GUI — right-click the .ckp file in the tree → Open layout.
  • CLI — cockpit orchestrate dev.ckp from inside a tab.
  • Worktree autorun — autorun: worktree in the file: the layout is applied by itself whenever you create a worktree of the workspace. The worktree is born empty, so the geometry comes out exact.
dev.ckp — yaml
# dev.ckp — anywhere in the project; cwd is relative to this file
autorun: worktree        # optional
panes:
  - name: Frontend       # required, unique — becomes the tab's stable label
    cwd: frontend        # relative to this file, always with "/"
    command: claude      # optional: typed into the shell after the tab opens
  - name: Backend
    cwd: backend
    split: right         # tab (default) | right (side by side) | down (stacked)
    command: npm run dev
  - name: Sign
    cwd: .
    command: ./sign.sh
    platforms: [macos]   # optional: macos | windows | linux (string or list)

Fields

Root: panes (required) is the list of panes in creation order. autorun (optional) accepts only worktree; with two or more autorun files at the root, none of them runs — ambiguity is never guessed.

FieldRequiredDefaultDescription
nameyes—Unique (case-insensitive). Becomes the tab's manual label and the merge key.
cwdno.Relative to the file's folder, forward slashes only. Absolute paths and \ are rejected for portability.
splitnotabWhere it is born, relative to the previously created pane: tab, right, down.
commandno—Typed into the terminal and run by the tab's shell (resolved through the machine's PATH).
platformsnoallmacos / windows / linux, string or list — same semantics as in tasks.json.

Replace semantics

  • Opening a layout means become this layout: the file is validated first, then every tab of the workspace is closed (pinned ones included), then the panes are built with exact geometry. If any tab has work running (an agent mid-turn, a process in a terminal, a live task) the GUI asks before closing; idle tabs close silently.
  • cockpit orchestrate dev.ckp replaces without asking and keeps only the tab that ran the command. Pass --append to keep the old additive behavior: panes whose name already exists are skipped and nothing is closed. The worktree autorun always appends (the worktree is born empty, so it makes no difference).
  • A missing cwd or invalid YAML gives a readable error (dialog in the GUI, stderr in the CLI); nothing is applied halfway from the failing pane on.
  • command is typed ~700 ms after the tab opens, so the shell has time to finish booting, with Enter at the end.

Cockpit treats .ckp as YAML for highlighting and shows the Cockpit logo as the file icon in the tree.

Task Run

Task Run executes your project's build and dev commands (npm run dev, flutter run, go run, make…) with streamed output, a visual lifecycle (play / stop / restart), interactive keys, and reload-on-save. Two sources coexist:

  • Auto-detected — on opening a project, Cockpit reads the manifests (package.json scripts, pubspec.yaml) and shows tasks with no config at all.
  • Declared — .cockpit/tasks.json, for customizing, adding tasks, or describing a monorepo. JSON tasks take precedence over a detected task with the same id.
The runner is generic

It knows only command, args, and env. There are no stack-specific keys (flavor, dart-define, NODE_ENV) — all of that is expressed as args and env.

Where the file lives

At the root of the workspace you open. Discovery is literal — Cockpit does not walk up the tree. For a single package, open the package folder. For a monorepo, open the root and let one .cockpit/tasks.json drive the subpackages through a per-task cwd.

The file is JSONC: comments (// and /* */) and trailing commas are allowed, just like VSCode's tasks.json. Point $schema at docs/tasks.schema.json for editor autocomplete; Cockpit ignores the field when running.

.cockpit/tasks.json — jsonc
{
  "tasks": [
    {
      "label": "run",
      "cwd": "app",                 // relative to the tasks.json folder
      "command": "flutter",
      "args": ["run"],
      "kind": "watch",
      "interactiveKeys": [
        { "key": "r", "label": "Hot reload", "icon": "refresh", "primary": true },
        { "key": "R", "label": "Hot restart", "icon": "restart", "primary": true },
        { "key": "q", "label": "Quit", "icon": "stop" }
      ],
      "watch": {
        "paths": ["lib", "assets"],
        "ignore": ["build", ".dart_tool"],
        "onChange": "Hot reload",   // an interactiveKey label, or "__restart__"
        "debounceMs": 300
      },
      "progressPatterns": [
        { "begin": "Performing hot reload", "end": "Reloaded .* in .*ms" }
      ],
      "profiles": [
        { "name": "default" },
        { "name": "web", "args": ["-d", "chrome"] }
      ]
    },
    {
      "label": "api",
      "cwd": "backend",             // monorepo: another subfolder
      "command": "dart",
      "args": ["run", "bin/server.dart"],
      "kind": "watch"
    }
  ]
}

Fields

Root: tasks (required) and cwd (optional) — a default cwd for every task, which each task may override.

FieldRequiredDefaultDescription
labelyes—Short display name. The task id is derived from it: json:<label>.
commandyes—Base executable (npm, flutter, dart…).
argsno[]Base args, placed before the profile's args.
cwdnorootRun folder, relative to the tasks.json folder (absolute also accepted). Falls back to the top-level cwd, then the root.
platformsnoallOSes where the task is visible: macos, windows, linux.
kindnooneShotwatch (long-lived process, e.g. a dev server) or oneShot (runs and exits).
interactiveKeysno[]Buttons that write to the process' stdin: key, label, optional icon (refresh, restart, stop, bolt) and primary.
watchnonullReload on save: paths, ignore, onChange (required — an interactiveKeys label or "__restart__") and debounceMs (300). Leave it out for tools that already watch (Vite, Next).
progressPatternsno[]begin / end regexes that swing the badge between building and running.
profilesno[]Named run variants (launch configs): name, extra argsappended after the task's, and env merged into the process environment. A chip cycles them before you hit play.
Two known limits

For an argument value containing spaces, use separate items in args (["--dart-define", "MSG=hello world"]). And the output tab does not survive an app restart — the task dies with it.

Databases

Connections live per workspace in .cockpit/databases.json, plus any SQLite files Cockpit auto-detects in the project. SQLite, Postgres, MySQL, SQL Server, Redis, and MongoDB open as tabs: a table view for SQL and Redis, a collection browser for MongoDB. A .dbq file is a saved query you can commit and re-run.

The same connections are reachable from the CLI, and the output is one JSON line — built to be parsed by an agent, not read by a human.

Cockpit terminal
$
cockpit db list
cockpit db schema --db dev-local orders
cockpit db query --db dev-local --sql "SELECT * FROM orders LIMIT 5"
cockpit db run reports/daily.dbq

# non-SQL engines have their own verbs
cockpit redis --db cache --command "SCAN 0 COUNT 20"
cockpit mongo --db atlas --database shop --command '{"find":"orders","limit":5}'

# open the same thing visually for a human
cockpit redis browse --db cache
cockpit mongo browse --db atlas --database shop
Per-connection guardrails

Each connection carries an access level (read — the default, including for connections created before the field existed — or readwrite) and an agents flag. A connection with agents: false is invisible to the CLI. The gates are enforced on the CLI surface: what you do by hand in the GUI is never blocked, but what an agent can reach is yours to decide. Run cockpit db --help for the full surface.

Documents as tabs

Besides terminals, a Cockpit tab can be a document. Each one is a plain file in your repository, so git sees it, your agent can write it from the CLI, and nothing is locked inside the app. The Gallery panel, next to Database, has one card per document type: click it and the file is created at the workspace root and opened, on local and remote workspaces alike.

Notebook (.notebook)

A folder whose name ends in .notebook is a notebook: one .md per note, each with a shallow YAML frontmatter. Cockpit shows the folder as a single item in the tree and opens it as a notes tab. Outside the app it is an ordinary folder, so git, Obsidian and an agent in the terminal read the same files.

notes.notebook/2026-09-07-ssh-tunnel.md — markdown
---
title: SSH tunnel on the host
tags: [relay, agent]
created: 2026-09-07T10:12
updated: 2026-09-07T11:40
---

Free markdown body.
FieldRequiredNotes
titlenoWithout it, the title is the file name minus .md
tagsnoA list [a, b]. With none, the note lands in the “no tag” group
creatednoYYYY-MM-DDTHH:MM
updatednoRewritten by the app on save

The parser never throws: a file with no frontmatter, or with an unterminated ---, becomes a note whose body is the whole content. The app only rewrites the title, tags and updated lines, leaving the body and any other key untouched.

Notes are always editable, in a single mode: the markdown is painted live as you type while the markers stay visible, so the file remains plain markdown. It saves on its own about a second and a half after you stop typing (and on tab change or close), and ⌘S forces it. Pasting or dropping an image writes it to _assets/ inside the notebook. [[Note title]] in the body becomes a clickable chip, creating the note if it does not exist, and each note lists its backlinks at the bottom.

Cockpit terminal
$
cockpit note add notes.notebook --title "SSH tunnel on the host" --tag relay   --body "The host opens the tunnel, not the client."

# read the body from stdin, and list what is there
cockpit note add notes.notebook --title "Session summary" --body - <<'NOTE'
Everything that changed today.
NOTE
cockpit note list notes.notebook
The agent tag

The CLI always adds the reserved tag agent to a note it writes, and the UI marks those with a spark, so a note left by an agent is never confused with one you wrote.

HTTP requests (.http)

A .http file is a request tab: editor on one side, response on the other. The syntax is the one the REST Client and JetBrains HTTP Client use, so files you already have work here: ### separates requests, @name = value declares a variable and {{name}} interpolates it. Run the request under the cursor with ⌘↵ and read the response as JSON, as headers or as raw text.

api/users.http
@base = https://api.example.com
@token = a-token-for-this-file

### list users
GET {{base}}/users
Authorization: Bearer {{token}}

### create one
POST {{base}}/users
Content-Type: application/json

{ "name": "Ada" }

The agent runs the same file through the CLI, and gets one JSON line back. A 4xx or 5xx is a normal response, so it should check status rather than the exit code.

Cockpit terminal
$
cockpit http list api/users.http
cockpit http run api/users.http --request "list users"

Boards, diagrams and windows

  • Kanban: a .kanban markdown file renders as a board, with dependencies between cards (blockedBy, plus blocked and ready filters), a title and label filter, drag and drop in list mode, markdown in comments, and a hold on the advance arrow to send a card straight to the last column.
  • Mermaid: a ```mermaid fence renders as a diagram in the markdown preview, offline, in your theme. Prefer it over ASCII art when an agent explains a flow.
  • Document window: open any file in its own light window from the app, or straight from the operating system. Double-click a .kanban, .notebook, .ckp, .dbq, .http or a markdown file in Finder, in Explorer or in your Linux file manager, and Cockpit opens it, forwarding the path to the instance you already have running.

Workspace environment (.env.cockpit)

A plain KEY=VALUE file named .env.cockpit at the workspace root is injected into every terminal Cockpit opens there: every root of a multi-root workspace, and remote workspaces too, where the host reads its own copy right before spawning the shell. Put the tokens your agents need there instead of pasting them into a prompt. New tabs pick up changes, and Restart reloads an existing tab in place.

.env.cockpit
OPENAI_API_KEY=sk-...
GITHUB_TOKEN=ghp_...
DATABASE_URL=postgres://user:pass@localhost/app

Redaction

Values injected from the file are replaced by *** before the output reaches the emulator, which covers the screen, the saved scrollback and cockpit read-tab at once. An agent reading another tab does not read your secrets back.

Out of scope, on purpose: a value that the program re-encodes (base64, for instance) or splits with ANSI escapes, and the file itself, which is an ordinary file that anything with your permissions can read.

Keys that are never injected

Keys that change who runs what are ignored, so a file that arrives with a repository cannot redirect the programs your shell starts:

blocked
PATH  SHELL  HOME  ZDOTDIR  BASH_ENV  ENV  PROMPT_COMMAND  IFS  LD_*  DYLD_*

On top of that, when the .env.cockpit is tracked by git (so it came with the repository, it is not yours), new terminals print a notice listing the key names it injected, never the values. The Gallery card that creates the file also keeps it out of git.

A personal and a work account, side by side

Claude Code and Codex read their whole account (credentials, settings and history) from a directory you can point somewhere else, and Cockpit injects those variables per workspace like any other. That is the recipe for separate accounts: no extra feature, one line per workspace.

work-project/.env.cockpit
CLAUDE_CONFIG_DIR=/Users/me/.claude-work
# Codex uses its own variable
CODEX_HOME=/Users/me/.codex-work

Restart the tabs (or open new ones), run claude and log in once with /login. From then on that workspace uses that account, and your other workspaces keep the default one. Use absolute paths: the variables name a directory, not a profile.

Themes

Cockpit ships nine built-in themes — cockpit, cockpit.2, violet, violet.2, midnight, rose, sun, flexoki and pantera — each with a light and a dark variant. Beyond those, a theme is a single JSON filethat paints all three layers at once: the app UI, the code viewer's syntax highlighting, and the terminal palette.

The theme file

  • Import with Settings → Appearance → Theme → Import…
  • Themes live in <data folder>/themes/(the same root as the “Storage” setting), one file per theme, named after its id. Copying a .json in there installs it too.
  • Export produces a complete file (every token, no extends) — a good starting point for hand editing.
theme.json — shape — json
{
  "$schema": "https://raw.githubusercontent.com/jacobaraujo7/remote_pi/main/cockpit/docs/theme.schema.json",
  "id": "acme.aurora",
  "name": "Aurora",
  "author": "Acme",
  "version": "1.0.0",
  "extends": "cockpit",
  "variants": {
    "dark":  { "ui": {}, "syntax": {}, "terminal": {} },
    "light": { "ui": {}, "syntax": {}, "terminal": {} }
  }
}
FieldRequiredWhat it is
idyesStable, namespaced identity (publisher.name). It is what gets stored in preferences, so renaming namenever loses the user's choice. Cannot collide with a built-in id.
nameyesWhat shows up in the picker.
authornoMetadata.
versionnoMetadata.
extendsnoId of a built-in theme to inherit from. Absent = inherits from cockpit.
variantsyesAt least one of dark / light. A dark-only theme is applied in light mode too — better than mixing half a light theme with half a dark one.

Inheritance is the point. Every token you do not declare comes from the base, so a useful theme can be five lines long:

acme.violet.json — json
{
  "$schema": "https://raw.githubusercontent.com/jacobaraujo7/remote_pi/main/cockpit/docs/theme.schema.json",
  "id": "acme.violet",
  "name": "Violet",
  "variants": {
    "dark":  { "ui": { "accent": "#8B5CF6", "accentSoft": "#8B5CF633", "accentText": "#C4B5FD" } },
    "light": { "ui": { "accent": "#7C3AED", "accentSoft": "#7C3AED22", "accentText": "#5B21B6" } }
  }
}

Colors are CSS-style hex — #RGB, #RRGGBB or #RRGGBBAA, alpha last. It is not Dart's 0xAARRGGBB. On a bad import the parser points at the field path that broke (variants.dark.ui.accent), and validation runs before the copy, so an invalid file never reaches the themes folder.

Tokens

GroupTokens
ui (25)Surfaces bg panel panel2 panel3 · strokes border border2 · text text text2 text3 text4 · brand accent accentSoft accentText · state online ok error warn · editing edited editedBg · git gitStaged gitUntracked gitDeleted gitConflict · overlay scrim shadow
syntax (12)background base comment keyword string number class builtin function variable meta deletion
terminal (23)cursor selection foreground background, the 8 normal ANSI colors, their 8 bright* counterparts, and searchHitBackground searchHitBackgroundCurrent searchHitForeground

Text drawn on top of accent and erroris not a token: it is derived from the color's luminance, so a light accent automatically gets dark text.

syntax.background has a special default. The code viewer, the editor, and the terminal are all content inside a tab, so they share the field: when a theme declares neither syntax.background nor terminal.background, the three follow ui.panel of this theme, not of the base. Declare the field if your code palette needs a surface of its own.

The $schema URL lives in the repository, versioned next to the code that implements it, so the app and the schema can never drift apart. Cockpit ignores the field when reading a theme — it only serves your editor. See theme.example.json for a commented file with every group filled in.

Agent turn status

When an agent runs in a tab, Cockpit shows whether it is working, waiting for you, or idle — as a spinner, a badge, a chime, and an OS notification when the window is unfocused. Supported harnesses today: Claude Code and Codex CLI (0.147+).

How it works:

  1. At boot, Cockpit materializes the internal CLI at ~/.cockpit/bin/cockpit and registers cockpit hookon the harness' lifecycle events.
  2. On each event the harness runs the hook, passing a JSON payload on stdin.
  3. The hook translates it into a status and sends it to the app over the local socket. (A socket rather than an escape sequence on the PTY: harnesses run hooks with no controlling terminal, and writing to /dev/tty fails with ENXIO.)
  4. Routing is by the COCKPIT_PANE_ID env var, which the app injects into the tab's PTY. An agent session started outside Cockpit does not have it, so the hook is a no-op there. Nothing to configure, nothing to disable.
HarnessFileFormat
Claude Code~/.claude/settings.jsonhooks.<Event>[], each item {matcher, hooks:[{type, command}]}
Codex CLI~/.codex/hooks.jsonSame shape, plus a trust block in ~/.codex/config.toml (Codex silently ignores an untrusted hook, so Cockpit computes and writes the trust hash for you, between # >>> cockpit hooks delimiters).

Both installers do an idempotent append of a marked entry (_cockpit: v1): re-running removes our old entry and re-adds it, never rewriting the list — your own hooks, and those of plugins or iTerm2, survive untouched.

Event mapping

EventClaudeCodexStatus
UserPromptSubmit✓✓working (turn starts)
PreToolUse✓✓working— except for Claude's blocking tools (below)
PostToolUse✓✓working
Notification✓—waiting / idle (heuristic on the text)
PermissionRequest—✓waiting
Stop✓✓idle
SessionStart / SessionEnd✓✓idle
SubagentStart/Stop, PreCompact/PostCompact—✓ignored

Two asymmetries matter. On Claude, tools that block waiting for the user (AskUserQuestion, ExitPlanMode) emit no Notification; the last hook before the block is PreToolUse, so that one maps to waiting for those two — otherwise the tab would spin forever with no chime. On Codex, approval has its own event, so there is no text heuristic and no detour.

Resuming a session

Cockpit persists the session_id that arrived through the hook, and on restoring the tab it types the command that reattaches the conversation. A session id alone does not say which harness it belongs to, and the commands differ — claude --resume <id> versus codex resume <id> — so the installer registers the hook as cockpit hook --harness <name> and the layout stores the harness next to the id. Entries written by older versions pass no flag and are assumed to be Claude, which is what they all were.

Codex trust is index-keyed

The Codex trust key includes the hook's group index. If you add a hook of your own before ours on the same event — or edit hooks.json by hand — the hash stops matching and the hook silently stops running. The installer repairs it on the next boot, because it recomputes the indices from the final file.

Remote hosts & VPS

A remote workspace is a folder on another machine, reached over SSH. Cockpit runs a small headless cockpit-server on the host, talks to it through an SSH tunnel to a Unix socket, and nothing is ever exposed on the network: SSH is the only door. Terminals and agents keep running on the host when you disconnect, and the next connection picks them up where they were.

From the desktop app you usually need nothing on the host: Cockpit uploads the server over SSH on first connect and keeps it updated (it compares a manifest of the installed files with the bundle it ships and reinstalls when they differ). The mobile apps (iPad, Android) carry no server, so a host you want to reach from them must be prepared once, either by a desktop or with the installer below.

The desktop can only install the targets it ships, so the installer is also the way in for the other combinations:

ClientLinux arm64 hostLinux x86_64 host
macOSinstalls and updates over SSHinstaller
Linux arm64installs and updates over SSHinstaller
Linux x86_64installerinstalls and updates over SSH
Windowsinstallerinstaller
iPad / Androidinstallerinstaller

Both paths land in the same place and recognize each other: a host prepared with the installer is reused as is by a desktop that ships the same version, and a host prepared by a desktop can be updated later with the installer. Whoever gets there first installs; the other one just connects.

Install the server

Linux x86_64 and arm64. User space, no sudo, idempotent; re-run it to update.

on the host — bash
curl -fsSL https://remote-pi.jacobmoura.work/cockpit-server.sh | bash
# or straight from GitHub (the URL above redirects here):
curl -fsSL https://raw.githubusercontent.com/jacobaraujo7/remote_pi/main/cockpit/install-server.sh | bash

The script detects the architecture, downloads cockpit-server-<version>-linux-<arch>.zip from the GitHub release, verifies its SHA-256 and runs the install.sh shipped inside the zip. That installs to ~/.cockpit/server (the same layout the desktop app uses), links the binary into ~/.local/bin, checks every file against bundle.manifest and does a smoke start before swapping the new version in. A host without internet access can take the zip by scp and run ./cockpit-server/install.sh directly.

Note

Versions must match. Client and server are released together and the app refuses a different server version. Pin one with COCKPIT_VERSION=1.28.33 in front of the command; without it the latest release is used. Desktop clients fix a mismatch by themselves over SSH; from mobile, re-run the installer.

Start at boot

By default the app starts the server on demand and it exits when idle with no live session; terminals and agents keep the server alive while they run, so most hosts need no service. Note that if you kill the server while a Cockpit workspace is open on it, the client treats that as a dropped connection and starts it again. For a dedicated VPS you can register a systemd --user unit: the server is up right after a reboot, never exits on idle, and systemd restarts it on failure. It does not change how the app connects, and a reboot still ends the sessions that were running.

on the host — bash
# at install time
curl -fsSL https://remote-pi.jacobmoura.work/cockpit-server.sh | bash -s -- --service

# or later (the installer links cockpit-server into ~/.local/bin)
cockpit-server service install
cockpit-server service status
cockpit-server service uninstall

The unit lives in ~/.config/systemd/user/cockpit-server.service. Starting at boot without an open SSH session requires linger; the command tries to enable it and, when that needs root, prints the one-line sudo loginctl enable-linger for you to run once. Updates restart the unit automatically. To stop it for real use systemctl --user stop cockpit-server (a plain kill is undone by systemd in two seconds).

Troubleshooting

  • version_mismatch: the host runs another release than the app. Re-run the installer (mobile) or reconnect from a desktop, which reinstalls.
  • cockpit-server did not start during install: the log printed above the error is the reason. A glibc older than the build expects is the usual cause on old distributions; check ldd --version.
  • Socket permission: the server listens on ~/.cockpit/cockpit-server.sock as the SSH user; connect with the same user that ran the installer.
  • Updating means restarting: a new server version replaces the running process, which ends the terminals and agents on that host. The desktop does it silently when its bundle differs from the host; the installer does it when you run it with a newer release. Finish long jobs first.
  • Nothing after reboot: without the service the first connection starts the server (a second or two); with it, check cockpit-server service status and linger.

Sounds & notifications

Turn status drives audio too. Under Settings → Notifications you can bind a sound per event — turn done, action needed, error — pick a custom audio file for each, and set the volume. With the window focused you get the chime; unfocused, an OS notification. Since an agent started outside Cockpit never reports status, nothing fires for sessions the app is not hosting.

Language

Cockpit's interface is fully localized in English, Portuguese (Brazil) and Spanish, down to the native application menu. Switch it in Settings → General → Language; the choice is independent of the OS locale.