# Installing klanker-gate (Frosty Deno) on Arch Linux The complete Arch port runbook. Read section 0 first — it records the actual porting verdict, because it is probably not what you expected. > **Upstream attribution:** klanker-gate is **not SysDeck's code**. It > is the "Frosty Deno" LLM gateway by **TykoDev** — > https://github.com/TykoDev/klanker-gate — licensed Apache-2.0 and > vendored unmodified. This `arch/` directory is SysDeck packaging; > see `../ATTRIBUTION.md` for the full credit and license notes. ## 0. The porting verdict: zero source changes klanker-gate is **not a Windows codebase**. It is a Deno 2 + TypeScript monorepo, and Deno runs identically on Windows, macOS and Linux — the Windows mentions in the tree are accommodations for a *second-class Windows dev platform*, not Windows-first code: | Evidence (upstream file) | What it says | | --- | --- | | `apps/gateway/cluster.ts` | `reusePortSupported()` returns true **only for linux/darwin**. Windows is locked to single-process; the failure message literally says "Use Docker/Linux for multi-process". | | `Dockerfile` | Both stages are Linux images (`denoland/deno:2.9.3`, `denoland/deno:alpine-2.9.3`). The Windows mention is a comment about Defender locking files on a Windows *host*. | | `deploy/docker-entrypoint.sh` | POSIX `/bin/sh` entrypoint. | | `deno.lock` win32 entries | Automatic cross-platform lockfile records — present in every project using npm-native deps, on every OS. | | `scripts/` | `.sh` + Deno `.ts` tasks; no `.bat`/`.ps1` anywhere in the 448-file tree. | So "porting to Arch Linux" is a **packaging exercise, not a code exercise**: install Deno + PostgreSQL, manage the process with systemd. The arch/ files in this tree do exactly that. Moving to Arch even *unlocks* a feature — `FROSTY_WORKERS` multi-process serving via `SO_REUSEPORT`, which the Windows platform cannot do at all. Estimated effort if you do it by hand (no packaging): **~30 minutes**. With this package: ~5 minutes, below. ## 1. Install the package ```bash pacman -S --needed deno base-devel # deno is in [extra] cd klanker-gate/arch makepkg -si ``` What lands where: | Path | Contents | | --- | --- | | `/usr/share/klanker-gate` | the gateway tree (Deno runs the TypeScript directly) | | `/usr/bin/klanker-gate` | ExecStart wrapper: cache warmup + scoped `--allow-run` when `FROSTY_WORKERS>1` | | `/etc/klanker-gate/env` | operator EnvironmentFile (pacman `backup=()` — survives upgrades) | | `/usr/lib/systemd/system/klanker-gate.service` | hardened service unit | | `/var/lib/klanker-gate` | StateDirectory: `data/` + the deno module cache (DENO_DIR) | The unit is hardened (`ProtectSystem=full`, `PrivateTmp`, empty `CapabilityBoundingSet`, …) but deliberately does **not** use `MemoryDenyWriteExecute` — V8's JIT needs W^X pages — and defers `SystemCallFilter` until it is validated against the Code Mode worker. ## 2. Provision PostgreSQL (local state store) Either run your own, or use the shipped compose file (`docker compose up -d postgres` from the tree root). Local Arch postgres: ```bash pacman -S postgresql sudo -u postgres initdb -D /var/lib/postgres/data sudo systemctl enable --now postgresql sudo -u postgres psql <<'SQL' CREATE ROLE klanker LOGIN PASSWORD 'CHANGE_ME'; CREATE DATABASE klanker OWNER klanker; SQL ``` Frosty's durable state (providers, virtual keys, budgets, logs, cache, pricing) all lives in this one database. A remote `FROSTY_PG_URL`, PgBouncer in front, or the compose service are equally supported. ## 3. Configure ```bash sudoedit /etc/klanker-gate/env ``` Minimum viable: `FROSTY_PG_URL` plus one provider credential. For SysDeck operation also set `FROSTY_ADMIN_TOKEN` (the module reads the operator API with it). **The package requires the token anyway** — see §9 before you template that file. The full knob reference is `/usr/share/klanker-gate/docs/reference/environment-variables.md`. ## 4. Start + verify ```bash sudo systemctl enable --now klanker-gate systemctl status klanker-gate # boot log states API-only mode curl http://localhost:8080/healthz # {"ok":true,...} curl http://localhost:8080/v1/models # configured model catalog ``` First start fetches the module cache (network needed once); later starts are served warm from `/var/lib/klanker-gate/.cache/deno`. ## 5. Multi-process serving (the Arch bonus) ```bash # /etc/klanker-gate/env FROSTY_WORKERS=4 ``` ```bash sudo systemctl restart klanker-gate curl http://localhost:8080/api/runtime # workers.effective == 4 ``` Workers share :8080 through `SO_REUSEPORT` with fleet-wide budgets and rate-limit windows through PostgreSQL. The wrapper grants `--allow-run` scoped to the Deno binary **only** when `FROSTY_WORKERS>1` — mirroring the upstream entrypoint's escalation policy. ## 6. Optional: the same-origin Control UI The packaged gateway serves API-only until `apps/control-ui/dist` exists. SysDeck's klanker module is the operator surface for this package, so building the UI is optional. If you want it anyway: ```bash cd /usr/share/klanker-gate sudo -u klanker deno task setup # one-time esbuild script dep sudo -u klanker deno task build-ui # emits apps/control-ui/dist sudo systemctl restart klanker-gate ``` (Building inside the packaged tree; pacman will overwrite it on upgrade — rebuild after upgrades, or keep a copy in /opt if you care.) ## 7. Wire it into SysDeck Both SysDeck editions ship a klanker module (cockpit edition: `bridge/klanker.py` + `plugins/sysdeck-klanker`; web edition: the AI Gateway panel). Point either at the gateway: **Cockpit edition** (bridge environment — e.g. the file cockpit sources `bridge_env` from): ```bash KLANKER_URL=http://127.0.0.1:8080 KLANKER_ADMIN_TOKEN= ``` **Web edition** (`.env` of the Next.js app, then restart it): ```bash KLANKER_URL=http://127.0.0.1:8080 KLANKER_ADMIN_TOKEN= ``` The panels read `/healthz`, `/api/providers`, `/api/virtual-keys`, `/api/logs`, `/api/analytics`, `/api/runtime`, `/v1/models` and drive service control (`systemctl start/stop/restart`, journal tail) through the sysdeck bridge. With the gateway down the panels degrade to clearly-badged demo data instead of erroring. ## 8. Upgrades / removal ```bash # upgrade: replace the tree, rebuild, reinstall cd klanker-gate/arch && makepkg -si # the operator env survives upgrades (backup'd); state lives in postgres sudo systemctl stop klanker-gate sudo pacman -Rns klanker-gate # -n also drops the backup env copy ``` The `klanker` user and `/var/lib/klanker-gate` are owned by pacman-hygiene tooling if you want them gone too (`sudo userdel klanker`). ## 9. Security notes (read before exposing the port) The 0.3.0 SysDeck security audit reviewed the vendored upstream code end-to-end. Full findings + suggested upstream patches live in `arch/SECURITY-UPSTREAM.md` (installed at `/usr/share/klanker-gate/SECURITY-UPSTREAM.md`). The operator-facing summary: 1. **The gateway binds `0.0.0.0` by default** (upstream `Deno.serve({port})` — no hostname option exists in 0.9.0). On a single-user machine behind no firewall, treat :8080 as a LAN-exposed port. Keep it loopback with a firewall rule: ```bash # nftables: drop non-loopback ingress to the gateway sudo nft add rule inet filter input tcp dport 8080 ip saddr != 127.0.0.1 drop # or ufw: sudo ufw deny in to any port 8080 sudo ufw allow in from 127.0.0.1 to any port 8080 ``` 2. **`FROSTY_ADMIN_TOKEN` is mandatory with this package.** Upstream's no-token "local-admin mode" serves the entire admin API (provider CRUD, key management, config export incl. secrets) unauthenticated; the packaged systemd unit refuses to start without the token (`ExecStartPre` guard — opt out via drop-in only on a firewalled host). 3. **Create a virtual key immediately after first start** if any provider account carries credentials: until the first vkey exists, upstream's governance layer passes `/v1/*` through *unauthenticated* (open proxy onto your provider spend): ```bash curl -H "Authorization: Bearer $FROSTY_ADMIN_TOKEN" \ -H 'content-type: application/json' \ -d '{"name":"console","limits":{}}' \ http://127.0.0.1:8080/api/virtual-keys ``` 4. Compose users: the upstream compose files publish Postgres (frosty/ frosty) and the observability stack on all interfaces with default passwords — bind them to loopback or change the credentials before `docker compose up` on any shared network. What this package already does for you: the service unit is hardened (`ProtectSystem=full`, `PrivateTmp`, empty `CapabilityBoundingSet`, `DevicePolicy=closed`, …), the Deno permission surface is scoped (`--allow-net --allow-env --allow-read --allow-write=data`), secrets never appear in the SysDeck panels (token travels in the `Authorization` header only), and the vendored tree is byte-identical to upstream — all mitigations live in this packaging layer, documented per finding.