Files
wireguard-ui/README.md
T
tben cbd5451ab7 Run the Caddy TLS ingress as a Docker container
Caddy previously ran as a host binary in /opt/caddy with a wgui-caddy
systemd/OpenRC unit. Only Caddy is containerized; the app still runs on the
host as root because it shells out to wg, ip and iptables.

- Add caddy-container-commands.ts as the single source of truth for the
  run/exec/validate/inspect argument arrays. Every value stays a separate
  argv element, the image is pinned to caddy:2.11.4-alpine, and the
  entrypoint is set explicitly because some mirrors publish the image with
  a null Entrypoint (which made Docker exec "run" as a binary).
- Rewrite install-caddy.sh: require_docker with distro-specific hints,
  pull + recreate the container, assert it is running and echo the daemon
  error instead of discarding it, normalize the admin socket path in
  existing Caddyfiles, and clean up a legacy native install only after the
  container is up so the ingress port is never unbound. Add
  --purge-legacy / --purge for hosts that want to drop the old install
  without installing the container.
- Move the admin socket to /var/lib/wgui/caddy/admin.sock so it lives
  inside the mounted Caddy dir and reloads work over docker exec.
- Validate candidate configs in a throwaway container, mounting the TLS
  dir read-only as well: provisioning the tls app loads every certificate
  referenced by a tls directive, so without that mount a route with a
  certificate failed validation and its Caddyfile was never committed.
- Report docker-based installed/version/service_active, skip
  validate/reload with a warning when the image is absent, and update the
  system setup tool check to look for docker.
- Issue certificates with a docker exec reload command and chown files to
  root:root, since the container reads them through a read-only mount.
- Update the Setup UI copy and docs (README, AGENTS.md).

Also drops the unused EmptyContent import and a stray debug log in the
client route form, and stops passing the unsupported cancel_href prop to
ClientRouteForm from the route add/edit pages.
2026-10-21 14:34:16 +01:00

327 lines
14 KiB
Markdown

