SysDeck/klanker-gate/arch/INSTALL-ARCH.md

228 lines
8.9 KiB
Markdown

# 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=<same token as FROSTY_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=<same 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.