Development
Run locally without Cloudflare#
npm install
AUTH_DISABLED=1 node server.js
# → http://127.0.0.1:7681, acting as your own user
AUTH_DISABLED=1 skips the Access check and is refused when running as root. Never set it on a server that is reachable through the tunnel.
Tests#
npm test
scripts/smoke-test.js starts the server twice:
- with auth disabled: creates a session over the API, checks that invalid names and cross-origin requests (HTTP and WebSocket) are refused, attaches through the WebSocket, disconnects, checks that the session survived, then kills it.
- with auth enabled: checks that requests and WebSocket upgrades without a valid Access JWT get a 401.
Shell scripts are checked with shellcheck -x install.sh bin/cloudflare-tmux scripts/*.sh. CI runs both.
Project layout#
server.js Express + WebSocket server: auth, tmux API, PTY bridge
public/ the web UI (dashboard, xterm.js terminal, file panel, mobile keys)
lib/files.js file helper run as the mapped user: list, stat, read, write
install.sh one-command interactive setup
bin/cloudflare-tmux the day-to-day command: status, people, add, remove, config, logs, restart, doctor
scripts/cloudflare.sh Cloudflare API provisioning (tunnel, IdP, Access, DNS)
scripts/people.sh add/remove/list people (behind cloudflare-tmux add|remove|people)
scripts/doctor.sh health checks with fixes (behind cloudflare-tmux doctor)
scripts/setup.sh systemd user services (single-user)
scripts/install-system.sh root system service (multi-user)
scripts/totp.js enroll authenticators for the two-step code
scripts/smoke-test.js end-to-end tests
deploy/*.service systemd unit templates
docs/ this documentation site (Tailwind)
The WebSocket protocol#
GET /ws?session=<name>&cols=<n>&rows=<n> (same-origin, authenticated) attaches tmux new-session -A -s <name>.
- Server → client: terminal output (text frames).
- Client → server: keyboard input as text frames. Control messages are JSON prefixed with
\x00, currently only{"type":"resize","cols":120,"rows":40}.
HTTP API (all under Access):
| Method | Path | Body | Result |
|---|---|---|---|
| GET | /api/me |
{ email, user, local } |
|
| GET | /api/sessions |
[{ name, windows, attached, activity, created }] |
|
| POST | /api/sessions |
{ name } |
201 |
| POST | /api/sessions/:name/rename |
{ name } |
200 |
| DELETE | /api/sessions/:name |
204 |
Docs site#
The site is plain Markdown in docs/content/, built into static HTML with a small Node script and styled with Tailwind CSS.
cd docs
npm install
npm run build # → docs/dist
npm run dev # build + serve locally
- Navigation order: the
NAVlist indocs/build.mjs. Each page hastitle:anddescription:front matter. - Callouts: GitHub syntax, for example
> [!TIP],> [!WARNING]. - Link other pages by their
.mdpath; links are rewritten to.html. - Pushing to
maindeploys to GitHub Pages through.github/workflows/docs.yml.