# Wireguard UI
An admin dashboard for Wireguard — manage hosts, clients, tunnels, and IP rules through a web interface.
## Features
- **Host Management** — Create and configure WireGuard hosts with public keys, IP addresses, and listen ports
- **Client Management** — Add clients per host with WireGuard configs, allowed IPs, and notes
- **Client Rules** — Define IP table rules per client (rule type, destination, ports, protocol)
- **Media Management** — Upload and manage media assets (images, documents, etc.) with tagging, thumbnails, and RAG ingestion
- **User Management** — Multi-user support with role-based access (user types)
- **Persistent Storage** — SQLite database with automatic schema generation
- **WebSocket Support** — Real-time updates via WebSocket connections
- **Responsive UI** — Tailwind CSS v4 with twui design system components
## Prerequisites
- **Bun** 1.3.1 or later ([install](https://bun.sh/install))
- **SQLite** (via `@moduletrace/bun-sqlite`)
- **git**
- **zip** and **unzip**
- **Linux** (Debian, Ubuntu, Fedora, Arch, Alpine, or similar)
- Root access (for installation script and system service setup)
## Installation
### Automatic (Recommended)
Run the install script on a fresh machine:
```bash
curl -fsSL https://url.tben.me/wg-ui | sudo bash
```
Or clone the repo and run the script manually:
```bash
git clone https://git.tben.me/Moduletrace/wireguard-ui.git
cd wireguard-ui
bash src/scripts/install-wg-ui.sh
```
The install script will:
1. Install system dependencies (git, curl, ca-certificates, openssl)
2. Install Bun 1.3.14 to `/opt/bun`
3. Set up library directories under `/var/lib/wgui` (keys, clients, hosts, iptables, scripts)
4. Clone or update the repo to `/var/lib/wgui/webapp`
5. Install app dependencies (`bun install`)
6. Generate `.env` with encryption secrets
7. Run WireGuard setup (if available)
8. Install Docker (if missing) and run Caddy as the `wgui-caddy` container
9. Install the `wg-quick` helper script
10. Set up systemd/OpenRC services for `wgui` and certificate renewal
The installer **never modifies your firewall** — it will print the commands to
temporarily allow the dashboard port so you can reach the first login, and then
point you to **Setup → Firewall** for reviewing and removing rules.
### Firewall
Firewall rules are managed per host, automatically:
1. **Bootstrap (manual, temporary)** — allow the dashboard port so you can
reach the web UI once:
```bash
ufw allow 10752/tcp # ufw (Debian/Ubuntu)
firewall-cmd --permanent --add-port=10752/tcp && firewall-cmd --reload # firewalld (RHEL/Fedora)
```
2. **Host creation** — every host setup automatically allows its entire
wireguard subnet (e.g. `10.5.0.1` → `10.5.0.0/24` from anywhere) and the
host's wireguard listen port (e.g. `51821/udp`) in ufw or firewalld.
Detailed per-client traffic rules are enforced by the existing iptables
chain scripts, not the host firewall.
3. **Host deletion** — the host's subnet and listen-port rules are removed
automatically.
4. **Cleanup** — once everything is reachable via the tunnel, remove the
temporary bootstrap rule and review all rules under **Setup → Firewall**:
```bash
ufw delete allow 10752/tcp
firewall-cmd --permanent --remove-port=10752/tcp && firewall-cmd --reload
```
Rules that no longer match any host (`temporary`/`orphaned`) are flagged
with copyable removal commands on the setup page.
### TLS for Tunnel Services
Client routes expose internal services to WireGuard clients over HTTPS. The
data plane is a dedicated Caddy container (`wgui-caddy`, host networking) on
`8443`; wg-ui owns the route and certificate records and regenerates the
Caddyfile. Caddy never binds ports 80 or 443, and the host's existing proxy is
left alone. Docker is required — the installer starts the daemon if it is not
running.
1. If Caddy isn't installed yet, open **Setup → TLS ingress** and click
**Install Caddy** (or use the Caddy action on **Setup → Maintenance**).
The installer pulls `caddy:2.11.4-alpine`, (re)creates the `wgui-caddy`
container, and sets up certificate support.
2. Open a route form from **Routes** (or a host's **Routes** panel) and set
the ACME email and staging toggle. No DNS provider token is needed:
certificates use manual DNS-01.
Let's Encrypt requires a public domain you control; internal-only names such
as `home.arpa` or `.local` need a private CA instead.
3. Add an A/AAAA record for each route hostname pointing at the host's
WireGuard IP. Caddy also restricts each route to the subnet of the host that
owns it.
4. Open the route page and click **Issue** (or **Renew**). The dashboard shows
the exact `_acme-challenge.<hostname>` TXT record. Add it at Cloudflare,
wait for propagation, then click **Validate**.
5. Clients use `https://<hostname>:8443/`. Portless URLs require the host's
existing 443 proxy to forward to `127.0.0.1:8443`; wg-ui does not take over
443. Caddy allows localhost so that proxy can reach it, which means the
proxy itself must enforce who may use those portless URLs.
Manual DNS-01 certificates are renewed from the route page because validation
needs your registrar. The route list shows expiry warnings. A successful
issuance reloads Caddy without restarting wg-ui. Check
`docker logs wgui-caddy` for reload failures.
#### Upgrading from the old native Caddy install
Earlier versions ran Caddy as a host binary in `/opt/caddy` with a `wgui-caddy`
systemd/OpenRC service. Re-running the installer migrates the host in place: it
pulls the image, starts the container, and only then removes the old service
and binary. The Caddyfile is rewritten to use the new admin socket path
(`/var/lib/wgui/caddy/admin.sock`); certificates in `/var/lib/wgui/tls` are
untouched.
To remove a leftover native install without installing the container:
```bash
sudo bash src/scripts/install-caddy.sh --purge-legacy
```
Add `--purge` to also remove the container, the image and the certificate
renewal job (the Caddyfile and certificates are always kept):
```bash
sudo bash src/scripts/install-caddy.sh --purge-legacy --purge
```
### Manual (Development)
```bash
git clone https://git.tben.me/Moduletrace/wireguard-ui.git
cd wireguard-ui
bun install
NODE_ENV=development bun run dev
```
The dev server runs on port **10752** by default.
## Configuration
### Environment Variables
The `.env` file (gitignored) controls runtime behavior. It is auto-generated during installation if missing:
| Variable | Description | Default |
| ----------------- | ---------------------------------------- | -------------------- |
| `NODE_ENV` | Environment (`development`/`production`) | `production` |
| `ENCRYPTION_KEY` | Encryption key for sensitive data | Auto-generated |
| `ENCRYPTION_SALT` | Encryption salt for sensitive data | Auto-generated |
| `DATA_DIR` | Application data directory | `$INSTALL_DIR/.data` |
| `CADDY_VERSION` | Caddy image tag (`caddy:<version>-alpine`)| `2.11.4` |
| `CADDY_HTTPS_PORT`| Caddy ingress HTTPS port (never 80/443) | `8443` |
| `CADDY_HTTP_PORT` | Caddy plain HTTP port (never 80) | `8081` |
| `CADDY_INGRESS_ENABLED` | Set to `0` to skip Caddy config sync | `1` |
| `ACME_STAGING` | Set to `1` to use the Let's Encrypt staging CA | `0` |
### Database Configuration
Edit `bun-sqlite.config.ts` to change database settings:
```typescript
const config: BunSQLiteConfig = {
db_name: "wgui", // Database file name
db_dir: "./db", // Database directory
typedef_file_path: "./db/types/db.ts", // Generated types
db_schema_file_name: "schema.ts", // Schema definition
};
```
### Server Port
The server port is defined in `src/data/site-data.ts`:
```typescript
export const SiteData = {
ServerPort: 10752,
// ...
} as const;
```
## Available Scripts
| Command | Description |
| ------------------- | ----------------------------------------- |
| `bun run dev` | Start development server (requires root) |
| `bun run start` | Start production server |
| `bun run db:schema` | Generate/update database schema and types |
| `bun run db:admin` | Open database admin interface |
## Project Structure
```
wireguard-ui/
├── src/
│ ├── pages/ # File-system based routes (__root.tsx, index.tsx, etc.)
│ │ ├── (sections)/ # Page sections
│ │ ├── (partials)/ # Page-local components
│ │ ├── (data)/ # Page-local data
│ │ ├── (functions)/ # Page-local functions
│ │ ├── (utils)/ # Page-local utilities
│ │ └── (hooks)/ # Page-local hooks
│ ├── components/
│ │ ├── twui/ # Local design system (publishable package)
│ │ ├── ui/ # shadcn/ui components
│ │ ├── shadcnblocks/ # shadcnblocks registry components
│ │ └── general/ # Shared page components (chat, date-picker, logo, etc.)
│ ├── hooks/ # Shared React hooks
│ ├── functions/
│ │ ├── backend/ # Server-only: DB queries, email, media upload, RAG
│ │ ├── frontend/ # Client-safe: CRUD handlers, fetch handlers
│ │ └── general/ # Platform-agnostic: join builders
│ ├── utils/ # Utility modules (auth, crypto, CSRF, hashing, etc.)
│ ├── dict/ # Constants and dictionaries (user types, gates, etc.)
│ ├── data/ # Static data (app constants, location data, etc.)
│ ├── types/ # Type definitions
│ ├── scripts/ # Install and setup scripts
│ └── server.ts # Entry point — starts Bun.serve with WebSocket support
├── db/
│ ├── schema.ts # Database schema definition
│ └── types/db.ts # Generated database types
├── public/ # Static assets served by the server
├── assets/ # Project assets
├── bun-sqlite.config.ts # Database configuration
├── bunext.config.ts # Bunext configuration
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
└── .env # Environment variables (gitignored)
```
## Database Schema
The database consists of the following tables:
| Table | Description |
| ----------------- | ----------------------------------------------- |
| `users` | User accounts (name, email, username, password) |
| `user_types` | User type assignments per user |
| `media` | Media assets (images, documents, etc.) |
| `media_paradigms` | Media paradigm classifications per media item |
| `clients` | WireGuard clients per host |
| `hosts` | WireGuard host configurations |
| `client_rules` | IP table rules for each client |
| `client_routes` | HTTPS routes exposed to WireGuard clients |
| `tls_certificates`| Certificates used by the Caddy ingress |
| `variables` | Application-wide key-value variables |
Generate types from the schema:
```bash
bun run db:schema
```
## Admin Route Hierarchy
| Route Pattern | Access | Features |
| ----------------------------------------------- | ------------ | ------------------------------------------------------------ |
| `/admin` | Admin | Dashboard overview |
| `/admin/hosts` | Admin | Host management (add, edit, per-host detail) |
| `/admin/routes` | Admin | Global HTTPS route list with host and certificate status |
| `/admin/hosts/[host_id]/routes` | Admin | HTTPS routes and upstreams exposed to tunnel clients |
| `/admin/hosts/[host_id]/routes/[route_id]` | Admin | Route details, TLS settings and certificate issue/renew |
| `/admin/clients` | Admin | Client management (add, edit, client rules per host) |
| `/admin/settings` | Admin | Account settings |
| `/admin/setup` | Super admin | System setup (hosts, WireGuard tools, update, restart) |
| `/admin/setup/tls` | Super admin | Caddy ingress installation and status |
## Development
### Development Mode
Set `NODE_ENV=development` and run:
```bash
NODE_ENV=development bun run dev
```
In development mode, the install script skips cloning and system service setup — the server is assumed to already be running.
### Path Alias
`@/*` resolves from the repository root. Examples:
```typescript
import { Button } from "@/src/components/ui/button";
import type { BUN_SQLITE_WGUI_USERS } from "@/db/types/db";
```
### Type-Checking
Run type checks with:
```bash
bunx tsc --noEmit
```
### Testing
Run individual test files:
```bash
bun test path/to/test.file.test.ts
```
## Repository
- **URL**: [https://git.tben.me/Moduletrace/wireguard-ui](https://git.tben.me/Moduletrace/wireguard-ui)
- **Install Script**: [src/scripts/install-wg-ui.sh](https://git.tben.me/Moduletrace/wireguard-ui/raw/branch/main/src/scripts/install-wg-ui.sh)