Skip to content

SSH Computers: start the App Server on a Linux computer over SSH - #592

Merged
OldKrab merged 24 commits into
mainfrom
shushakov/ssh-computers
Oct 23, 2026
Merged

OldKrab merged 24 commits into
mainfrom
shushakov/ssh-computers

Conversation

@OldKrab

@OldKrab OldKrab commented Oct 23, 2026

Copy link
Copy Markdown
Owner

Implements ADR-0069: an App Shell installs, starts and keeps the App Server on a Linux computer it reaches over SSH, the way the Mobile App runs it in Termux. This is different from pairing with a computer, which needs an App Shell already running there.

What changes

  • Protocol / App Server: client/replacementImpact reports what replacing the App Server would interrupt. A keep command holds the App Server on the computer while App Shells or waiting Tasks need it, then leaves so it stops by the existing rule.
  • remote-client: the shared SSH launcher (probe, fetch, upload with checksum, start or attach, forward), and release archive unpacking behind the release-archive feature.
  • Desktop: SSH Computers through the system ssh, so the user's configuration, jump hosts, agent and prompts keep working.
  • android-ssh: a russh client with UniFFI bindings for the phone. It pins the host key after the user confirms it, adds the phone's Keystore-encrypted key with a password used once, and supports no jump hosts.
  • Android: adding, using, stopping and forgetting SSH Computers. HeldServer is shared with the Termux App Server, and background work and Task notifications cover an SSH Computer too.
  • Frontend: SSH Computers in Settings → Remote on Desktop and phone. Replacing an older App Server asks first and warns about other apps and running agents.
  • CI: test APKs carry the Linux App Server built from the same commit, about 17 MB. Releases pin the published one by SHA-256.

Verification

  • Unit and integration tests for the protocol, App Server, remote-client, android-ssh, Desktop SSH, Android, setup UI and Settings. Workspace clippy passes with -D warnings.
  • End to end against a Docker sshd: the Desktop launcher and android-ssh host tests, and an Android emulator going through add (host key, then password), launch with upload, the workspace over the forward, Settings, Stop, App Server exit after the last client, and relaunch without upload.
  • The Android CI build is green, and the APK contains the SSH library and the Linux App Server.

Not verified

Known failures outside this change

  • cargo clippy -D warnings for the Desktop crate on Linux, in Windows- and macOS-only code.
  • feed_endpoint_is_version_and_channel_specific fails on Linux.

Follow-ups: #589 (arm64 Linux computers), #590 (Windows askpass).

Add ADR-0069: an App Shell installs, starts, and keeps the App Server on a
Linux computer over SSH, holds its token, and forwards its loopback endpoint
through the SSH connection. Covers version cases with confirmed updates, a
keeper client for lifetime, system ssh on Desktop and an embedded client on
Android. Add the SSH Computer term to CONTEXT.md.
Add client/replacementImpact (protocol 2.2). It lists the other attached
clients with their kind, name, product version, protocol version, and
whether they are reconnecting, plus active turns and pending Task
requests, without starting a replacement. A client that installed another
App Server version shows it before the user confirms the update
(ADR-0069). The App Server now keeps each client's declared protocol
version.
Add `openaide-app-server keep start|stop`, which an App Shell's SSH launcher
runs on an SSH Computer (ADR-0069). `keep start` attaches to or starts the
App Server for the default state root, detaches a keeper client into its own
session, and prints one JSON report with the endpoint, token and versions.

The keeper holds the App Server while Turns run, while a Task waits for the
user for up to 24 hours, and for 30 minutes after the last App Shell leaves.
One keeper runs per state root, guarded by a lease lock. An App Server of a
different version is reported as a mismatch instead of being replaced; the
launcher then chooses `--replace` or `--join` after asking the user.

The handoff gains a reporting policy for incompatible servers, and the
protocol gains the `keeper` shell kind.
Add the launcher App Shells share for SSH Computers (ADR-0069). Through a
transport each App Shell provides, it checks that the computer is Linux x64,
uploads this release's App Server only when the installed one's SHA-256
differs, has the installed binary's keeper attach to or start the App Server,
and forwards the loopback endpoint.

An older App Server that accepts this release's protocol is joined and
offered an update; an older one it cannot use needs a confirmed update; a
newer or unordered one is never replaced. The launcher also reports when
logind would end the App Server at logout without lingering. Commands are
single `sh -c` lines that every common login shell passes through unchanged,
and each stage logs its outcome and duration without the token or paths.
The Desktop runs the shared launcher over the system `ssh`. It relays
ssh's questions to the WebView through an askpass helper, multiplexes
connections on macOS and Linux, and keeps a supervised loopback forward
to the App Server. The Linux App Server for the matching release is
downloaded once, checked against a SHA-256 pinned at build time, and
cached; Windows reuses its bundled WSL copy.

