fix htmx refactor route mismatches and remaining TODO items
- wireguard: POST /peers with JSON encoding (was /add-peer) - rules: delete by rule_id in URL path (was JSON body); pass rule objects with id from server; add hx-disable to initial render - nat: port forward delete uses URL path params to match blueprint - nat: masquerade toggle uses native hx-post/hx-vals (was inline fetch) - app.js renderers updated to use URL path deletes for rules and forwards - remove TODO.md
This commit is contained in:
+277
-204
@@ -34,12 +34,79 @@ Error responses carry one of the following HTTP status codes:
|
||||
| `404` | Not found — the requested resource does not exist |
|
||||
| `500` | Internal server error — unexpected failure in the backend |
|
||||
|
||||
### Route Patterns
|
||||
|
||||
Resource identification uses **path parameters** whenever possible. Exceptions occur only when the identifier is inherently long (e.g., a rich rule string), in which case the body carries the identifier.
|
||||
|
||||
---
|
||||
|
||||
## Firewall API
|
||||
|
||||
Endpoints prefixed with `/api/firewall/...`. Interact with firewalld for zone management, rich rules, NAT, and masquerade.
|
||||
|
||||
### Declarative Config
|
||||
|
||||
The firewall supports a two-step declarative workflow: save config to `config/firewall/config.json`, then apply it to live firewalld. The config tracks `rich_rules` and `forward_ports` with auto-generated `id` fields.
|
||||
|
||||
#### Get Config
|
||||
|
||||
```
|
||||
GET /api/firewall/config
|
||||
```
|
||||
|
||||
Return the current declarative firewall config.
|
||||
|
||||
**Response:** `data` contains the config object with a `zones` mapping.
|
||||
|
||||
#### Save Config
|
||||
|
||||
```
|
||||
POST /api/firewall/config
|
||||
```
|
||||
|
||||
Replace the declarative config. Returns pending changes summary.
|
||||
|
||||
**Request Body:** Request body must contain `zones`.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `config_saved` | `boolean` | Always `true` |
|
||||
| `pending` | `[object, ...]` | List of pending changes |
|
||||
| `needs_apply` | `boolean` | Whether changes need to be applied |
|
||||
| `unmanaged_zones` | `object` | Zones active on system but not in config |
|
||||
|
||||
#### Apply Config
|
||||
|
||||
```
|
||||
POST /api/firewall/config/apply
|
||||
```
|
||||
|
||||
Apply the declarative config to live firewalld. Applies targets, services, interfaces, masquerade, rich rules, and forward ports.
|
||||
|
||||
**Response:** `data` contains `applied_zones` list and backup path.
|
||||
|
||||
#### Check Pending Changes
|
||||
|
||||
```
|
||||
GET /api/firewall/config/pending
|
||||
```
|
||||
|
||||
Compare declarative config against live firewalld state. Returns diff for interfaces, services, targets, masquerade, rich rules, and forward ports.
|
||||
|
||||
**Response:** Same structure as POST /config response.
|
||||
|
||||
#### Partial Update Config
|
||||
|
||||
```
|
||||
PATCH /api/firewall/config
|
||||
```
|
||||
|
||||
Deep-merge the provided fields into the existing config.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
### Zone Management
|
||||
|
||||
#### List All Zones
|
||||
@@ -76,8 +143,8 @@ Return detailed configuration for a single zone.
|
||||
| `services` | `[string, ...]` | Services allowed through the zone |
|
||||
| `ports` | `[string, ...]` | Explicit port rules (format: `"443/tcp"`) |
|
||||
| `masquerade` | `boolean` | Whether masquerade (NAT) is enabled |
|
||||
| `forward_ports` | `[{port: number, proto: string, toaddr: string, toport: number}, ...]` | Port forward rules |
|
||||
| `rich_rules` | `[string, ...]` | Rich rule definitions |
|
||||
| `forward_ports` | `[{port, proto, toaddr, toport}, ...]` | Port forward rules |
|
||||
| `rich_rules` | `[{rule, id}, ...]` | Rich rule definitions with IDs |
|
||||
|
||||
Returns HTTP `404` if the zone does not exist.
|
||||
|
||||
@@ -135,7 +202,7 @@ Replace all interfaces assigned to the zone with the provided list.
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `zone` | `string` | Zone name |
|
||||
| `interfaces` | `[string, ...]` | List of interface names now assigned to the zone |
|
||||
| `interfaces` | `[string, ...]` | List of interface names now assigned |
|
||||
|
||||
---
|
||||
|
||||
@@ -158,9 +225,9 @@ Replace all services allowed in the zone with the provided list.
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `zone` | `string` | Zone name |
|
||||
| `services` | `[string, ...]` | List of services now allowed in the zone |
|
||||
| `services` | `[string, ...]` | List of services now allowed |
|
||||
|
||||
### Firewall Rules
|
||||
### Rich Rules
|
||||
|
||||
#### Add Rich Rule
|
||||
|
||||
@@ -168,7 +235,7 @@ Replace all services allowed in the zone with the provided list.
|
||||
POST /api/firewall/rich-rules
|
||||
```
|
||||
|
||||
Add a firewalld rich rule to a zone.
|
||||
Add a firewalld rich rule to a zone. The rule is persisted to the declarative config with an auto-generated `id`.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
@@ -182,6 +249,7 @@ Add a firewalld rich rule to a zone.
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `zone` | `string` | Zone name |
|
||||
| `id` | `string` | 8-character unique ID |
|
||||
| `rule` | `string` | Full rich rule string |
|
||||
|
||||
---
|
||||
@@ -189,24 +257,19 @@ Add a firewalld rich rule to a zone.
|
||||
#### Remove Rich Rule
|
||||
|
||||
```
|
||||
DELETE /api/firewall/rich-rules
|
||||
DELETE /api/firewall/rich-rules/<zone>/<id>
|
||||
```
|
||||
|
||||
Remove an existing rich rule from a zone. The `rule` string must match exactly.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `zone` | `string` | Yes | Zone the rule belongs to |
|
||||
| `rule` | `string` | Yes | Exact rich rule string to remove |
|
||||
Remove a rich rule by zone and auto-generated ID. (The rule string itself is too long for a URL path.)
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `zone` | `string` | Zone name |
|
||||
| `rule` | `string` | Exact rich rule string that was removed |
|
||||
| `id` | `string` | ID of the removed rule |
|
||||
|
||||
Returns HTTP `404` if the rule ID is not found.
|
||||
|
||||
---
|
||||
|
||||
@@ -216,15 +279,64 @@ Remove an existing rich rule from a zone. The `rule` string must match exactly.
|
||||
GET /api/firewall/rich-rules/<zone>
|
||||
```
|
||||
|
||||
Return all rich rules for the specified zone.
|
||||
Return all rich rules for the specified zone, each with an `id` and `rule` string.
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `[string, ...]` | Rich rule strings |
|
||||
| `data` | `[{id, rule}, ...]` | Rich rules with IDs |
|
||||
|
||||
### NAT
|
||||
### Port Forwarding
|
||||
|
||||
#### Add Port Forward
|
||||
|
||||
```
|
||||
POST /api/firewall/forward-port
|
||||
```
|
||||
|
||||
Add a port forwarding rule to a zone. The rule is persisted to the declarative config with an auto-generated `id`.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `zone` | `string` | Yes | Zone to add the rule to |
|
||||
| `port` | `number` | Yes | External port |
|
||||
| `proto` | `string` | Yes | Protocol (`"tcp"` or `"udp"`) |
|
||||
| `toaddr` | `string` | No | Internal destination address |
|
||||
| `toport` | `number` | No | Internal destination port |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `zone` | `string` | Zone name |
|
||||
| `id` | `string` | 8-character unique ID |
|
||||
| `port` | `number` | External port |
|
||||
| `proto` | `string` | Protocol (`"tcp"` or `"udp"`) |
|
||||
|
||||
---
|
||||
|
||||
#### Remove Port Forward
|
||||
|
||||
```
|
||||
DELETE /api/firewall/forward-port/<zone>/<port>/<proto>
|
||||
```
|
||||
|
||||
Remove a port forwarding rule. Zone, port, and protocol are all path parameters.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `zone` | `string` | Zone name |
|
||||
| `port` | `number` | External port |
|
||||
| `proto` | `string` | Protocol (`"tcp"` or `"udp"`) |
|
||||
|
||||
Returns HTTP `404` if the forward port is not found.
|
||||
|
||||
### Masquerade (NAT)
|
||||
|
||||
#### Enable / Disable Masquerade
|
||||
|
||||
@@ -246,63 +358,7 @@ Toggle masquerade (source NAT) for a zone.
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `zone` | `string` | Zone name |
|
||||
| `masquerade` | `boolean` | Whether masquerade is now enabled for the zone |
|
||||
|
||||
---
|
||||
|
||||
#### Add Port Forward
|
||||
|
||||
```
|
||||
POST /api/firewall/forward-port
|
||||
```
|
||||
|
||||
Add a port forwarding rule to a zone.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `zone` | `string` | Yes | Zone to add the rule to |
|
||||
| `port` | `number` | Yes | External port |
|
||||
| `proto` | `string` | Yes | Protocol (`"tcp"` or `"udp"`) |
|
||||
| `toaddr` | `string` | No | Internal destination address |
|
||||
| `toport` | `number` | No | Internal destination port |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `zone` | `string` | Zone name |
|
||||
| `port` | `number` | External port |
|
||||
| `proto` | `string` | Protocol (`"tcp"` or `"udp"`) |
|
||||
|
||||
---
|
||||
|
||||
#### Remove Port Forward
|
||||
|
||||
```
|
||||
DELETE /api/firewall/forward-port
|
||||
```
|
||||
|
||||
Remove a port forwarding rule. The body must match the original rule exactly.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `zone` | `string` | Yes | Zone the rule belongs to |
|
||||
| `port` | `number` | Yes | External port |
|
||||
| `proto` | `string` | Yes | Protocol (`"tcp"` or `"udp"`) |
|
||||
| `toaddr` | `string` | No | Internal destination address |
|
||||
| `toport` | `number` | No | Internal destination port |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `zone` | `string` | Zone name |
|
||||
| `port` | `number` | External port |
|
||||
| `proto` | `string` | Protocol (`"tcp"` or `"udp"`) |
|
||||
| `masquerade` | `boolean` | Whether masquerade is now enabled |
|
||||
|
||||
### Info
|
||||
|
||||
@@ -406,24 +462,74 @@ Write the in-memory configuration to `/etc/dnsmasq.d/vacuum-wall.conf` and reloa
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
### Leases
|
||||
### Status
|
||||
|
||||
#### Get Live Leases
|
||||
#### Get Service Status
|
||||
|
||||
```
|
||||
GET /api/dhcp/leases
|
||||
GET /api/dhcp/status
|
||||
```
|
||||
|
||||
Return the current DHCP lease table from dnsmasq.
|
||||
Return the current service status, config summary, and active lease count.
|
||||
|
||||
**Response:**
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `[object, ...]` | Array of lease objects |
|
||||
| `service_active` | `boolean` | Whether dnsmasq is running |
|
||||
| `config_file_exists` | `boolean` | Whether config file exists on disk |
|
||||
| `config_in_sync` | `boolean` | Whether disk config matches expected |
|
||||
| `dhcp_ranges` | `number` | Number of DHCP ranges |
|
||||
| `static_leases` | `number` | Number of static leases |
|
||||
| `custom_dns_records` | `number` | Number of custom DNS records |
|
||||
| `upstreams` | `[string, ...]` | Upstream DNS servers |
|
||||
| `domain` | `string` | Local DNS domain |
|
||||
| `active_leases` | `number` | Number of active leases |
|
||||
| `leases` | `[object, ...]` | Active lease objects |
|
||||
|
||||
### DHCP Ranges
|
||||
|
||||
#### Add Range
|
||||
|
||||
```
|
||||
POST /api/dhcp/ranges
|
||||
```
|
||||
|
||||
Add or replace the DHCP range for a given interface.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `interface` | `string` | No | Interface name (empty = all interfaces) |
|
||||
| `start` | `string` | Yes | Start of IP range |
|
||||
| `end` | `string` | Yes | End of IP range |
|
||||
| `lease_time` | `string` | No | Lease duration; defaults to `"12h"` |
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
---
|
||||
|
||||
#### Remove Range
|
||||
|
||||
```
|
||||
DELETE /api/dhcp/ranges
|
||||
```
|
||||
|
||||
Remove a DHCP range. Body contains identifying fields.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `interface` | `string` | Yes | Interface name |
|
||||
| `start` | `string` | Yes | Start of IP range |
|
||||
| `end` | `string` | Yes | End of IP range |
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
### Static Leases
|
||||
|
||||
#### Add Static Lease
|
||||
|
||||
```
|
||||
@@ -446,28 +552,38 @@ Add a static (reserved) DHCP lease.
|
||||
|-------|------|-------------|
|
||||
| `mac` | `string` | MAC address |
|
||||
| `ip` | `string` | Reserved IP address |
|
||||
| `hostname` | `string` | Hostname for the reservation |
|
||||
| `hostname` | `string` | Hostname |
|
||||
|
||||
---
|
||||
|
||||
#### Remove Static Lease
|
||||
|
||||
```
|
||||
DELETE /api/dhcp/static-lease?mac=aa:bb:cc:dd:ee:ff
|
||||
DELETE /api/dhcp/static-lease/<mac>
|
||||
```
|
||||
|
||||
Remove a previously configured static lease.
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `mac` | `string` | Yes | MAC address of the lease to remove |
|
||||
Remove a static lease by MAC address.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
Returns HTTP `404` if no matching lease is found.
|
||||
|
||||
### Live Leases
|
||||
|
||||
#### Get Live Leases
|
||||
|
||||
```
|
||||
GET /api/dhcp/leases
|
||||
```
|
||||
|
||||
Return the current DHCP lease table from dnsmasq.
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `[object, ...]` | Array of lease objects |
|
||||
|
||||
### DNS Records
|
||||
|
||||
#### Add DNS Record
|
||||
@@ -484,6 +600,7 @@ Add a custom DNS A record served by dnsmasq.
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Fully qualified domain name |
|
||||
| `address` | `string` | Yes | IP address to resolve to |
|
||||
| `hostname` | `string` | No | Short hostname |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -498,16 +615,10 @@ Add a custom DNS A record served by dnsmasq.
|
||||
#### Remove DNS Record
|
||||
|
||||
```
|
||||
DELETE /api/dhcp/dns-record?name=nas.lan
|
||||
DELETE /api/dhcp/dns-record/<name>
|
||||
```
|
||||
|
||||
Remove a custom DNS record.
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Fully qualified domain name to remove |
|
||||
Remove a custom DNS record by domain name.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
@@ -553,6 +664,8 @@ Add a new reverse proxy domain.
|
||||
| `backend_host` | `string` | Yes | Backend server IP or hostname |
|
||||
| `backend_port` | `number` | Yes | Backend server port |
|
||||
| `backend_proto` | `string` | No | Backend protocol (`"http"` or `"https"`); defaults to `"http"` |
|
||||
| `cert` | `string` | No | Certificate domain |
|
||||
| `extra_headers` | `object` | No | Extra proxy headers |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -572,14 +685,7 @@ GET /api/proxy/domains/<domain>
|
||||
|
||||
Return the configuration for a single proxy domain.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `domain` | `string` | Domain name |
|
||||
| `backend_host` | `string` | Backend server address |
|
||||
| `backend_port` | `number` | Backend server port |
|
||||
| `backend_proto` | `string` | Backend protocol |
|
||||
**Response (`data`):** Domain name plus backend configuration fields.
|
||||
|
||||
Returns HTTP `404` if the domain is not configured.
|
||||
|
||||
@@ -591,15 +697,9 @@ Returns HTTP `404` if the domain is not configured.
|
||||
PUT /api/proxy/domains/<domain>
|
||||
```
|
||||
|
||||
Update one or more fields of an existing domain entry. Only the fields present in the body are modified.
|
||||
Update one or more fields of an existing domain entry. Only fields present in the body are modified.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `backend_host` | `string` | No | Backend server IP or hostname |
|
||||
| `backend_port` | `number` | No | Backend server port |
|
||||
| `backend_proto` | `string` | No | Backend protocol |
|
||||
**Request Body:** Any subset of (`backend_host`, `backend_port`, `backend_proto`, `cert`, `extra_headers`).
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -649,15 +749,17 @@ Returns HTTP `500` if nginx config generation fails or the reload fails.
|
||||
POST /api/proxy/test
|
||||
```
|
||||
|
||||
Run `nginx -t` against the generated configuration without reloading. Useful for validating changes before applying.
|
||||
Run `nginx -t` against the generated configuration without reloading.
|
||||
|
||||
**Response:**
|
||||
**Response (valid):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data.valid` | `boolean` | Whether the configuration syntax is valid |
|
||||
| `data.valid` | `boolean` | Always `true` |
|
||||
| `data.output` | `string` | Raw nginx test output |
|
||||
|
||||
**Error (invalid):** HTTP `400` with standard `{"ok": false, "error": "<nginx output>"}` response.
|
||||
|
||||
### Management
|
||||
|
||||
#### Configure Management WebUI Proxy
|
||||
@@ -675,13 +777,11 @@ Configure the nginx proxy block for the management WebUI itself, including optio
|
||||
| `domain` | `string` | Yes | Management domain (e.g., `"myhost.local"`) |
|
||||
| `flask_host` | `string` | No | Flask app bind host; defaults to `"127.0.0.1"` |
|
||||
| `flask_port` | `number` | No | Flask app bind port; defaults to `9090` |
|
||||
| `auth_user` | `string` | No | Username for basic auth. An `.htpasswd` entry is created when this field is present. |
|
||||
| `auth_pass` | `string` | No | Password for basic auth. Used together with `auth_user`. |
|
||||
| `auth_user` | `string` | No | Username for basic auth |
|
||||
| `auth_pass` | `string` | No | Password for basic auth |
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
If `auth_user` and `auth_pass` are provided, the endpoint creates or updates the corresponding `.htpasswd` file entry.
|
||||
|
||||
---
|
||||
|
||||
## Certificate API
|
||||
@@ -704,15 +804,7 @@ Return all managed certificates with metadata.
|
||||
|-------|------|-------------|
|
||||
| `data` | `[object, ...]` | Array of certificate objects |
|
||||
|
||||
Each certificate object:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `domain` | `string` | Domain the certificate covers |
|
||||
| `expires_at` | `string` | Expiration date (ISO 8601) |
|
||||
| `days_until_expiry` | `number` | Remaining days until expiration |
|
||||
| `cert_path` | `string` | Path to the certificate file |
|
||||
| `key_path` | `string` | Path to the private key file |
|
||||
Each certificate object contains `domain`, `expires_at`, `days_until_expiry`, `cert_path`, `key_path`.
|
||||
|
||||
---
|
||||
|
||||
@@ -724,15 +816,7 @@ GET /api/certs/<domain>
|
||||
|
||||
Return details for a single certificate.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `domain` | `string` | Domain |
|
||||
| `expires_at` | `string` | Expiration date (ISO 8601) |
|
||||
| `days_until_expiry` | `number` | Remaining days |
|
||||
| `cert_path` | `string` | Certificate file path |
|
||||
| `key_path` | `string` | Private key file path |
|
||||
**Response (`data`):** Fields: `domain`, `expires_at`, `days_until_expiry`, `cert_path`, `key_path`.
|
||||
|
||||
Returns HTTP `404` if no certificate is found for the domain.
|
||||
|
||||
@@ -755,7 +839,7 @@ Request a new certificate for a domain.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
Returns HTTP `400` if the domain is missing or the request is malformed. Returns HTTP `500` if the ACME challenge or certificate issuance fails.
|
||||
Returns HTTP `400` if the domain is missing. Returns HTTP `500` if issuance fails.
|
||||
|
||||
---
|
||||
|
||||
@@ -765,7 +849,7 @@ Returns HTTP `400` if the domain is missing or the request is malformed. Returns
|
||||
POST /api/certs/<domain>/renew
|
||||
```
|
||||
|
||||
Force-renew an existing certificate, regardless of its current expiry status.
|
||||
Force-renew an existing certificate.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
@@ -793,7 +877,7 @@ Returns HTTP `404` if the certificate is not found.
|
||||
POST /api/certs/email
|
||||
```
|
||||
|
||||
Set or update the ACME account contact email (used by the CA for expiration and security notices).
|
||||
Set or update the ACME account contact email.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
@@ -801,11 +885,7 @@ Set or update the ACME account contact email (used by the CA for expiration and
|
||||
|-------|------|----------|-------------|
|
||||
| `email` | `string` | Yes | Contact email address |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `email` | `string` | Contact email address |
|
||||
**Response (`data`):** Returns the set `email` field.
|
||||
|
||||
---
|
||||
|
||||
@@ -821,13 +901,13 @@ Endpoints prefixed with `/api/wireguard/...`. Manage the WireGuard VPN server, p
|
||||
GET /api/wireguard/config
|
||||
```
|
||||
|
||||
Return the current WireGuard server configuration. The `private_key` field is stripped from the response.
|
||||
Return the current WireGuard server configuration. The `private_key` field is stripped.
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `object` | Full WireGuard configuration dictionary (`private_key` omitted) |
|
||||
| `data` | `object` | WireGuard config (`private_key` omitted) |
|
||||
|
||||
---
|
||||
|
||||
@@ -845,11 +925,7 @@ Replace the entire WireGuard configuration. The `private_key` field is stripped
|
||||
|-------|------|----------|-------------|
|
||||
| *(entire body)* | `object` | Yes | Complete WireGuard configuration object |
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `object` | Updated configuration (`private_key` omitted) |
|
||||
**Response:** `data` contains the updated configuration (`private_key` omitted).
|
||||
|
||||
### Tunnel Control
|
||||
|
||||
@@ -859,11 +935,21 @@ Replace the entire WireGuard configuration. The `private_key` field is stripped
|
||||
POST /api/wireguard/apply
|
||||
```
|
||||
|
||||
Write the current configuration to `wg0.conf` on disk and bring the WireGuard tunnel up.
|
||||
Write the current configuration to `wg0.conf` and bring the tunnel up.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
Returns HTTP `500` if config write or interface bring-up fails.
|
||||
---
|
||||
|
||||
#### Start Tunnel
|
||||
|
||||
```
|
||||
POST /api/wireguard/up
|
||||
```
|
||||
|
||||
Alias for `/api/wireguard/apply` — write config and bring the tunnel up.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
---
|
||||
|
||||
@@ -885,15 +971,15 @@ Bring down the WireGuard tunnel interface (`wg0`).
|
||||
GET /api/wireguard/status
|
||||
```
|
||||
|
||||
Return live tunnel state, including interface metrics and per-peer connection statistics.
|
||||
Return live tunnel state with interface metrics and per-peer connection statistics.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `up` | `boolean` | Whether the tunnel interface is up |
|
||||
| `interface` | `object` | Interface info (listen port, public key, etc.) |
|
||||
| `peers` | `[object, ...]` | Per-peer connection stats (handshake time, transfer bytes, endpoint, etc.) |
|
||||
| `interface` | `object` | Interface info (listen port, public key) |
|
||||
| `peers` | `[object, ...]` | Per-peer stats (handshake, bytes, endpoint) |
|
||||
|
||||
---
|
||||
|
||||
@@ -903,19 +989,35 @@ Return live tunnel state, including interface metrics and per-peer connection st
|
||||
POST /api/wireguard/initialize
|
||||
```
|
||||
|
||||
Perform first-time setup: generate a server key pair, write an initial configuration, and prepare for peer enrollment. This endpoint is idempotent — calling it multiple times has no additional effect.
|
||||
First-time setup: generate server key pair, write initial config. Idempotent.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
### Peer Management
|
||||
|
||||
#### List Peers
|
||||
|
||||
```
|
||||
GET /api/wireguard/peers
|
||||
```
|
||||
|
||||
Return all configured peers. Private keys are stripped.
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `[object, ...]` | Peer objects (private keys omitted) |
|
||||
|
||||
---
|
||||
|
||||
#### Add Peer
|
||||
|
||||
```
|
||||
POST /api/wireguard/add-peer
|
||||
POST /api/wireguard/peers
|
||||
```
|
||||
|
||||
Add a new WireGuard peer. A key pair is auto-generated for the peer. The response includes peer details with the private key stripped.
|
||||
Add a new WireGuard peer. A key pair is auto-generated. Private key stripped from response.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
@@ -924,33 +1026,20 @@ Add a new WireGuard peer. A key pair is auto-generated for the peer. The respons
|
||||
| `name` | `string` | Yes | Peer identifier name |
|
||||
| `endpoint` | `string` | No | Allowed endpoint address (`"ip:port"`) |
|
||||
| `allowed_ips` | `[string, ...]` | No | Allowed IPs; defaults to `["0.0.0.0/0"]` |
|
||||
| `persistent_keepalive` | `number` | No | Persistent keepalive interval in seconds |
|
||||
| `persistent_keepalive` | `number` | No | Persistent keepalive interval (seconds) |
|
||||
| `preshared_key` | `string` | No | Preshared key |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `string` | Peer name |
|
||||
| `public_key` | `string` | Peer's public key |
|
||||
| `allowed_ips` | `[string, ...]` | Allowed IPs |
|
||||
| `endpoint` | `string` | Allowed endpoint |
|
||||
| `persistent_keepalive` | `number` | Keepalive interval |
|
||||
**Response (`data`):** Peer object with `name`, `public_key`, `allowed_ips`, etc. (no `private_key`).
|
||||
|
||||
---
|
||||
|
||||
#### Remove Peer
|
||||
|
||||
```
|
||||
DELETE /api/wireguard/remove-peer?name=alice
|
||||
DELETE /api/wireguard/peers/<name>
|
||||
```
|
||||
|
||||
Remove a configured peer.
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Peer name to remove |
|
||||
Remove a configured peer by name.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -962,35 +1051,19 @@ Returns HTTP `404` if the peer is not found.
|
||||
|
||||
---
|
||||
|
||||
#### List Peers
|
||||
|
||||
```
|
||||
GET /api/wireguard/peers
|
||||
```
|
||||
|
||||
Return all configured peers. Private keys are stripped from the response.
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `[object, ...]` | Array of peer objects (private keys omitted) |
|
||||
|
||||
---
|
||||
|
||||
#### Peer Connection Status
|
||||
|
||||
```
|
||||
GET /api/wireguard/peer-status
|
||||
```
|
||||
|
||||
Return live per-peer connection status from `wg show`, including last handshake time, transfer bytes, and current endpoint.
|
||||
Return live per-peer connection status from `wg show`.
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `[object, ...]` | Array of live peer status objects |
|
||||
| `data` | `[object, ...]` | Live peer status (handshake time, bytes, endpoint) |
|
||||
|
||||
### Client Configuration
|
||||
|
||||
@@ -1000,21 +1073,21 @@ Return live per-peer connection status from `wg show`, including last handshake
|
||||
POST /api/wireguard/generate-client
|
||||
```
|
||||
|
||||
Generate a complete WireGuard client configuration file for provisioning a device. The returned config includes the peer's private key for the client to use.
|
||||
Generate a complete WireGuard client configuration file. The returned config includes the peer's private key for provisioning.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Peer name to generate config for |
|
||||
| `server_endpoint` | `string` | Yes | Server public address (`"ip:port"`) for the client's `[Peer]` section |
|
||||
| `server_endpoint` | `string` | Yes | Server public address (`"ip:port"`) |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `config` | `string` | Complete WireGuard client config text (`[Interface]` + `[Peer]` block) |
|
||||
| `config` | `string` | Complete client config text (`[Interface]` + `[Peer]`) |
|
||||
|
||||
The client config includes the generated private key so the client can be provisioned directly. Note that this is the only endpoint that returns a WireGuard private key — all other endpoints strip private keys from responses.
|
||||
This is the only endpoint that returns a WireGuard private key. All other endpoints strip private keys from responses.
|
||||
|
||||
Returns HTTP `404` if the peer is not found.
|
||||
Returns HTTP `404` if the peer is not found.
|
||||
Reference in New Issue
Block a user