Files
wireguard-ui/AGENTS.md
T
2026-10-21 09:13:59 +01:00

12 KiB
Raw Blame History

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.

MCP Tool Usage Guidelines

When building, refactoring, styling, or auditing UI components and layouts, actively invoke these configured MCP servers:

1. design-mcp (UI/UX Engineering & Design Standards)

  • When to call: Before constructing new page layouts, design systems, empty/loading states, or interactive UI flows.
  • Instruction: Query design-mcp to ensure accessibility (a11y), proper layout hierarchy, visual balance, and consistent interaction patterns across application workflows.

2. design-systems (Design System Specs & Reference Patterns)

  • When to call: When creating or extending reusable component interfaces, verifying design token structures, or inspecting component composition patterns.
  • Instruction: Query design-systems to pull canonical UI patterns, atomic component guidelines, and standard prop naming conventions before creating custom components.

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.

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 Caddy/acme ingress installer 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.