Wish: SSH apps in Go, from Bubble Tea to Git hosting
Make SSH apps, just like that! đź’«
At a glance
- What is it?
- Wish is Charm's Go library for building SSH applications: an SSH server with sensible defaults and a middleware stack for Bubble Tea TUIs, Git hosting, logging and access control. Version 2 lives at charm.land/wish/v2 and was last released as v2.0.4 on 2026-09-10.
- Who is it for?
- Use Wish when the terminal is the right client for your app and you want key-based identification and encryption without running a web stack. Use gliderlabs/ssh directly if you only need the server without the middleware conveniences, and plain openssh-server when a shell is genuinely all you serve.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 20 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.
Editorial analysis
SSH apps without openssh-server
Wish's founding observation is that SSH is a protocol, in the same sense as HTTP or SMTP, and not merely a way to reach a shell on a server. Treating it as an application platform buys three things the README names directly: secure communication without the hassle of HTTPS certificates, user identification through SSH keys, and access from any terminal, since a client is installed on effectively every developer machine. Wish is a Go library for writing these custom servers, an SSH server with sensible defaults plus a collection of middlewares, built on gliderlabs/ssh and easy to fold into existing projects. Safety follows from the scope: OpenSSH is never used nor needed, you could uninstall it, and there is no default behavior that shares a shell, so a Wish app cannot accidentally hand out interactive access it never defined.
Middlewares compose first to last, so the last runs first
Middlewares are the programming model, and the README is explicit that they are analogous to those in HTTP frameworks: handlers that do a specific task and then call the next one. The ordering rule deserves a second read, because composition from first to last means the last middleware in your list is the first one executed. The documented set covers the visible use cases: bubbletea for serving terminal UIs, git for repository hosting, logging for connection records, activeterm to require an active terminal, and accesscontrol to restrict allowed commands. The repository tree adds more that the README text does not dwell on: scp for file transfer, ratelimiter, recover, elapsed and comment packages, each a directory at the root. The honest alternative is staying on gliderlabs/ssh itself, which gives you the server without this stack, and Wish is what you reach for when the conveniences outweigh the dependency.
Every session gets its own tea.Program
The bubbletea middleware is the bridge to Charm's terminal UI framework, and its mechanics are per-session rather than global: each SSH connection gets its own tea.Program with the SSH pty input and output connected, and client window dimension and resize messages are natively handled by that program. That per-session property is what makes multi-user SSH applications sane, since one user's render loop never touches another's terminal state. The middleware is demonstrated on a live endpoint, ssh git.charm.sh, which is also the Charm project's own Git host, so the example is production traffic rather than a toy. Under the hood the v2 dependency graph pulls charm.land/bubbletea/v2 plus lipgloss for styling, which places Wish at the center of the Charm stack rather than beside it, and golang-lru alongside golang.org/x/time appear as direct dependencies, the kind of primitives a connection-heavy service keeps close.
Git hosting as a middleware, with a caveat
The git middleware turns any Wish server into a Git host, supporting repository creation on initial push and custom public key based authentication, so pushing to a nonexistent repo brings it into being and access decisions ride on SSH keys you already control. One requirement is stated without apology: git must be installed on the server. That sits beside a curious fact in go.mod, where github.com/go-git/go-git/v5 appears among the direct dependencies, a pure Go Git implementation, meaning the tree carries both the external git binary requirement and an in-process Git library. The README does not explain the division of labor between them, and anyone building serious Git infrastructure on Wish should treat that split as the first thing to investigate in the source.
A default server that generates its own keys
For the common case, Wish ships a default server that is always authenticating and generates the server key automatically, with the charmbracelet/keygen dependency visible in go.mod doing that work. Around it, the access-control pair handles the edges of a public endpoint: activeterm only allows connections that arrive with an active terminal attached, which filters out non-interactive probes, and accesscontrol lets you enumerate the allowed commands so anything else is refused. Logging covers both ends of a connection with specifics: connects record the remote address, invoked command, TERM setting, window dimensions and whether authentication was public key based, while disconnects record the remote address and total connection duration. Add the ratelimiter package from the tree and the outline of a publicly exposed service is complete without leaving the repository.
From a simple example to a systemd unit
The examples directory is broad enough to learn from by filename alone: simple, bubbletea, git, cat, cobra, exec, forward, graceful-shutdown, identity, multi-auth, multichat, pty, scp and banner cover starting points for most shapes of SSH application. Deployment documentation is practical, down to a systemd unit with Type=simple, a dedicated user and Restart=on-failure:
[Unit]
Description=My App
After=network.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/home/myapp/
ExecStart=/usr/bin/myapp
Restart=on-failure
[Install]
WantedBy=multi-user.targetThe workflow after editing the unit is the standard pair, daemon-reload then start, and the docs suggest creating the dedicated user first:
useradd --system --user-group --create-home myappFor local development, a tip in the README saves known_hosts churn by pointing localhost at /dev/null for that file:
Host localhost
UserKnownHostsFile /dev/nullWith that in place, repeated host key changes on test servers stop polluting your SSH state, which matters because the default server generates its key automatically, so every fresh local instance arrives with a new identity.
V2 at charm.land, and a gallery of built apps
Version 2 is where the project lives now: go.mod declares the module as charm.land/wish/v2 on Go 1.26.8, an UPGRADE_GUIDE_V2.md sits at the repository root for the migration, and releases run v2.0.2 on 2026-07-30, v2.0.3 on 2026-07-31 and v2.0.4 on 2026-09-10, which was also the last push. The proof of the platform is the applications list: Soft Serve for Git hosting, Wishlist for app directories, VHS for terminal recording, SSHWordle and clidle as games, pico.sh as a hosted service, plus Fztea, SSH Slides and others. When the ecosystem around a library includes both games and production services, the middleware abstraction is carrying real weight. Wish is MIT licensed, part of Charm, with feedback channels on Discord, Twitter and the Fediverse.
Editorial conclusion
Use Wish when the terminal is the right client for your app and you want key-based identification and encryption without running a web stack. Use gliderlabs/ssh directly if you only need the server without the middleware conveniences, and plain openssh-server when a shell is genuinely all you serve. Before adopting, read UPGRADE_GUIDE_V2.md if you are coming from version 1, and confirm git is installed on the host if you plan to serve repositories.
Frequently asked questions
What is Wish, the Go SSH library?
Wish is a Go library from Charm for building SSH applications: an SSH server with sensible defaults plus composable middlewares for serving Bubble Tea programs, Git hosting, logging and access control. It is built on gliderlabs/ssh and needs no OpenSSH on the server.
Can Wish serve a TUI over SSH?
Yes. The bubbletea middleware serves any Bubble Tea application over SSH, giving each session its own tea.Program with the SSH pty input and output connected, and handling window size and resize messages natively. A live demo runs at ssh git.charm.sh.
Does a Wish server expose a shell?
No. The documentation states there is no default behavior that shares a shell, and OpenSSH is never used nor needed. What a client can do is defined by the middlewares and access controls you compose.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/charmbracelet-wish)