{"id":51421118,"url":"https://github.com/cplieger/web-terminal-server","last_synced_at":"2026-07-05T00:01:46.004Z","repository":{"id":368213828,"uuid":"1283997384","full_name":"cplieger/web-terminal-server","owner":"cplieger","description":"Generic web terminal: runs a command in a PTY, serves a touch-first browser UI","archived":false,"fork":false,"pushed_at":"2026-06-29T14:27:45.000Z","size":48,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-29T15:23:30.987Z","etag":null,"topics":["golang","pty","terminal","web-terminal","websocket"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cplieger.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":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-29T12:33:52.000Z","updated_at":"2026-06-29T14:27:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cplieger/web-terminal-server","commit_stats":null,"previous_names":["cplieger/web-terminal-server"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/cplieger/web-terminal-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cplieger%2Fweb-terminal-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cplieger%2Fweb-terminal-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cplieger%2Fweb-terminal-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cplieger%2Fweb-terminal-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cplieger","download_url":"https://codeload.github.com/cplieger/web-terminal-server/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cplieger%2Fweb-terminal-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35139194,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"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":["golang","pty","terminal","web-terminal","websocket"],"created_at":"2026-07-05T00:01:45.129Z","updated_at":"2026-07-05T00:01:45.991Z","avatar_url":"https://github.com/cplieger.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# web-terminal-server\n\n[![Image Size](https://ghcr-badge.egpl.dev/cplieger/web-terminal-server/size)](https://github.com/cplieger/web-terminal-server/pkgs/container/web-terminal-server)\n![Platforms](https://img.shields.io/badge/platforms-amd64%20%7C%20arm64-blue)\n![base: Debian](https://img.shields.io/badge/base-Debian-A81D33?logo=debian)\n[![Test coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/cplieger/web-terminal-server/badges/coverage.json)](https://github.com/cplieger/web-terminal-server/actions/workflows/coverage.yml)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/13432/badge)](https://www.bestpractices.dev/projects/13432)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/cplieger/web-terminal-server/badge)](https://scorecard.dev/viewer/?uri=github.com/cplieger/web-terminal-server)\n[![SBOM](https://img.shields.io/badge/SBOM-SPDX-1D4ED8)](https://github.com/cplieger/web-terminal-server/releases)\n\nA small, generic web terminal: it runs a configured command in a PTY and serves\nthe [`@cplieger/web-terminal-ui`](https://github.com/cplieger/web-terminal-ui)\nfront end over HTTP + WebSocket, built on the\n[`github.com/cplieger/web-terminal-engine`](https://github.com/cplieger/web-terminal-engine)\nengine. A native-touch terminal in the browser for any command — phone and\ndesktop alike.\n\nPublished as a container image: `ghcr.io/cplieger/web-terminal-server`.\n\n## ⚠️ Security: this is a remote shell\n\nAnyone who can reach the server **and pass auth (if configured)** gets an\ninteractive process running `WT_CMD` with this server's privileges. Treat it\nlike exposing SSH.\n\n- **The binary binds `127.0.0.1` by default.** Reachable only from the same\n  host until you change `WT_ADDR`.\n- **The container image binds `:7681`** (it has to, to be reachable via a\n  published port) and so is **unauthenticated and network-exposed by default**.\n  Before exposing it beyond a trusted host, do **one** of:\n  - set `WT_PASSWORD` (enables HTTP Basic auth on every route, including the\n    WebSocket handshake), and/or\n  - front it with an authenticating reverse proxy (Caddy + forward-auth,\n    oauth2-proxy, Authentik, …), and/or\n  - keep the published port bound to loopback / a private network only.\n- The server logs a loud warning at startup when it is listening on a\n  non-loopback address without `WT_PASSWORD` set.\n\nBuilt-in Basic auth is a convenience for simple setups; a reverse proxy with\nreal identity is the recommended posture for anything internet-facing. The\nprocess runs as the container user (root by default) — restrict it with a\nnon-root `WT_CMD` target, a read-only root filesystem, dropped capabilities,\nand a scoped work directory as your threat model requires.\n\n## Run\n\n```sh\ndocker run --rm -p 127.0.0.1:7681:7681 \\\n  -e WT_PASSWORD=changeme \\\n  -v \"$PWD\":/work -e WT_WORKDIR=/work \\\n  ghcr.io/cplieger/web-terminal-server\n```\n\nOpen \u003chttp://127.0.0.1:7681\u003e. The example binds the published port to loopback\nand sets a password; adjust for your environment.\n\n## Configuration\n\nAll configuration is via environment variables:\n\n| Variable | Default (binary / image) | Purpose |\n| --- | --- | --- |\n| `WT_ADDR` | `127.0.0.1:7681` / `:7681` | Listen address. The binary defaults to loopback; the image must listen on all interfaces. |\n| `WT_CMD` | `/bin/bash` | Command to run in the PTY, whitespace-split (use a wrapper script for complex commands). |\n| `WT_WORKDIR` | _(process default)_ | Working directory for the command. Must be an existing directory if set. |\n| `WT_SCROLLBACK` | `5000` | Lines of scrollback the server retains for reconnect replay. |\n| `WT_IDLE_REAPER` | _(unset → disabled)_ | Go duration (e.g. `30m`); when \u003e 0, idle sessions are reaped after this long. |\n| `WT_USERNAME` | `admin` | Basic-auth username (only used when `WT_PASSWORD` is set). |\n| `WT_PASSWORD` | _(unset → no auth)_ | Basic-auth password. When set, every route (including `/ws`) requires it. |\n\nEndpoints: `/` (UI), `/ws?session=\u003cid\u003e` (per-session terminal WebSocket), `/api/sessions` (create/list/close), `/api/sessions/events` (status SSE), `/healthz` (readiness).\n\n## How it fits together\n\n```\ngithub.com/cplieger/web-terminal-engine   (Go engine: PTY + VT screen + wire protocol)\n        │\n        ├── terminal.NewSessionManager ─────►  this server (main.go)\n        │\n@cplieger/web-terminal-engine  +  @cplieger/web-terminal-ui   (TS engine + UI)\n        └── compiled to static/vendor/ at image build, served to the browser\n```\n\nThe server is deliberately thin: env parsing, `terminal.NewSessionManager`, the\nsession REST API + status SSE, a create rate limit, static file serving, optional\nBasic auth, and graceful shutdown. All terminal behavior lives in the engine and\nUI packages.\n\n## Related projects\n\nThe web-terminal family:\n\n- [`web-terminal-engine`](https://github.com/cplieger/web-terminal-engine) — the\n  Go session engine + TypeScript browser renderer this server embeds.\n- [`@cplieger/web-terminal-ui`](https://github.com/cplieger/web-terminal-ui) —\n  the touch-first browser UI this server ships to the client.\n\nApps built on the same engine:\n\n- [`vibekit`](https://github.com/cplieger/vibekit)\n- [`vibecli`](https://github.com/cplieger/vibecli)\n\n## Disclaimer\n\nThis project is built with care and follows security best practices, but it is intended for personal / self-hosted use. No guarantees of fitness for production environments. Use at your own risk.\n\nThis project was built with AI-assisted tooling using [Claude Opus](https://www.anthropic.com/claude) and [Kiro](https://kiro.dev). The human maintainer defines architecture, supervises implementation, and makes all final decisions.\n\n## License\n\nGPL-3.0-or-later. See `LICENSE`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcplieger%2Fweb-terminal-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcplieger%2Fweb-terminal-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcplieger%2Fweb-terminal-server/lists"}