Development setup
Prerequisites
Section titled “Prerequisites”| Tool | Version | Needed for |
|---|---|---|
| Go | 1.26 | Every Go module. GOTOOLCHAIN=auto (the Makefile’s default) lets an older go fetch it. |
| Node.js | 26 (24 works) | The desktop UI, the relay’s admin UI, this documentation site, and the TypeScript protobuf bindings. |
| golangci-lint | v2.6.2+, built with Go 1.26 | make lint. |
| Docker | any recent | Redis and the deployment stack. |
golangci-lint refuses to analyse a module whose go directive is newer than the Go it was built
with, so install it with the matching toolchain:
GOTOOLCHAIN=go1.26.0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.2Everyday targets
Section titled “Everyday targets”make build # compile every Go modulemake test # go test -race, every modulemake lint # go vet + golangci-lint, every modulemake fmt # gofumpt, falling back to gofmtmake tidy # go mod tidy + go work syncmake proto # regenerate protobuf bindings (Go + TypeScript)make proto-check # fail if the committed generated output is stalemake run-server # go run ./cmd/tpp from server/make help # everything elsemake run-server reads .env from server/ and needs a Redis it can reach (TPP_REDIS_ADDR,
default 127.0.0.1:6379).
Run the relay’s admin UI from a checkout
Section titled “Run the relay’s admin UI from a checkout”The admin page is a React app in server/ui. The relay binary embeds its build, so build it
before the binary (or before make run-server) if you want the real page; without it the
admin screens show a page saying the UI is not built. The Go tests do not need it.
cd server/ui && npm ci && npm run build # builds into ../web/admin/distcd .. && go run ./cmd/tppFor UI work, run Vite next to a running relay. It serves the app at
http://127.0.0.1:5174/admin/ and forwards /admin/api/, the token QR images and /source to
the relay, so sign-in, the session cookie and the CSRF check behave as in production:
make run-server # the relay, on :8080cd server/ui && npm run dev # http://127.0.0.1:5174/admin/TPP_ADMIN_DEV_RELAY=http://127.0.0.1:9090 npm run dev # a relay elsewhereWith an http:// TPP_PUBLIC_BASE_URL the session cookie is not marked Secure, which is what
lets this work without TLS.
The service worker that makes the admin installable and lets it open offline is
server/ui/sw/sw.js. npm run build writes it into the output as sw.js, with the list of
built files it keeps and a version taken from their contents; the dev server registers none.
It never touches /admin/api/ or a token’s QR image. Its icons come from
assets/icon/generate.py like every other icon, but from their own artwork,
assets/icon/icon-admin.svg (the app’s clipboard with a person on it), so an installed admin is
told apart from the app.
Every build also has a build id, a digest of the built files, computed by the same Vite
plugin (buildFiles in vite.config.ts). It is written into the bundle (src/lib/build.ts),
into version.json, which the relay serves as /admin/version.json, and into the worker as its
version. The running app polls version.json and shows an Update bar at the bottom when the
relay serves a different id (src/hooks/useUpdateCheck.ts, src/components/UpdateBar.tsx). A
rebuild with any change to the UI is a new id, wherever it was built; the dev server has no id
and never checks. To try it, build and run the relay, open the admin, change something in
server/ui, rebuild and restart the relay, and come back to the tab after half a minute.
npm test runs the UI’s unit and component tests (Vitest, in a simulated browser) and
npm run typecheck its type check; CI runs both, and the build, for every change under
server/ui.
make proto needs no protoc: proto/generate.sh builds pinned tools from proto/tools/go.mod
and proto/package-lock.json. Generated output is committed and must stay byte-identical.
Run the desktop app from a checkout
Section titled “Run the desktop app from a checkout”The UI must be built before the Go binary, because the binary embeds it:
cd desktop/ui && npm ci && npm run build # builds into ../internal/localui/distcd .. && go run ./cmd/tppdesktopFor UI work, run Vite and point the service at it. The token and Origin checks still apply:
cd desktop/ui && npm run dev # http://127.0.0.1:5173TPP_DESKTOP_UI_DEV=http://127.0.0.1:5173 go run ./cmd/tppdesktop| Variable | Meaning |
|---|---|
TPP_DESKTOP_CONFIG_DIR |
Where desktop.json lives. Defaults to the per-user config directory. Handy for running two instances side by side. |
TPP_DESKTOP_UI_DEV |
Base URL of a running Vite server; the UI is proxied from there. |
TPP_DESKTOP_LOG |
debug, info, warn, error. Defaults to info. |
TPP_DESKTOP_GITHUB_TOKEN |
A GitHub token for the Nightly update channel, used instead of the stored one. |
TPP_DESKTOP_UPDATE_ALLOW_LOCAL |
1 lets a build from a checkout install updates, to test the updater. |
On Linux the desktop build runs without a clipboard or tray and prints its URL to the terminal.
This documentation site
Section titled “This documentation site”The site lives in docs/site/ and is built with Starlight.
cd docs/sitenpm cinpm run dev # http://localhost:4321/two-place-paste/npm run build # static output in docs/site/dist, with link checkingPages are Markdown or MDX files in docs/site/src/content/docs/; the sidebar is defined in
docs/site/astro.config.mjs. Screenshots live in docs/site/src/assets/screenshots/. Every push to
the default branch that touches docs/site/ publishes the site to GitHub Pages.