65741644a3
- dashboard.html: Fix zones, leases, wg_status, cert key names, add services var
- server.py: Pass services to dashboard template via _get_service_status()
- lib/acme.py: Fix dead third date format (%Y%m%d%H%M%z) using astimezone(UTC)
- lib/wireguard.py: Add -- separator to cp command to match sudoers rule
- lib/nginx.py: Replace shallow dict.copy() with {**...} for DEFAULT_SSL
- AGENTS.md: Update test count 149 -> 154
- docs/api.md: Rename cert field expiry -> expires_at
245 lines
12 KiB
Markdown
245 lines
12 KiB
Markdown
# Configuration Reference
|
|
|
|
This document describes the JSON configuration files used by Vacuum Wall to manage each subsystem. All configuration is stored in the `data/` directory as declarative JSON. The application renders these declarations into the format expected by each underlying service.
|
|
|
|
## DHCP/DNS Configuration
|
|
|
|
**File**: `data/dnsmasq/config.json`
|
|
|
|
This file defines all DHCP server settings and DNS resolution behavior for the dnsmasq service. The application renders it into `/etc/dnsmasq.d/vacuum-wall.conf`.
|
|
|
|
```json
|
|
{
|
|
"dhcp": {
|
|
"ranges": [
|
|
{
|
|
"interface": "eth1",
|
|
"start": "192.168.2.100",
|
|
"end": "192.168.2.200",
|
|
"lease_time": "12h",
|
|
"gateway": "192.168.2.1",
|
|
"dns": "192.168.2.1"
|
|
}
|
|
],
|
|
"static_leases": [
|
|
{
|
|
"mac": "aa:bb:cc:dd:ee:ff",
|
|
"ip": "192.168.2.50",
|
|
"hostname": "printer"
|
|
}
|
|
]
|
|
},
|
|
"dns": {
|
|
"upstreams": ["8.8.8.8", "1.1.1.1"],
|
|
"domain": "lan",
|
|
"custom_records": [
|
|
{
|
|
"name": "nas.lan",
|
|
"address": "192.168.2.10",
|
|
"hostname": "nas"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### DHCP Fields
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `ranges` | array | Yes | One or more DHCP address pools. Each range defines a subnet from which addresses are leased. |
|
|
| `ranges[].interface` | string | Yes | Network interface on which to serve this DHCP range (e.g., `eth1`). |
|
|
| `ranges[].start` | string | Yes | First IP address in the pool. |
|
|
| `ranges[].end` | string | Yes | Last IP address in the pool. |
|
|
| `ranges[].lease_time` | string | No | DHCP lease duration. Accepts values like `12h`, `1d`, `30m`. Default: `1h`. |
|
|
| `ranges[].gateway` | string | No | Default gateway advertised to DHCP clients. Typically the router's LAN IP. |
|
|
| `ranges[].dns` | string | No | DNS server address advertised to DHCP clients. Typically the Vacuum Wall host's LAN IP. |
|
|
| `static_leases` | array | No | Fixed IP assignments tied to MAC addresses. Clients with matching MACs always receive the specified IP. |
|
|
| `static_leases[].mac` | string | Yes | MAC address of the client (colon-separated lowercase hex). |
|
|
| `static_leases[].ip` | string | Yes | The IP address to assign to this MAC. Must be outside the dynamic pool ranges. |
|
|
| `static_leases[].hostname` | string | No | Hostname to associate with the lease. Used for reverse DNS and mDNS. |
|
|
|
|
### DNS Fields
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `upstreams` | array | Yes | Upstream DNS servers to forward unresolved queries to. Supports IPv4 and IPv6 addresses. |
|
|
| `domain` | string | Yes | Local domain suffix. Hostnames without a FQDN are resolved within this domain (e.g., `printer` becomes `printer.lan`). |
|
|
| `custom_records` | array | No | Static DNS A records for internal services and devices. |
|
|
| `custom_records[].name` | string | Yes | Fully qualified domain name (e.g., `nas.lan`). |
|
|
| `custom_records[].address` | string | Yes | The IP address to resolve the name to. |
|
|
| `custom_records[].hostname` | string | No | Short hostname without the domain suffix. Adds a reverse DNS entry as well. |
|
|
|
|
Additional dnsmasq directives can be appended verbatim by placing plain-text files in `data/dnsmasq/fragments/`. Each file's contents are concatenated into the generated config. This is useful for advanced options not covered by the JSON schema (e.g., `bogus-priv`, `cache-size`, `log-queries`).
|
|
|
|
## Nginx Configuration
|
|
|
|
**File**: `data/nginx/config.json`
|
|
|
|
This file defines reverse proxy domains, the management interface, and global SSL settings. The application renders it into per-domain server block files in `data/nginx/sites-enabled/` and into the shared SSL snippet at `/etc/nginx/snippets/vacuum-wall-ssl.conf`.
|
|
|
|
```json
|
|
{
|
|
"domains": {
|
|
"app.example.com": {
|
|
"backend": {
|
|
"host": "192.168.2.50",
|
|
"port": 8080,
|
|
"proto": "http"
|
|
},
|
|
"force_ssl": true,
|
|
"headers": {
|
|
"X-Forwarded-Proto": "https",
|
|
"X-Real-IP": "$remote_addr"
|
|
},
|
|
"cert": {
|
|
"type": "acme",
|
|
"email": "admin@example.com"
|
|
}
|
|
}
|
|
},
|
|
"management": {
|
|
"domain": "wall.lan",
|
|
"backend": {
|
|
"host": "127.0.0.1",
|
|
"port": 9090,
|
|
"proto": "http"
|
|
},
|
|
"auth": {
|
|
"user": "admin",
|
|
"htpasswd": "/home/wall/vacuum-wall/data/nginx/.htpasswd"
|
|
}
|
|
},
|
|
"ssl": {
|
|
"protocols": "TLSv1.2 TLSv1.3",
|
|
"ciphers": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384",
|
|
"prefer_server_ciphers": false
|
|
}
|
|
}
|
|
```
|
|
|
|
### Domain Entries
|
|
|
|
The `domains` object maps domain names (keys) to proxy configurations. Each entry produces a separate nginx `server` block.
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `backend` | object | Yes | The upstream service that receives proxied traffic. |
|
|
| `backend.host` | string | Yes | IP address or hostname of the backend service. |
|
|
| `backend.port` | integer | Yes | Port the backend service is listening on. |
|
|
| `backend.proto` | string | Yes | Protocol for the backend connection: `http` or `https`. |
|
|
| `force_ssl` | boolean | No | Enable HTTPS redirect. HTTP requests to this domain receive a 301 redirect to HTTPS. Default: `true`. |
|
|
| `headers` | object | No | Custom headers to set on proxied requests. Supports nginx variable interpolation (e.g., `$remote_addr`). |
|
|
| `cert` | object | No | Certificate configuration for this domain. Required unless the management domain shares its cert. |
|
|
| `cert.type` | string | Yes (if `cert`) | Certificate provisioning method. One of: `acme`, `file`, or `selfsigned`. |
|
|
| `cert.email` | string | Yes (if `acme`) | ACME account email used by Let's Encrypt. |
|
|
| `cert.path` | string | Yes (if `file`) | Full path to the public certificate file (PEM). |
|
|
| `cert.key_path` | string | Yes (if `file`) | Full path to the private key file (PEM). |
|
|
|
|
### Certificate Types
|
|
|
|
| Type | Description |
|
|
|---|---|
|
|
| `acme` | Vacuum Wall uses acme.sh to request and renew a Let's Encrypt certificate via the HTTP-01 challenge. The nginx configuration is temporarily modified to serve the ACME challenge files at `/.well-known/acme-challenge/`. The `email` field is required. |
|
|
| `file` | Use a pre-existing certificate and private key from the local file system. The `path` and `key_path` fields must point to readable PEM files. Vacuum Wall will not attempt to renew these certificates. |
|
|
| `selfsigned` | Vacuum Wall generates a self-signed certificate and private key on first apply. Useful for internal domains or testing. The generated certificate is stored alongside other acme-managed files in `~/.acme.sh/` with a `.selfsigned` marker. |
|
|
|
|
### Management Domain
|
|
|
|
The `management` block configures the Vacuum Wall admin interface itself. It follows the same structure as a domain entry but includes an `auth` block for HTTP Basic Authentication.
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `domain` | string | Yes | The hostname used to access the management WebUI (e.g., `wall.lan`). |
|
|
| `backend` | object | Yes | Points to the Flask app at `127.0.0.1:9090`. |
|
|
| `auth` | object | Yes | HTTP Basic Authentication configuration. |
|
|
| `auth.user` | string | Yes | Username for the `.htpasswd` file. |
|
|
| `auth.htpasswd` | string | Yes | Full path to the `.htpasswd` file containing the username and hashed password. |
|
|
|
|
The `.htpasswd` file can be created with the `htpasswd` utility:
|
|
|
|
```bash
|
|
htpasswd -bc /home/wall/vacuum-wall/data/nginx/.htpasswd admin yourpassword
|
|
```
|
|
|
|
### Global SSL Settings
|
|
|
|
The `ssl` block defines TLS parameters applied to all HTTPS server blocks via the shared snippet.
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `protocols` | string | No | nginx `ssl_protocols` directive value. Default: `TLSv1.2 TLSv1.3`. |
|
|
| `ciphers` | string | No | nginx `ssl_ciphers` directive value. Default is a curated AEAD-only cipher string. |
|
|
| `prefer_server_ciphers` | boolean | No | Whether to prefer server cipher order. Default: `false`. |
|
|
|
|
## WireGuard Configuration
|
|
|
|
**File**: `data/wireguard/config.json`
|
|
|
|
This file defines the WireGuard server interface and all connected peers. The application renders it into `/etc/wireguard/wg0.conf` and applies it with `wg-quick`.
|
|
|
|
```json
|
|
{
|
|
"interface": {
|
|
"name": "wg0",
|
|
"listen_port": 51820,
|
|
"private_key": "kOv8lK...',
|
|
"public_key": "YzP3xI...',
|
|
"addresses": ["10.137.0.1/24"],
|
|
"post_up": null,
|
|
"post_down": null
|
|
},
|
|
"peers": {
|
|
"alice": {
|
|
"public_key": "nR7mQ2...',
|
|
"private_key": "xLpDgF...',
|
|
"endpoint": "203.0.113.1:51820",
|
|
"allowed_ips": ["0.0.0.0/0"],
|
|
"persistent_keepalive": 25,
|
|
"preshared_key": null
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Interface Fields
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `name` | string | Yes | WireGuard interface name. Default: `wg0`. |
|
|
| `listen_port` | integer | Yes | Port the WireGuard interface listens on. Default: `51820`. Must be opened in the firewall. |
|
|
| `private_key` | string | Yes | Base64-encoded private key for the server interface. Use `wg genkey` to generate. |
|
|
| `public_key` | string | Yes | Corresponding public key. Use `wg pubkey` to derive from the private key. |
|
|
| `addresses` | array | Yes | IP address(es) assigned to the server interface in CIDR notation (e.g., `10.137.0.1/24`). |
|
|
| `post_up` | string | No | Shell command to run after the interface is brought up. Common uses: adding NAT rules, enabling IP forwarding for the tunnel. Set to `null` to omit. |
|
|
| `post_down` | string | No | Shell command to run after the interface is brought down. Used to clean up rules added by `post_up`. Set to `null` to omit. |
|
|
|
|
### Peer Fields
|
|
|
|
Peers are stored in an object keyed by a human-readable identifier (e.g., `alice`, `office-laptop`). Each peer entry defines a WireGuard peer configuration.
|
|
|
|
| Field | Type | Required | Description |
|
|
|---|---|---|---|
|
|
| `public_key` | string | Yes | The peer's public key. |
|
|
| `private_key` | string | No | The peer's private key, stored for generating downloadable client configuration files. This value is stripped from all API responses — the WebUI never exposes peer private keys over the network. |
|
|
| `endpoint` | string | No | The peer's public endpoint (IP:port). Required for server-initiated connections (e.g., the server reaching out to a peer behind a firewall). Leave empty or `null` for peer-initiated connections where the peer connects to the server. |
|
|
| `allowed_ips` | array | No | CIDR blocks that traffic from this peer is allowed to route. Defaults to `[]` (no routing restrictions from the server side). `["0.0.0.0/0"]` allows all traffic. `["10.137.0.0/16"]` restricts traffic to the VPN subnet. |
|
|
| `persistent_keepalive` | integer | No | Keepalive interval in seconds. `25` is recommended for peers behind NAT. Set to `0` or `null` to disable. |
|
|
| `preshared_key` | string | No | Optional pre-shared key for post-quantum resistance. Use `wg genpsk` to generate. |
|
|
|
|
### Client Configuration Generation
|
|
|
|
When a peer's `private_key` is set, the WebUI can generate a complete WireGuard client configuration file that the user can download and import into their WireGuard client app. The generated config includes the peer's interface settings, the server as a `[Peer]` entry, and the appropriate `Endpoint` and `AllowedIPs` values. The `private_key` field is written into the client config file for download but is never returned by the API.
|
|
|
|
### Applying Configuration
|
|
|
|
When configuration is saved through the WebUI or API, the application:
|
|
|
|
1. Validates all key pairs and IP ranges.
|
|
2. Renders the `wg0.conf` file from the JSON configuration.
|
|
3. Copies the rendered file to `/etc/wireguard/wg0.conf` using the sudo whitelist.
|
|
4. Runs `sudo wg-quick up wg0` to apply the configuration.
|
|
5. Returns success or error status to the caller.
|
|
|
|
If the interface is already up, `wg-quick up` will reconfigure it in place without dropping existing connections.
|