Serving a TUI App Over SSH
Learning to serve a terminal application over SSH just to see how it works
Serving a TUI App Over SSH
I've been building a few TUI apps over the past few weeks and wanted to share an approach that's helped me keep them organized. Plus I wanted to learn how to serve them over SSH - not because anyone asked for it, but because serving terminal apps over SSH seemed like a cool thing to figure out.
Turns out there's more to SSH server setup than I expected when you're learning infrastructure by breaking things. The idea was simple: instead of users installing apps locally, they just ssh your-server
and get the TUI directly. But the implementation has gotchas.
The Problem: SSH Server Setup
When you want to serve a TUI app over SSH, you hit all the infrastructure problems:
- SSH key generation and management
- Authentication setup
- Terminal compatibility across different SSH clients
- Session handling
- Error handling when connections drop
And with all the other shit you have to learn to build websites, figuring out SSH server implementation from scratch is mad annoying.
I know that developers are probably laughing here, but when you're learning infrastructure by breaking things, even basic SSH setup has more gotchas than you expect.
The Solution: Charm's Wish Library
Instead of building SSH server infrastructure from scratch, Charm's Wish library handles all the annoying parts. You just wrap your Bubble Tea app and get a working SSH server.
# User connects to your server
ssh your-server -p 2234
# Gets your TUI app directly
# No installation, no setup, just works
Wish handles the SSH complexity, you handle the application logic.
Template for Organization
After building a few TUI apps, I found myself copying the same architecture patterns. Rather than figuring out the SSH server setup from scratch each time, I built a template that shows the patterns that worked:
git clone https://github.com/williavs/tui-template.git my-app
cd my-app
go mod edit -module my-app
go build -o my-app .
./my-app
The template gives you the patterns I found useful:
- Working SSH server setup with Charm's Wish library
- 3-layer architecture that keeps things organized (routing, handlers, views)
- Example pages showing different UI patterns
- Ed25519 key generation
- Responsive layouts that work over SSH
The architecture pattern helped me avoid the mess of putting everything in one giant file when building multiple TUI apps.
How Wish Works
Wish provides middleware that wraps your Bubble Tea app for SSH serving. The basic pattern is:
- Create your normal Bubble Tea app
- Wrap it with
bubbletea.Middleware()
- Add SSH server configuration
- Users connect via SSH and get your TUI
The SSH server handles:
- Key generation (Ed25519)
- Authentication
- Terminal compatibility
- Session management
- Connection handling
You just write normal Bubble Tea code and Wish makes it work over SSH.
Implementation Details
The template uses Charm's Wish library for SSH server setup. This was the part I had to figure out from examples:
// main.go
server, err := wish.NewServer(
wish.WithAddress("localhost:2234"),
wish.WithHostKeyPEM(k.RawPrivateKey()),
wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
return true // Allow all keys for demo
}),
wish.WithMiddleware(
bubbletea.Middleware(func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
return initialAppModel(), []tea.ProgramOption{
tea.WithAltScreen(),
}
}),
activeterm.Middleware(), // Better terminal support
logging.Middleware(), // Request logging
),
)
The key part is bubbletea.Middleware()
which wraps your Bubble Tea app for SSH serving. Once you have this setup, everything else is standard Bubble Tea code - the SSH layer is handled for you.
Results
You get an SSH-served TUI app that works like this:
# Connect to your server
ssh localhost -p 2234
# Get your interactive TUI directly
# esc: back to home, q: quit, arrows: navigate
The template shows three different page layouts to demonstrate patterns:
- Simple lists
- Categorized content
- Card-based displays
Anyone can connect with any SSH client and get a responsive terminal interface. No installation required on their end. Though honestly, nobody's asking to connect to my homelab TUI apps - this was just about learning how SSH serving works.
Implementation Notes
SSH key management: Wish handles Ed25519 key generation and stores keys in .wishlist/
. I'm still figuring out proper key management for production use.
Terminal compatibility: The activeterm.Middleware()
improves compatibility across different SSH clients and terminal emulators. This fixed issues I was having with some terminals not displaying correctly.
Session handling: Each SSH connection gets its own Bubble Tea app instance. Multiple users can connect simultaneously without interfering with each other.
The main benefit is that you can build a TUI app with normal Bubble Tea patterns and serve it over SSH without getting deep into SSH server implementation. Wish handles the SSH complexity, you handle the application logic. For a first homelab setup, this removes a lot of the networking headaches I was trying to avoid.
From Wish Repo
With Wishlist you can have a single entry point for multiple SSH endpoints, whether they are Wish apps or not.
As a server, it can be used to start multiple SSH apps within a single package and list them over SSH. You can list apps provided elsewhere, too.