Architecture¶
terminux follows a two-process design: a sandboxed web UI that renders terminals, and a backend that owns the PTYs and streams raw bytes over a narrow, typed channel.
flowchart LR
subgraph Window["pywebview window (WKWebView / WebKit2GTK)"]
UI["xterm.js UI<br/>(TypeScript / Vite)"]
end
subgraph Backend["Loopback backend (Starlette + uvicorn)"]
ASGI["ASGI app<br/>HTTP + per-terminal WebSocket"]
PTY["PTY backend<br/>(ptyprocess)"]
STORE["Versioned state store<br/>(atomic JSON on disk)"]
end
UI <-->|"HTTP + WS<br/>(token-authed, loopback)"| ASGI
ASGI <--> PTY
ASGI <--> STORE
The two processes¶
- Frontend: a Vite/TypeScript app built on xterm.js, rendered inside a
sandboxed pywebview window. The build output
is committed to
src/terminux/web/static/, so the Python package runs with no Node toolchain. - Backend: a loopback Starlette/uvicorn ASGI server. It owns the PTYs
(via
ptyprocess), serves the static UI, and exposes a per-terminal WebSocket that streams raw PTY bytes.
The same backend runs headless in web mode (--host / --no-window), which
is also the dev/test path.
Source layout¶
| Path | Responsibility |
|---|---|
src/terminux/app.py |
Entry point; wires the window + server together. |
src/terminux/server/asgi.py |
ASGI app: HTTP routes + WebSocket handler. |
src/terminux/server/auth.py |
Per-session loopback token. |
src/terminux/core/model.py |
Workspace / tab / terminal data model. |
src/terminux/core/persistence.py |
Atomic, versioned on-disk state. |
src/terminux/core/terminal.py |
Terminal session lifecycle. |
src/terminux/core/pty_backend.py |
PTY spawning and I/O (ptyprocess). |
src/terminux/core/shellprobe.py |
Shell / working-directory probing. |
src/terminux/web/static/ |
Committed frontend build output. |
Key design decisions¶
- Raw-byte streaming with backpressure. Terminal output uses a bounded per-subscriber buffer with coalesced flushing and an explicit drop notice. PTY spawning is serialized.
- Structure and scrollback, never processes. The layout and each tab's visible buffer are saved; live processes are not. State is written atomically and versioned, so a port or version change doesn't lose your workspaces.
- Loopback + token by default. HTTP responses carry a strict CSP and
security headers; a per-session token authenticates every request and
WebSocket. Binding beyond loopback (
--host 0.0.0.0) makes the token the only line of defense. Keep the URL private. - Reliability over features. Small, auditable surface; no editor, AI, git, or file navigator. The data model leaves the door open to split panes without committing to them in v1.
For the full rationale, see notes/vision.md, notes/functional-spec.md, and
notes/technical-spec.md.