← Back to home

Architecture Overview

How TermOnMac Works

TermOnMac is a zero-knowledge relay between your iPhone and your Mac. The iOS app, the Mac daemon, and the Cloudflare Workers relay each do a specific job — and everything interesting happens at the boundaries. This page walks through the whole system in nine sections, each linking to a deeper engineering note grounded in the shipping source code.

1. The Three Parts

TermOnMac has three components: an iOS app, a Mac CLI daemon, and a relay server on Cloudflare Workers. All terminal data is end-to-end encrypted between the Mac and iPhone — the relay forwards ciphertext it cannot read.

2. Pairing & Identity

Pairing is a QR code scan. The QR carries a room_id and a one-time token. After pairing, the Mac's identity key is locked in via trust-on-first-use, so later reconnects are verified against the same key.

3. Cryptography

Every connection derives a fresh session key. Identity keys are persistent, per-connection ephemeral keys give forward secrecy, and an HMAC over the sorted ephemeral keys prevents a compromised relay from pulling off a key substitution.

4. The PTY

On the Mac side, each session is a real login shell spawned via forkpty(). The helper reads from the PTY without blocking a thread, buffers output in a way that doesn't flood the wire, and resizes correctly when the iPhone keyboard appears.

5. The Relay

The relay is a Cloudflare Worker plus a Durable Object per room. DOs hibernate when idle, so we rebuild state from storage on every wake. Quota is credit-based with a 5-hour rolling window, and room limits are enforced by a KV prefix scan — no counters, no cleanup job.

6. Reliability

Mobile networks drop, switch, and wake. TermOnMac reconnects on its own — exponential backoff with an immediate reconnect on a detected network change, a heartbeat cadence that keeps the relay DO awake during active use, and replay of missed output from the ring buffer.

7. The iOS Side

On the iPhone, the terminal is SwiftTerm wrapped in a UIViewRepresentable. A custom keyboard toolbar fills in the keys that iOS doesn't have, and scroll position is preserved even when new output arrives and when the PTY is reset on reconnect.

8. Auth, Billing, and Integration

The Mac CLI talks to its helper daemon over a Unix socket. The iOS app signs in via GitHub, Google, or Apple, and those identities are linked into a single user by email. App Store subscriptions are verified by hand-parsing Apple's JWS receipts, and subscription notifications are deduplicated across retries.

9. Recurring Patterns

Across the relay, the Mac agent, and the iOS app, the same few ideas come up again and again: strong consistency inside a Durable Object, split KV keys to avoid read-modify-write races, version markers, lazy expiry, and channel binding wherever two parties share a secret.