In-terminal WPM test. Optional multiplayer, no install. ssh termkeys.xyz
Find a file
EvilEKo aa0f09c224 refactor: move UI into internal/tui, entrypoint to cmd/termkey
Root is now clean: cmd/termkey (main) + internal/{theme,corpus,store,tui}.
typing engine and race hub stay with the app in tui — they share the app
model and the engine's internals, so a split would only leak API.
2026-07-16 00:23:43 -04:00
deploy rebrand: termkeys -> termkey (repo, module, units, docs) 2026-07-15 06:48:40 -04:00
internal refactor: move UI into internal/tui, entrypoint to cmd/termkey 2026-07-16 00:23:43 -04:00
.gitignore rename DB file sshtype.db -> termkey.db; tidy gitignore 2026-07-15 23:49:08 -04:00
go.mod rebrand: termkeys -> termkey (repo, module, units, docs) 2026-07-15 06:48:40 -04:00
go.sum feat: moderation (name filter + bans + admin screen); route :22 for bare ssh 2026-07-15 05:29:09 -04:00
LICENSE docs: open-source prep — GPL-3.0 license, public README, comment cleanup 2026-07-15 05:46:03 -04:00
README.md refactor: move UI into internal/tui, entrypoint to cmd/termkey 2026-07-16 00:23:43 -04:00

termkey

License Go Built with Bubble Tea Play

A typing speed test you play over SSH — no install, no signup. Point your terminal at the server and you drop straight into a full-screen TUI: timed tests, word counts, book quotes, live WPM, leaderboards, and real-time multiplayer races.

ssh termkey.xyz

Your SSH key is your identity — no passwords, nothing to install, nothing to sign up for. Anonymous play works immediately; claim a username to save stats and appear on the leaderboards.

Features

  • Modes — timed (15/30/60s), word count (10/25/50), or a random public-domain book quote
  • Live feedback — WPM, accuracy, and a per-second WPM graph after every test
  • Accounts — your SSH public key is your login; link extra machines with a one-time code
  • Stats — personal bests, averages, accuracy trends, recent history
  • Leaderboards — per mode and duration, daily and all-time
  • Races — real-time multiplayer with multi-room lobbies and live progress bars
  • Moderation — automatic username filtering and an admin ban screen

Navigation: arrows / enter to move, b leaderboards, r races, s stats, esc back.

Run it yourself

Requires Go 1.26+.

git clone https://git.evileko.dev/evileko/termkey
cd termkey
go run ./cmd/termkey -addr :2222 -data ./data
ssh -p 2222 localhost
  • -addr — listen address (default :2222)
  • -data — state directory for the SQLite database and generated SSH host key

Build a static binary (pure Go — SQLite included, no cgo, no runtime deps):

CGO_ENABLED=0 go build -o termkey ./cmd/termkey

Deploy

termkey is a single static binary plus a SQLite database. Run it under systemd; hardened unit files are in deploy/:

  • deploy/termkey-test.service — port 2022, leaves your admin sshd untouched (ssh -p 2022 host)
  • deploy/termkey.service — port 22, so bare ssh host works

For the bare-port setup, termkey needs port 22, so move your admin sshd to another port first — and verify you can log in on the new port before you disconnect. Point your ~/.ssh/config at the new admin port afterward.

DNS is just an A record → your server's IP. Behind Cloudflare, set it DNS-only (the proxy doesn't forward SSH). State (database + generated SSH host key) lives in the -data directory and survives restarts.

Moderation

  • Admins are identified by SSH key: put admin public keys (authorized_keys format, one per line) in <data>/admin_keys. Admins get an [m] moderation entry in the menu — filter the user list, ban and unban.
  • Username filtering is automatic and leetspeak-aware via go-away. Banned users are dropped from the leaderboards and blocked on connect.

How it works

Built on the charm stack — wish (SSH server), bubbletea (TUI), and lipgloss (styling) — with modernc.org/sqlite for storage (pure Go). It's a single package main, no internal packages.

All timing happens server-side and pastes are ignored, so results reflect real keystrokes. termkey only ever runs the TUI: it never spawns a shell and registers no exec, SFTP, or port-forwarding handlers, so a connecting user can't reach anything but the game. The systemd unit sandboxes it further (DynamicUser, seccomp, read-only filesystem, dropped capabilities).

License

GPL-3.0 © EvilEKo