{"id":51824229,"url":"https://github.com/htlin222/ttyd-tmux-cf","last_synced_at":"2026-07-22T07:33:38.980Z","repository":{"id":359630904,"uuid":"1246904962","full_name":"htlin222/ttyd-tmux-cf","owner":"htlin222","description":"ttyd + tmux + Cloudflare Zero Trust — log into a persistent web terminal from any browser. Single-prompt Claude Code setup.","archived":false,"fork":false,"pushed_at":"2026-06-13T17:05:11.000Z","size":40,"stargazers_count":15,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-13T19:06:55.956Z","etag":null,"topics":["claude-code","cloudflare-r2","cloudflare-tunnel","cloudflare-zero-trust","launchd","macos","nerd-fonts","tmux","ttyd","web-terminal"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/htlin222.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":".zenodo.json","notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-22T17:26:55.000Z","updated_at":"2026-06-13T17:05:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/htlin222/ttyd-tmux-cf","commit_stats":null,"previous_names":["htlin222/ttyd-tmux-cf"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/htlin222/ttyd-tmux-cf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/htlin222%2Fttyd-tmux-cf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/htlin222%2Fttyd-tmux-cf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/htlin222%2Fttyd-tmux-cf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/htlin222%2Fttyd-tmux-cf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/htlin222","download_url":"https://codeload.github.com/htlin222/ttyd-tmux-cf/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/htlin222%2Fttyd-tmux-cf/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35753457,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-22T02:00:06.236Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","cloudflare-r2","cloudflare-tunnel","cloudflare-zero-trust","launchd","macos","nerd-fonts","tmux","ttyd","web-terminal"],"created_at":"2026-07-22T07:33:37.906Z","updated_at":"2026-07-22T07:33:38.972Z","avatar_url":"https://github.com/htlin222.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ttyd-tmux-cf\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![CI](https://github.com/htlin222/ttyd-tmux-cf/actions/workflows/lint.yml/badge.svg)](https://github.com/htlin222/ttyd-tmux-cf/actions/workflows/lint.yml)\n[![Platform: macOS](https://img.shields.io/badge/Platform-macOS-000000?logo=apple\u0026logoColor=white)](https://www.apple.com/macos/)\n[![Cloudflare Zero Trust](https://img.shields.io/badge/Cloudflare-Zero%20Trust-F38020?logo=cloudflare\u0026logoColor=white)](https://www.cloudflare.com/zero-trust/)\n[![Cloudflare R2](https://img.shields.io/badge/Cloudflare-R2-F38020?logo=cloudflare\u0026logoColor=white)](https://developers.cloudflare.com/r2/)\n[![ttyd](https://img.shields.io/badge/ttyd-1.7%2B-181717.svg?logo=gnometerminal\u0026logoColor=white)](https://github.com/tsl0922/ttyd)\n[![tmux](https://img.shields.io/badge/tmux-1BB91F.svg?logo=tmux\u0026logoColor=white)](https://github.com/tmux/tmux)\n[![Python](https://img.shields.io/badge/Python-%3E%3D3.10-3776AB.svg?logo=python\u0026logoColor=white)](https://www.python.org)\n[![Shell: bash](https://img.shields.io/badge/Shell-bash-4EAA25?logo=gnubash\u0026logoColor=white)](https://www.gnu.org/software/bash/)\n[![Claude Code Ready](https://img.shields.io/badge/Claude%20Code-Ready-D97757?logo=anthropic\u0026logoColor=white)](CLAUDE.md)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/htlin222/ttyd-tmux-cf/pulls)\n[![GitHub stars](https://img.shields.io/github/stars/htlin222/ttyd-tmux-cf?style=social)](https://github.com/htlin222/ttyd-tmux-cf/stargazers)\n[![GitHub last commit](https://img.shields.io/github/last-commit/htlin222/ttyd-tmux-cf)](https://github.com/htlin222/ttyd-tmux-cf/commits/main)\n[![Repo size](https://img.shields.io/github/repo-size/htlin222/ttyd-tmux-cf)](https://github.com/htlin222/ttyd-tmux-cf)\n[![Issues](https://img.shields.io/github/issues/htlin222/ttyd-tmux-cf)](https://github.com/htlin222/ttyd-tmux-cf/issues)\n\nDeploy a **persistent web terminal** (ttyd + tmux) on a Mac, gated by **Cloudflare Zero Trust** so you can log in from any browser with email OTP. Nerd Font glyphs served from **Cloudflare R2** for snappy loads on every device.\n\n```\nbrowser ── HTTPS ──\u003e term.example.com (Cloudflare edge)\n                      │  Cloudflare Access challenge\n                      │  (email OTP to your address)\n                      ▼\n                cloudflared tunnel (existing, on your Mac)\n                      │\n                      ▼\n                http://127.0.0.1:7681  (ttyd, loopback only)\n                      │\n                      ▼\n                login shell ($SHELL -l)   (run tmux yourself for a persistent session)\n\nNerd Font glyphs load in parallel from R2:\n  fonts.example.com/jbmono-nerd-{regular,bold}.woff2   (CDN-cached, immutable)\n```\n\n## Why\n\n- Open any browser, OTP login, land in a login shell — run `tmux new -A -s web` for a session that survives reboots and reconnects, or just use the shell directly.\n- No SSH key juggling on the client. Cloudflare handles auth at the edge.\n- Works on phones, tablets, borrowed laptops.\n\n## Prereqs\n\n- macOS with Homebrew. (Linux works too — swap LaunchAgent for systemd; see \"Linux\" section in `CLAUDE.md`.)\n- A Cloudflare account with **a zone you own** (e.g. `example.com` on Cloudflare nameservers).\n- An existing **cloudflared tunnel** running on this Mac. (Don't have one? See [Cloudflare Tunnel quick start](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/) — takes 5 min.)\n- A **Cloudflare API token** with these scopes (least privilege):\n  - `Account → Access: Apps and Policies → Edit`\n  - `Account → Access: Organizations, Identity Providers, and Groups → Read`\n  - `Account → Workers R2 Storage → Edit`\n  - `Zone → Zone → Read` (any zone — used to look up the zone ID)\n- `wrangler` logged in (`wrangler login`) — needed for R2 uploads.\n- A copy of the **JetBrainsMono Nerd Font** TTFs (Regular + Bold) on disk, or willingness to download them. Get them from [nerd-fonts releases](https://github.com/ryanoasis/nerd-fonts/releases) (`JetBrainsMono.zip`). Any Nerd Font works — adjust `.env`.\n\nCosts: tunnel free, Access free up to 50 users, R2 free under 10 GB / 1 M ops/month. Fonts are ~2 MB total — effectively zero.\n\n## Quick start (with Claude Code)\n\n```sh\ngit clone \u003cthis-repo\u003e ~/ttyd-tmux-cf\ncd ~/ttyd-tmux-cf\ncp .env.example .env\n$EDITOR .env   # fill in your hostname, email, token, etc.\n```\n\nThen in this directory, ask Claude Code:\n\n\u003e read the CLAUDE.md and go\n\nClaude will preflight your tools, discover your tunnel/zone/account, create the Cloudflare Access app + policy, wire up the cloudflared ingress + DNS, provision the R2 bucket + custom domain + CORS, build a custom `index.html`, install the ttyd LaunchAgent, and verify end-to-end. It's idempotent — safe to re-run.\n\nWhen it's done, open `https://\u003cyour-hostname\u003e` in any browser, do the email OTP, and you're in tmux.\n\n## Manual run (no Claude)\n\n`CLAUDE.md` is also a readable runbook. Open it; each phase has copy-pasteable shell. Same result, just slower.\n\n## File layout\n\n```\nttyd-tmux-cf/\n├── README.md                  # this file\n├── CLAUDE.md                  # runbook (Claude reads this and executes)\n├── .env.example               # template — copy to .env and fill in\n├── .gitignore\n├── examples/\n│   ├── com.USER.ttyd.plist.template     # LaunchAgent template\n│   ├── cloudflared-ingress-snippet.yml  # ingress fragment to add\n│   └── cors.json                        # R2 CORS policy (ready to use)\n└── scripts/\n    └── build-index.py         # harvest ttyd's index.html and inject @font-face\n```\n\n## Rollback\n\n`CLAUDE.md` has a `Rollback` phase at the bottom. Or by hand:\n\n```sh\nlaunchctl bootout \"gui/$(id -u)/com.${USER}.ttyd\"\n# remove the term.* ingress block from ~/.cloudflared/config.yml\nlaunchctl kickstart -k \"gui/$(id -u)/com.cloudflare.cloudflared.\u003ctunnel-label\u003e\"\n# In Cloudflare dashboard: delete the Access app, delete the R2 bucket/domain.\n```\n\n## Security notes\n\n- ttyd binds to `127.0.0.1` only — never directly reachable, even on LAN.\n- Cloudflare Access enforces identity before the tunnel forwards. Defense in depth: even without Access, the tunnel hostname won't resolve directly to anything but the Cloudflare edge.\n- Don't commit `.env`. `.gitignore` already excludes it.\n- Revoke the API token (`https://dash.cloudflare.com/profile/api-tokens`) once setup is done; it's only needed during deploy.\n\n## Citation\n\nIf you use this project, please cite it:\n\n**BibTeX:**\n\n```bibtex\n@software{lin2026ttydtmuxcf,\n  author  = {Lin, Hsieh-Ting},\n  title   = {ttyd-tmux-cf: Persistent web terminal gated by Cloudflare Zero Trust},\n  year    = {2026},\n  url     = {https://github.com/htlin222/ttyd-tmux-cf},\n  version = {0.1.0}\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eAMA format\u003c/summary\u003e\n\nLin HT. ttyd-tmux-cf: Persistent web terminal gated by Cloudflare Zero Trust. Published online 2026. https://github.com/htlin222/ttyd-tmux-cf\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eAPA format\u003c/summary\u003e\n\nLin, H.-T. (2026). *ttyd-tmux-cf: Persistent web terminal gated by Cloudflare Zero Trust* (Version 0.1.0) [Computer software]. https://github.com/htlin222/ttyd-tmux-cf\n\n\u003c/details\u003e\n\n## License\n\nThis project is licensed under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhtlin222%2Fttyd-tmux-cf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhtlin222%2Fttyd-tmux-cf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhtlin222%2Fttyd-tmux-cf/lists"}