# Self-hosted HTTP Toolkit A private distribution repo that builds [HTTP Toolkit](https://httptoolkit.com) into a **single self-contained binary** that runs the backend and serves the web UI locally: ```bash ./httptoolkit --port 7070 # → starts the proxy server and opens the web UI at http://localhost:7070 ``` No Electron, no hosted UI, no account/login. Upstream is tracked as **git submodules** and all local modifications live in this repo as **patches** + an **overlay**, so updating to a new upstream release is a controlled, reviewable step. ## Layout ``` . ├── httptoolkit-ui/ # submodule → github.com/httptoolkit/httptoolkit-ui (pinned) ├── httptoolkit-server/ # submodule → github.com/httptoolkit/httptoolkit-server (pinned) ├── patches/ │ ├── ui/ # diffs against upstream UI files │ └── server/ # diffs against upstream server files ├── overlay/server/ # NEW files copied into the server submodule (never conflict) │ ├── htk-entry.ts # single-file binary entry (Bun) │ └── src/ │ ├── htk-app.ts # shared: run backend + serve UI + open browser │ └── commands/app.ts # oclif fallback command ├── scripts/ │ ├── apply.sh # reset submodules → apply patches → copy overlay │ ├── build.sh # build UI → embed → bun compile → dist/ │ └── smoke.sh # launch binary & verify it serves + backend is up ├── .build/prod/ # Docker builder (Gitea `docker-compose` runner) │ ├── Dockerfile # Node + Bun + native toolchain │ ├── compose.yml # mounts repo, runs build.sh │ ├── build.sh # build + smoke → out/ │ └── release.sh # Gitea-API release + upload └── .gitea/workflows/build.yml ``` ## What the patches change | Kind | File | Change | |------|------|--------| | UI patch | `src/model/account/account-store.ts` | injects a synthetic active Pro subscription | | UI patch | `src/components/settings/settings-page.tsx` | removes the account/subscription card | | UI patch | `src/components/app.tsx` | removes the "Give feedback" sidebar button | | Server patch | `src/constants.ts` | allow `localhost`/`127.0.0.1` origins in prod builds | | Overlay | `htk-entry.ts`, `src/htk-app.ts`, `src/commands/app.ts` | serve the UI locally + `--port` | **Security note:** upstream restricts a packaged build's API to `https://app.httptoolkit.tech` so no *other* local page can drive your proxy. Because we self-host the UI on localhost we must allow localhost origins (this matches upstream's own dev-mode allowlist). Trade-off: any local HTTP page could talk to the server while it's running. Acceptable for a personal tool. ## Build locally Requires **node ≥22**, **npm**, **bun**, and **tar**. ```bash git submodule update --init --recursive bash scripts/build.sh # → dist/httptoolkit bash scripts/smoke.sh # optional: verify it runs ``` Run it: ```bash ./dist/httptoolkit --port 7070 # UI on :7070 ./dist/httptoolkit --no-open # don't auto-open a browser ./dist/httptoolkit --help ``` ## Updating to a new upstream release ```bash # bump a submodule to a new tag cd httptoolkit-ui && git fetch && git checkout v && cd .. git add httptoolkit-ui # re-apply and see if patches still fit bash scripts/apply.sh ``` If a patch no longer applies, `apply.sh` fails and names the patch. Fix it: ```bash cd httptoolkit-ui git checkout -- . # reset # hand-apply the change, then regenerate the patch: git diff -- > ../patches/ui/000X-....patch ``` Overlay files never conflict (they're new files), so only the small `patches/` diffs ever need attention on upgrade. ## Packaging notes / risks - **Single file via Bun `--compile`.** The server pulls in native `.node` addons (`node-datachannel`, `registry-js`, `adbkit`, …). These **cannot be cross-compiled**, so the binary is built **per host platform**. `scripts/smoke.sh` is the guard: if a native addon won't load from the embedded FS, it fails there. - **Linux only, for now.** CI builds inside a Linux Docker container, which produces a trustworthy Linux x64 binary. Windows/macOS binaries are **not** reliably buildable from Linux Docker (foreign-platform native addons can't be produced or smoke-tested there, and macOS needs signing) — they'd need real Windows/macOS runners. - The UI is embedded as `ui.tar.gz` and extracted to `$TMPDIR/httptoolkit-ui-` on first run. - No `HTK_SERVER_TOKEN` is set, so the local UI talks to the backend without a token. ## CI (Gitea Actions, Docker) Matches the `docker-compose` runner convention: - **`.gitea/workflows/build.yml`** — on a `v*` tag (or manual dispatch): checks out with submodules, runs `docker compose run --rm prod` in `.build/prod/`, then creates a Gitea release and uploads the artifacts via the Gitea API (`secrets.PAT_GITEA`). - **`.build/prod/`** — the Docker builder: - `Dockerfile` — Node 22 + Bun + native-build toolchain - `compose.yml` — mounts the repo at `/source`, caches npm/bun - `build.sh` — runs `scripts/build.sh` + `scripts/smoke.sh`, drops the binary in `out/` - `release.sh` — Gitea-API release + asset upload Tag a release: ```bash git tag v1.27.1 && git push origin v1.27.1 ```