Skip to content

feat(ssh): native SSH as default, remove WebSocket relay#854

Merged
paulocsanz merged 8 commits intomasterfrom
paulo/native-ssh-flow
Apr 27, 2026
Merged

feat(ssh): native SSH as default, remove WebSocket relay#854
paulocsanz merged 8 commits intomasterfrom
paulo/native-ssh-flow

Conversation

@paulocsanz
Copy link
Copy Markdown
Collaborator

@paulocsanz paulocsanz commented Apr 22, 2026

Replaces the WebSocket relay SSH implementation with native SSH (ssh <serviceInstanceId>@ssh.railway.com) as the only connection mode.

  • Remove WebSocket relay, platform event loops, and --native flag — native SSH is now the default (--native kept hidden, no-op, prints deprecation warning)
  • Remove --session relay dependency; tmux sessions now work via native SSH with auto-reconnect
  • Add --deployment-instance flag: passes instance ID directly as the SSH username
  • Autodetect PTY allocation from stdin/stdout TTY state (matches docker exec / kubectl exec):
    • command + both TTYs → -t (interactive remote tools like vim, htop work)
    • command + non-TTY → -T (clean pipes for scripts / CI)
    • no command + TTY → ssh default (interactive shell with PTY)
    • no command + non-TTY → -T (avoid mangling piped stdin)

@paulocsanz paulocsanz added the release/major Author major release label Apr 22, 2026
- Remove WebSocket relay, platform event loops, and --native flag
- Add --session flag: tmux persistent sessions via native SSH with auto-reconnect
- Add --deployment-instance flag: passes instance ID directly as SSH username
- Add -T flag for non-interactive command execution
@paulocsanz paulocsanz force-pushed the paulo/native-ssh-flow branch from 62f55c5 to 8c4f89d Compare April 22, 2026 14:30
The flag is kept hidden for backward compatibility with existing scripts
and skills that pass --native, but now prints a deprecation warning.
Replace unconditional -T-on-command with stdin/stdout TTY detection,
mirroring docker exec / kubectl exec behavior:

- command + both TTYs  -> -t  (so vim/htop/less work remotely)
- command + non-TTY    -> -T  (clean pipes for scripts/CI)
- no command + TTY     -> ssh default (interactive shell with PTY)
- no command + non-TTY -> -T  (avoid mangling piped stdin)

Fixes the previous behavior where `railway ssh -- vim foo` blocked PTY
and made interactive remote tools unusable.
Accepts a path to a private key and forwards it to the spawned ssh
process via -i, mirroring ssh(1). When set, skips the local ~/.ssh
fingerprint scan — ssh surfaces any auth failures directly.
Adds a small tel helper that wraps each stage of the SSH flow and fires
a stage-tagged CliTrackEvent on failure, in addition to the generic
event from the commands! macro. Stages: key_setup, resolve_target,
instance_lookup, tmux_install, session_connect, spawn, exit_nonzero,
and keys_{list,add,remove,github}.

Also fixes a gap where a non-zero ssh child exit bypassed telemetry
entirely via std::process::exit, and splits run_native_ssh_with_session
so tmux-install failures are distinguishable from session connect
failures in telemetry.
Backend change (railwayapp/mono#27883) adds workspace-owned SSH keys —
registered under a workspace instead of a user, so CI/CD can authenticate
as the workspace over native SSH. Thread that through the CLI:

- sshPublicKeys query now accepts workspaceId; sshPublicKeyCreate takes
  workspaceId in its input. Schema.json regenerated from the new backend
  schema.
- get_registered_ssh_keys / register_ssh_key take Option<String>
  workspace_id. None → user keys (current behavior); Some → workspace.
- `railway ssh keys` gains a global --workspace flag. `list / add /
  remove` operate on that workspace's keys when set; ADMIN access on the
  workspace is required for add / remove (enforced by backboard).
- `railway ssh keys github` rejects --workspace explicitly — GitHub
  import is a user-only path. Error message points the user at
  `railway ssh keys add --workspace <id> --key <path>` after importing.
- The auto-register prompt the regular `railway ssh` flow runs before
  connecting (ensure_ssh_key) stays user-scoped; workspace keys are
  registered once up front by an admin, not interactively per-connect.

No change to the `railway ssh` command itself — once a workspace key is
registered, the OS ssh client presents it and backboard's
lookupOwnerByKeyFingerprint recognizes it as workspace-owned via the
backend PR above.
…cope

Adds the `ssh_keys` scope to the CLI's OAuth request so `railway login`
sessions can manage SSH keys (backend gates list/create/delete on it per
railwayapp/mono#27883).

Under `RAILWAY_API_TOKEN` scoped to a single workspace, `railway ssh keys`
and `railway ssh`'s preflight now auto-resolve the workspace via the
`apiToken { workspaces }` introspection query — no explicit `--workspace`
needed. Explicit `--workspace` is still accepted but must match the
token's workspace, else the CLI errors locally with a clear message.

`railway ssh keys` with `RAILWAY_TOKEN` (project token) bails up front
since project tokens cannot manage SSH keys.
…s from ctx

Paired with the backboard change that defaults `sshPublicKey{s,Create}`
`workspaceId` to `ctx.workspace.id` when the caller is on a
workspace-scoped API token. The CLI no longer needs to pre-call
`apiToken { workspaces }` to discover what the backend already knew.

Removes:
  - `src/gql/queries/strings/ApiToken.graphql` (+ registration)
  - `resolve_token_workspace` / `TokenWorkspace` in controllers
  - `resolve_workspace_arg` reconciler in `ssh keys`
  - workspace-token branch in `ensure_ssh_key` preflight

Kept:
  - `ssh_keys` in CLI OAuth scopes (required by OAuth auth path regardless)
  - `RAILWAY_TOKEN` rejection at the top of `ssh keys` (project tokens still can't manage SSH keys)
  - The `--workspace` flag and its explicit-override behavior

Net: one fewer GraphQL round-trip per `railway ssh` invocation, ~120
fewer lines of CLI code, same UX under a workspace token.
@paulocsanz paulocsanz added release/minor Author minor release and removed release/major Author major release labels Apr 27, 2026
@paulocsanz paulocsanz merged commit b28d45d into master Apr 27, 2026
10 checks passed
@paulocsanz paulocsanz deleted the paulo/native-ssh-flow branch April 27, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release/minor Author minor release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants