Source code for 🔗 https://progapanda.org
A terminal in your browser. Every visitor gets a private, network-less container running a Go TUI portfolio.
- The browser loads Xterm.js and opens a WebSocket to
/term. - The Go server starts a throwaway Docker container per visitor
(
--network none, 0.1 CPU, 64M,--rm) behind a PTY. - PTY bytes and keystrokes are piped over the socket, raw. Xterm owns UTF-8 decoding, because a multibyte character can straddle two messages.
The TUI inside the container is cmd/hello2 in this repo. It began life as a
separate repository and its history came along with it.
Wire format is binary frames, first byte is the type: 0 raw terminal bytes,
1 a JSON {"rows":n,"cols":n} resize. The first message a client sends must
be a resize — the server needs it to size the PTY.
| path | what |
|---|---|
cmd/webterm |
the web server: WebSocket, PTY, one container per visitor |
cmd/hello2 |
the TUI that runs inside that container |
cmd/hello2/content.md |
all the prose — edit this to change the copy |
visitor |
everything else that ships in the visitor container |
src |
Xterm.js frontend, built with Vite |
k8s |
Deployment, Service, Ingress, cert |
tools/icons.py |
regenerates the favicon and link-preview image |
tools/stripeek_fixture.py |
regenerates the scrubbed stripeek capture |
cmd/hello2/content.md is embedded into the TUI binary at build time. Every
# heading starts a new section and becomes its menu entry, in file order.
Within a section, blank lines separate blocks and the opening characters pick
the style:
## heading
- bullet headline, or a [label](https://url) to make it clickable
continuation text, hangs under the bullet
[Label](https://url)
[Another](https://url)
anything else is a paragraphSource line breaks are ignored — text is re-wrapped to the terminal's real
width, so content.md stays readable (and renders on GitHub).
The original TUI (./hello, 2020) was built with
tview and its source was lost. Both binaries
live in the visitor image so they can be compared: the site pre-fills
./hello2, replace it with ./hello at the prompt.
The container also carries stripeek,
a Stripe API/webhook inspector, pinned by version in Dockerfile.visitor. There
is no network and no Stripe key, so it runs against a captured session loaded
from STRIPEEK_HISTORY_PATH — 65 calls and 35 webhooks, including subscription
schedules and the whole invoice lifecycle.
Stripe object ids embed a fragment derived from the account that made them, so a
raw capture leaks the account id in every id it contains.
tools/stripeek_fixture.py rewrites that fragment consistently — ids are opaque
strings, so every cross-reference survives — and refuses to write a fixture that
still contains it.
visitor/stripeek is a wrapper, not the binary. On a coarse pointer or under
80 columns it prints a note and exits, because the inspector is wide and
keyboard-driven. stripeek.bin is still there for anyone who insists.
- Go, gorilla/mux + gorilla/websocket, creack/pty
- Xterm.js 💻, built with Vite
- Bubble Tea for the TUI 📺
- Docker 🚢 — Docker-in-Docker sidecar per pod
- k3s with k3sup 🍅 on a Digital Ocean droplet
make dev # the whole site on :4567
make run-tui # just the TUI, in this terminal
make test
make deploy # build and push both images, apply, roll the podsThere are two images: the web server, and the one every visitor gets. deploy
builds and pushes both, then rolls the pods.
Rolling recreates the Docker-in-Docker sidecars, whose image cache is an
emptyDir, so each one pulls the visitor image back for itself. The readiness
probe is what makes that safe — a pod stays out of rotation until it holds the
image, because otherwise it would take traffic first and docker run would
quietly serve whatever the registry tag pointed at previously.
Anything that ships inside the container — the TUI, its copy, the welcome
screen — therefore needs a full make deploy, not a shortcut.
Deploys need KUBECONFIG pointed at the k3s cluster (defaults to
~/kubeconfig).
↑/↓ navigate · enter read (scroll the pane) · tab switch panes · esc back to menu · q quit
This started as research into scalable online coding environments for programming students at Le Wagon. It stuck around as a portfolio.
- lewagon/wait-on-check-action, the GitHub Action that can be used to halt any workflow until required checks for a given ref pass successfully.
- lewagon/foot_traffic, pure Ruby DSL for Chrome scripting based on Ferrum. No Selenium required. Works from any script. Simulate web app usage scenarios in production or locally.
- lewagon/quay-github-actions-dispatch, a tiny web service for securely forwarding Quay build notifications to Github Action's repository_dispatch webhook. A missing link for creating powerful build flows with Quay and GHA.
Andy Baranov — andrey@hey.com
MIT