Files
wireguard-ui/AGENTS.md
2026-11-01 14:27:10 +01:00

94 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agent Instructions
WireGuard UI — a self-hosted web admin for WireGuard hosts and clients. The app runs **as root** and executes system commands (`wg`, `ip`, `iptables`, `wg-quick`, firewall managers). Treat every API route and every value that reaches a shell or a WireGuard config as security-sensitive.
## Stack
- **Runtime:** Bun — not Node. Use `bun` commands, never `npm`/`npx`.
- **Framework:** `@moduletrace/bunext` — not Next.js. Routing is filesystem-based under `src/pages/`, served by `Bun.serve` in `src/server.ts`.
- **Database:** `@moduletrace/bun-sqlite` — SQLite. Schema in `db/schema.ts`, generated types in `db/types/db.ts`. Config in `bun-sqlite.config.ts` (DB name `wgui` in `./db`). Import its types from `@moduletrace/bun-sqlite/types` (not `/dist/types`).
- **UI:** Tailwind CSS v4 + local `twui` components (`src/components/twui`). Global styles in `src/styles/main.css`.
- **Icons:** `lucide-react`.
## Entrypoint
`src/server.ts` — runs `bunextInit()`, starts `Bun.serve` on `0.0.0.0` (port from `SiteData.ServerPort`), serves `/media/*` via `handleMediaServer`, then runs `syncWireguardHosts()` to bring host tunnels in line with the DB. Caddy's data plane runs as the `wgui-caddy` Docker container (host networking, `/var/lib/wgui/caddy` and `/var/lib/wgui/tls` bind-mounted); reloads go through `docker exec`, and the admin socket lives at `/var/lib/wgui/caddy/admin.sock`.
On-disk state: WireGuard keys/configs/iptables scripts live under `/var/lib/wgui` (`grabDirNames()` in `src/utils/grab-dir-names.ts`); uploaded media under `DATA_DIR` (default `./.data`).
## Commands
| Purpose | Command |
| --------------- | ------------------------------------------------------- |
| Dev server | `bun run dev` (runs `sudo bun src/server.ts`) |
| Production | `bun run start` |
| Build | `bun run build` |
| Type-check | `bun run typecheck` (`bunx tsc --noEmit`) |
| Tests | `bun run test` (`bun test`) |
| DB schema | `bun run db:schema` |
| DB admin UI | `bun run db:admin` |
No linter or formatter configured. Type-check and `bun test` are the verification steps.
## Architecture
- `src/pages/__root.tsx` — picks the layout (`src/layouts/admin` for `/admin*`, `src/layouts/centered` for auth, 404 and everything else), mounts `AppProviders` (toast + confirm dialog) and provides `AppContext`. Its `Head` renders `ThemeScript` so the theme applies before first paint.
- **Pages:** `/` redirects to `/admin` or `/auth/login`; `/auth/{login,logout,signup}`; `/admin` (dashboard), `/admin/setup/{status,maintenance,firewall}` (super admin only, guarded per page by `guard-setup-page`; `/admin/setup` redirects to `status`), `/admin/settings/{profile,password}` (`/admin/settings` redirects to `profile`), `/admin/clients`, `/admin/clients/add` (the only add-client form — `?host_id=` preselects the host), `/admin/hosts/*` (add/edit hosts, `[host_id]/clients` list, `[host_id]/clients/[client_id]` tabbed detail + `edit`). `[host_id]/clients/add-client` is a legacy redirect.
- **Theme:** light/dark/system, stored in the `wgui_theme` cookie (`src/dict/theme-dict.ts`). twui dark styles key off a `.dark` class on `<html>` — toggle it only via `applyTheme`, never replace `className`.
- **API:** `src/pages/api/admin/*` (host/client setup, network info, firewall status, service restart/update, media), generic CRUD at `src/pages/api/admin/crud/[[...paths]].ts` with per-table handlers in `crud/(tables)/<table>/{get,post,put,del}.ts`, plus `api/auth/login.ts` and `api/create-first-user.ts`.
- **Every `/api/admin/*` route must authenticate** — nothing guards the prefix globally. Use `adminApiGuard({ req })` (`src/functions/backend/auth/admin-api-guard.ts`) or `userAuth` + `checkUserAccess`.
- **Shell safety:** never interpolate request/DB values into `execSync` strings. Use `execFileSync` with an args array or `node:fs` APIs, and validate values before they reach a WireGuard config (newlines there can inject `PostUp` commands). Validators live in `src/utils/` (`validate-ipv4`, `validate-ipv4-cidr`, `validate-allowed-ips`, `validate-endpoint-host`, `validate-interface-name`).
## Path Alias
`@/*` resolves from repo root. Import like `@/src/components/general/admin-card`, `@/db/types/db`.
## Page Directory Rules
For route-local code under `src/pages`, use non-routed meta folders:
- `(sections)` — page sections
- `(partials)` — components used by sections
- `(data)` — page-local data
- `(functions)` — page-local functions
- `(utils)` — page-local utilities
- `(hooks)` — page-local hooks
Keep route files (e.g. `index.tsx`) thin — compose from route-local meta folders.
Dynamic routes use `[param]` syntax (e.g. `[id]`, `[user_type]`, `[key]`). Catch-all routes use `[[...paths]]`.
Server functions use `index.server.ts` alongside `index.tsx` (e.g. `single-document/[id]/index.server.ts`).
## Shared Code Organization
- **`src/functions/backend/`** — Server-only: `auth/`, `db/`, `firewall/` (firewalld/ufw rules, status), `media/`, `setup/` (host/client setup, iptables script builder, network detection), `caddy/` (Caddyfile generation, ingress sync, route validation), `acme/` (manual DNS-01 with `acme-client`, challenge state, certificate status). Certificates are configured per route in the route form; `/admin/setup/tls` is only for the global Caddy installation and status. The route page displays the `_acme-challenge` TXT record and validates after the user adds it at their DNS registrar.
- **`src/functions/frontend/`** — Client-safe: CRUD/fetch handlers, media upload/delete, client rule label formatting.
- **`src/functions/server/`** — Request-level server handlers (media server).
- **`src/hooks/`** — `use-admin-crud-get` (with `refetch`), `use-client-rules-map`, `use-confirm`, `use-toast`, `use-theme`, `use-fetch-get`, `use-form-init`, `use-users-get`.
- **`src/utils/`** — Cookies, crypto, dir names, IP/CIDR/port/interface validators, WireGuard interface naming. Most have `.test.ts` files.
- **`src/dict/`**, **`src/data/`** — Constants (`app-data.ts`, `site-data.ts`), user types, client rules, links.
- **`src/scripts/`** — Shell scripts: `install-wg-ui.sh`, `install-caddy.sh` (idempotent installer that pulls the `caddy:<version>-alpine` image and (re)creates the `wgui-caddy` Docker container plus acme.sh renewal; used by the UI and the full installer), `setup-wireguard.sh`, `wg-quick-manage.sh`.
- **`src/components/general/`** — Shared app components, all built from twui. Reach for these before writing page markup:
- Page frame: `page-header` (title, meta, primary action + overflow menu), `page-body`, `panel` (the one card style), `form-section`, `form-actions` (submit is always rendered).
- Text & status: `muted-text`, `mono-text` (IPs, keys, paths), `section-label`, `status-dot`, `inline-alert`, `empty-state`, `stat-tile`, `key-value-list`, `copy-button`, `copyable-value`.
- Data: `data-table` (desktop table + mobile card list; the only place raw `<table>` is allowed), `clients-table`, `host-clients-panel`, `action-menu`, `sub-nav` (link tabs between sibling routes).
- Flows: `client-form` + `client-rules` (shared by add/edit client and the client detail Rules tab), `user-form`, `host-wg-ip-field`, `use-delete-host`.
- Feedback: `confirm/` + `useConfirm()` and `toast/` + `useToast()` (`src/hooks`). Never use `window.confirm`/`alert`; update lists in place instead of `location.reload()`.
- **Design tokens** live in `src/styles/main.css`: one accent, WireGuard red `#89161b` (`primary` = `secondary` = `accent`; `error` is a separate brighter red), zinc neutrals, and theme-flipping semantic colours (`bg-surface`, `bg-surface-2`, `border-line`, `text-fg`, `text-muted`, `text-subtle`) that need no `dark:` pair.
## Conventions
- **Numeric IDs are not booleans — the main WireGuard host has `id = 0`.** Never test an ID with truthiness (`if (!host?.id)`, `if (host_id)`, `id ? ... : ...`); `0` is a valid ID and `AppData.WireguardMainHostID` is `0`. Use `typeof id === "number"` / `Number.isSafeInteger(id)` for numeric IDs, or explicit `id !== undefined && id !== ""` when the ID may arrive as a string. This applies to host lookups especially, but to route/cert IDs too.
- **Every form that submits must ask for confirmation first.** Use `useConfirm()` and await `confirm({...})` before submitting; never use `window.confirm` or `window.alert`. This applies to create, update, settings, and auth forms.
- Prefer one function/component per file over monolithic files with multiple exports.
- **Every rendered array must have a separate component for each array item.** Always extract the item JSX into its own component and render it via `items.map((item) => <ItemComponent {...item} />)` — never inline the item's JSX inside the map callback.
- Always use object parameters (`{ foo, bar }`) instead of comma-separated positional parameters.
- Prefer existing patterns over new abstractions.
- **Modularization is non-negotiable: prioritize modularization over monolithic functions/components.** Repeated UI blocks must be extracted into their own reusable components driven by props (e.g. an info row component taking `{ icon, text }`), never repeated inline markup.
- **UNCHANGEABLE RULE — twui components always win over raw JSX elements.** ALWAYS use twui components (`H1`–`H5`, `P`, `Span`, `List`, `Img`, `Row`, `Stack`, `Container`, `Section`, `Border`, `Card`, `Button`, `Tag`, `Link`, etc.) before basic JSX elements (`<h1>`–`<h6>`, `<p>`, `<span>`, `<ul>`, `<li>`, `<img>`, `<a>`, `<button>`, `<div>`-as-card, etc.). Never introduce or reintroduce a raw JSX element where a twui equivalent exists. Override twui defaults via `className` (e.g. `mb-0!`) rather than reaching for the raw element.
- **UNCHANGEABLE RULE — twui components are preferred over shadcn/ui components.** Use twui before `src/components/ui/*` (shadcn) whenever a twui equivalent exists. The long-term goal is to phase out shadcn/ui entirely — do not introduce new shadcn usage where twui covers the need, and prefer migrating existing shadcn call sites to twui when touching them anyway.
- **LIMIT EDITS TO `src/components/twui/`** — it is a general, publishable library shared across projects (its own `package.json`), not scoped to this repo. Do not modify its components unless strictly necessary. Customize/appearance overrides for this project belong in `src/styles/main.css` (e.g. tweaking `.twui-card`, `--color-*` tokens, font/type styles) rather than editing twui source files.
- **twui `Input`/`Textarea`: prefer `defaultValue` over `value`.** The component manages internal state and patches the DOM directly (`src/components/twui/form/Input/index.tsx`), so `value` is not a true controlled prop — a static `value` gets reverted on every re-render, wiping in-progress edits. `value` only works well when the parent updates it on each change (live display), not for pre-filling an editable field. Use `defaultValue` to pre-fill editable inputs; `onChange`/`changeHandler` still fire.
- `.env` is gitignored but exists locally. Do not commit it.