New commands list SSH hosts from the SSH configuration, add an SSH
Computer, report its status and last failure, confirm an update, stop
its keeper, and answer prompts. The launcher now also logs the start of
each stage.
Settings lists SSH Computers beside paired ones, adds one by host with
suggestions from the SSH configuration, stops OpenAIDE there after
confirmation, and offers an update when its App Server is older. The
update warning lists what the replacement interrupts, as the App Server
reports it. It also suggests `loginctl enable-linger` when logind would
end the App Server at logout.

The Desktop shows ssh's questions in a dialog above the startup screen
and the app, restores an SSH Computer's App Server after sleep like its
own, and offers the confirmed update when a start fails because the App
Server there is too old.
Adds the openaide-android-ssh crate: russh sessions behind UniFFI bindings
for the Mobile App. It reads and pins the host key, adds the phone's key
with a password used once, and runs the shared SSH Computer launcher over
the session with a loopback forward. Its events are metadata only.

The release archive unpacking moves into remote-client behind the
release-archive feature so the Desktop and Android verify and unpack the
Linux App Server the same way; iroh pairing becomes the default pairing
feature, which the Android crate leaves out.
The phone can now add an SSH Computer and work on the App Server it starts
there, as ADR-0069 describes. Native dialogs read the computer's host key
for the user to confirm, pin it, and add the phone's own key, either by the
user or with a password used once and never stored. The key is kept
encrypted with Android Keystore.

HeldServer takes the launch, heartbeat and relaunch lifecycle that the
Termux App Server used, so LocalServer and SshServer share it. The gateway
serves the SSH workspace on its own port, and the background service
reports its Tasks the same way. A launch that finds an App Server too old
asks before replacing it and warns that agents and other apps stop.

The Gradle build compiles the Rust library with the NDK and generates its
Kotlin bindings. The release build verifies the published Linux App Server
against the checksum built into the app before uploading it.
Remote settings on the Mobile App list the phone's SSH Computers beside
This phone and paired computers. Each row can be used, stopped or
forgotten, and the section adds a computer through the phone's native
flow. The confirmation dialogs say what stopping or updating ends, and the
section shows the update and linger notices from the last launch.
Test APKs now bundle the static Linux App Server built from the same
commit, so SSH Computers work in them before a version is published.
Releases still pin the published binary.
Main added a paired computer's Task notifications and the shared
AttentionWire helpers. HeldServer now reads Task attention and initializes
through AttentionWire, so work on an SSH Computer notifies like work on
this phone. The background service treats an SSH Computer as a held
workspace: it polls it, reports it as "ssh" in diagnostics, and keeps
running when the user switches away from it; only a paired computer's
watch stops on a switch. The protocol bindings are regenerated with both
client/replacementImpact and task/attentionWait.
ADR-0071 decides what a Terminal is and what it is not; the plan records
the approved presentation and how each layer implements it.
Protocol 2.3 adds terminals/open, terminals/attach and terminals/close,
the terminals subscription scope, and the open-Terminal count in the
replacement impact. Clients stop advertising the unread openTerminal
shell capability, which still deserializes for 2.2 clients.
App Server owns each login shell, keeps about 10,000 lines of replay in
memory, and streams input and output on a WebSocket per viewer, admitted
by a single-use attach token. Timed text pings let both sides notice a
silent connection, and Windows marks a shell with a child process busy.
A browser WebSocket cannot carry the gateway token, so the gateway
forwards an exact GET /terminal upgrade and leaves admission to the
attach token.
Opens from a Project menu or the Task header, shows a Project's
Terminals as tabs, reattaches with replay after a lost connection, adds
the phone key row, and warns before an update closes Terminals.
Quit asks natively before its exit fallback starts, from the open count
the Frontend reports, and only when this Desktop owns its App Server.
The crate joined the workspace unformatted, which fails the Rust CI job.
Terminals: an interactive shell on the App Server's computer
Ports SSH Computers onto the computer switcher (ADR-0070):
- Desktop: SSH Computers are rows of the computers list, marked by their
  `ssh` destination. Bootstrap, switch and resume recovery reach them
  through the `ssh` forward, and adding one is a switch like pairing.
- Frontend: the `ssh` capability moves onto `ComputersCapability`, and
  Settings shows SSH rows, Stop, Update and the linger notice there.
- Android: leaving an SSH Computer through the phone's switch ends its
  forward. The phone's SSH Computers stay in its Settings state and
  switch outside the computer switch (TODO).
- The Desktop Quit warning counts this computer as owning its App
  Server unless the window uses another computer.
@OldKrab
OldKrab merged commit 87f162f into main Oct 23, 2026
11 checks passed
@OldKrab
OldKrab deleted the shushakov/ssh-computers branch October 23, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant