Remote terminal

A remote terminal system with a cross-platform GPUI front end.

Your SSH. iznik-server on the host if it isn’t there. Every pane is a real pseudoterminal, drawn from its raw bytes. Linux, macOS, Windows.

İznikCircular ceramic medallion in the manner of sixteenth-century İznik ware: hatayi rosette, floral wreath of tulip and carnation, saz leaves, cloud band and pearl rim on white slip.

Download

Download a snapshot

Rolling build off develop. Replaced in place after the gates. Not a stable release. The archive carries the app and the Linux and macOS servers. No Windows server.

Windows is an unsigned zip, and it opens one ssh per command. Win32-OpenSSH can’t multiplex. The release notes name the commit.

Rust

Written in Rust

Server, client, protocol, app, harness. Rust, for stability and speed.

Same rules for people and agents. Change one in the open, or it’s a bug. cargo xtask check is the bar: format, lint, docs, tests, claims. Warnings fail it. No proof, no claim. The numbers in the docs are the numbers in the gate. Every run has a deadline.

A snapshot ships only after that. A release is a list: six-hour soak, two hosts; measure again; the gate, on the clean tree; every claim; each server built twice, bytes compared; the generated C header, read by eye. Name the machine.

The name

A fond jab at Zellij

tmux first. Tabs and panes that lived through a bad link and a restarted client. I couldn’t leave it.

Then Zellij, because it looked like now. I wanted a code editor with that stamina and that UI.

rune.build was almost love at first sight. Then I asked if I really need that much complexity, day to day.

Ghostty’s speed mesmerized me. I kept seeing a love-child of Zed’s UI and Ghostty.

İznik isn’t an editor. It’s the terminal I built instead: one window, a session that outlives the client.

The name pokes Zellij, fondly. The same word is Morocco’s mosaic tile. İznik ware is the Ottoman answer: saz leaf, tulip, carnation. Same craft, other shore.

Koray Taylan Davgana

01

The session stays put

Sessions, tabs and panes live on the host. Drop the link, close the laptop, restart the app. You pay a reconnect.

02

One channel, every pane

One SSH channel per host. Every pane rides it. The daemon has never heard of SSH.

03

Bytes stay bytes

Raw pseudoterminal bytes. libghostty-vt draws them. The wire never parses a cell.

A connection

Four steps, one host

Your ssh, your config, your known_hosts. The name you pick is handed to ssh as-is. A new name is appended, never overwritten. Batch mode: a missing host key, a passphrase, or a password is yours to fix.

On a host

Exactly this, and nothing else

Prefix, first hit wins: $XDG_DATA_HOME/iznik, $HOME/.local/share/iznik, or a runtime directory you already own. Runtime dir: $XDG_RUNTIME_DIR/iznik, else $TMPDIR/iznik-<user id>. Nothing is created just to look.

  • <prefix>/bin/iznik-server

    The server. One static binary. SHA-256, then it is renamed into place.

  • <prefix>/terminfo

    xterm-ghostty, compiled by the host’s own tic. No tic, and panes are told xterm-256color.

  • <runtime>/server.sock

    The daemon’s socket.

  • <runtime>/server.lock

    One daemon per user. The lock holds its process id.

  • <runtime>/server.log

    The daemon’s log.

  • <runtime>/agent.sock

    The newest connection’s SSH agent, and only if one was forwarded. Otherwise a new pane has no SSH_AUTH_SOCK.

A pane does not inherit SSH_CONNECTION, SSH_CLIENT, SSH_TTY, or XDG_SESSION_ID. Those die with the session. Uninstall removes the binary, the terminfo, the runtime dir, and a prefix iznik made itself.

Measured

Numbers, not adjectives

Real server, real unix socket. AMD Ryzen 7 PRO 8700GE, 62 GiB, Linux, regression profile, commit 6afd429. The ceilings are separate, and loose on purpose.

0.055 ms
Keystroke to echo, at rest, median
89 MiB/s
One pane’s throughput
6 MiB
Resident memory at rest
6.4 ms
Startup, until the socket answers

Run

From the repository

The app looks two directories up for one iznik-server per host triple. cargo app builds the x86_64 and aarch64 Linux servers, because a host is whatever it is. Wrong architecture: refused. The host keeps the build it had.

  • This machine

    cargo xtask doctor

    What is missing here, and how to install it.

  • The application

    ./scripts/app/run.sh

    Toolchain, both Linux servers, then the app.

  • The gates

    cargo xtask check

    Format, lint, docs, tests, proofs.

  • A host

    iznik doctor <host>

    JSON. The layer that failed, and skipped below it.

  • Take it off

    iznik uninstall <host>

    Binary, terminfo, runtime dir. A borrowed prefix keeps what was not ours.

The server, by itself

Bootstrap runs exactly one of these. So can you. --program is what a pane runs. --idle-shutdown-seconds is how fast an empty daemon quits.

iznik-server --stdio

What SSH runs. Relays stdio to the daemon, and starts it if it is down.

Pieces

What the repository holds

  • iznik-app

    The GPUI application. One window, the pane grid, the bars, the palette.

  • iznik-client

    SSH, bootstrap, the client-side model, optimistic commands, several hosts.

  • iznik-server

    The daemon. Panes, mirrors, history, sessions, multiplexing, resume.

  • iznik-protocol

    The iznik/1 messages. No I/O, no clock, no dependencies.

  • iznik-ffi

    C ABI over the client engine, for a front end in another language.

  • iznik

    Plumbing: probe, state, tail, benchmark, doctor, uninstall. It never draws a screen.

Left out

What it will not grow

  • One interface: the app. The iznik command prints structured text.
  • The server remembers a tab’s layout. It never computes a cell size.
  • No second backend. The host gets iznik-server, or a reason it didn’t.