Replace the legacy top-level management key with a unified paths-based model. Each domain now contains a paths map where each entry defines its own backend, auth, headers, and flags (is_management, is_websocket). - Add _migrate_config() to auto-migrate legacy formats on first load - Remove set_management_proxy() and POST_NGINX_MANAGEMENT endpoint - Update server_block.conf template to iterate paths with per-location auth - Update daemon handler, API blueprint, state collector, and install script - Add server config generation tests for paths, WebSocket, auth inheritance - Update frontend proxy page to display per-path rows with flags
35 KiB
REST API Reference
All endpoints are served by the management WebUI Flask application bound to 127.0.0.1:9090, proxied through nginx with SSL termination and HTTP basic authentication. Requests target the management domain (e.g., https://<hostname>.local/api/...).
Every request and response uses Content-Type: application/json.
Conventions
Success Responses
{
"ok": true,
"data": <value>
}
The data field contains the payload, which may be an object, array, string, or null.
Error Responses
{
"ok": false,
"error": "<human-readable message>"
}
Error responses carry one of the following HTTP status codes:
| Code | Meaning |
|---|---|
400 |
Bad request — invalid body, missing required field, or malformed value |
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. Returns pending changes summary.
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 |
Zone Management
List All Zones
GET /api/firewall/zones
Returns active zone-to-interface mappings and all available zone definitions.
Response:
| Field | Type | Description |
|---|---|---|
data.active |
object<name, [interface, ...]> |
Currently assigned interfaces per zone |
data.available |
[string, ...] |
All zones known to firewalld |
Get Zone Details
GET /api/firewall/zones/<name>
Return detailed configuration for a single zone.
Response (data):
| Field | Type | Description |
|---|---|---|
target |
string |
Zone target (e.g., "default", "ACCEPT", "REJECT") |
interfaces |
[string, ...] |
Interfaces assigned to this 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, proto, toaddr, toport}, ...] |
Port forward rules |
rich_rules |
[{rule, id}, ...] |
Rich rule definitions with IDs |
Returns HTTP 404 if the zone does not exist.
Create Zone
POST /api/firewall/zones
Create a new firewalld zone.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Zone name |
target |
string |
No | Zone target; defaults to "default" |
Response: data is null on success.
Returns HTTP 400 if the zone already exists.
Delete Zone
DELETE /api/firewall/zones/<name>
Remove a zone from firewalld.
Response: data is null on success.
Returns HTTP 404 if the zone does not exist.
Zone Configuration
Set Zone Interfaces
POST /api/firewall/zones/<name>/interfaces
Replace all interfaces assigned to the zone with the provided list.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
interfaces |
[string, ...] |
Yes | List of interface names |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
interfaces |
[string, ...] |
List of interface names now assigned |
Set Zone Services
POST /api/firewall/zones/<name>/services
Replace all services allowed in the zone with the provided list.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
services |
[string, ...] |
Yes | List of firewalld service names |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
services |
[string, ...] |
List of services now allowed |
Rich Rules
Add Rich Rule
POST /api/firewall/rich-rules
Add a firewalld rich 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 |
rule |
string |
Yes | Full rich rule string |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
id |
string |
8-character unique ID |
rule |
string |
Full rich rule string |
Remove Rich Rule
DELETE /api/firewall/rich-rules/<zone>/<id>
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 |
id |
string |
ID of the removed rule |
Returns HTTP 404 if the rule ID is not found.
List Rich Rules
GET /api/firewall/rich-rules/<zone>
Return all rich rules for the specified zone, each with an id and rule string.
Response:
| Field | Type | Description |
|---|---|---|
data |
[{id, rule}, ...] |
Rich rules with IDs |
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
POST /api/firewall/masquerade
Toggle masquerade (source NAT) for a zone.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
zone |
string |
Yes | Zone to configure |
enable |
boolean |
Yes | true to enable, false to disable |
Response (data):
| Field | Type | Description |
|---|---|---|
zone |
string |
Zone name |
masquerade |
boolean |
Whether masquerade is now enabled |
Info
Available Services
GET /api/firewall/services
List all service names known to firewalld.
Response:
| Field | Type | Description |
|---|---|---|
data |
[string, ...] |
Service names |
Available Interfaces
GET /api/firewall/interfaces
List all network interfaces currently available on the system.
Response:
| Field | Type | Description |
|---|---|---|
data |
[string, ...] |
Interface names |
DHCP / DNS API
Endpoints prefixed with /api/dhcp/.... Manage dnsmasq configuration, DHCP leases, and custom DNS records.
Configuration
Get Configuration
GET /api/dhcp/config
Return the current DHCP/DNS configuration object.
Response:
| Field | Type | Description |
|---|---|---|
data |
object |
Full dnsmasq configuration dictionary |
Replace Configuration
POST /api/dhcp/config
Replace the entire configuration with the provided JSON object.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
| (entire body) | object |
Yes | Complete configuration object |
Response: data is null on success.
Partial Update Configuration
PATCH /api/dhcp/config
Deep-merge the provided fields into the existing configuration. Useful for targeted updates (e.g., changing DNS upstream servers without replacing the full config).
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
| (any subset) | any |
Yes | Fields to merge into the existing config |
Response: data is null on success.
Apply Configuration
POST /api/dhcp/apply
Write the in-memory configuration to /etc/dnsmasq.d/vacuum-wall.conf and reload the dnsmasq service.
Response: data is null on success.
Status
Get Service Status
GET /api/dhcp/status
Return the current service status, config summary, and active lease count.
Response (data):
| Field | Type | Description |
|---|---|---|
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
POST /api/dhcp/static-lease
Add a static (reserved) DHCP lease.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
mac |
string |
Yes | MAC address ("aa:bb:cc:dd:ee:ff") |
ip |
string |
Yes | Reserved IP address |
hostname |
string |
No | Hostname for the reservation |
Response (data):
| Field | Type | Description |
|---|---|---|
mac |
string |
MAC address |
ip |
string |
Reserved IP address |
hostname |
string |
Hostname |
Remove Static Lease
DELETE /api/dhcp/static-lease/<mac>
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
POST /api/dhcp/dns-record
Add a custom DNS A record served by dnsmasq.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Fully qualified domain name |
address |
string |
Yes | IP address to resolve to |
hostname |
string |
No | Short hostname |
Response (data):
| Field | Type | Description |
|---|---|---|
name |
string |
Fully qualified domain name |
address |
string |
IP address |
hostname |
string |
Short hostname |
Remove DNS Record
DELETE /api/dhcp/dns-record/<name>
Remove a custom DNS record by domain name.
Response: data is null on success.
Returns HTTP 404 if no matching record is found.
Proxy API
Endpoints prefixed with /api/proxy/.... Manage reverse proxy domains, nginx configuration generation, and the management WebUI proxy.
Configuration
Get Proxy Configuration
GET /api/proxy/config
Return the current proxy configuration object.
Response:
| Field | Type | Description |
|---|---|---|
data |
object |
Full proxy configuration dictionary |
Replace Proxy Configuration
POST /api/proxy/config
Replace the entire proxy configuration with the provided JSON object.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
| (entire body) | object |
Yes | Complete proxy configuration object |
Response: data is null on success.
Partial Update Proxy Configuration
PATCH /api/proxy/config
Deep-merge the provided fields into the existing proxy configuration.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
| (any subset) | any |
Yes | Fields to merge into the config |
Response: data is null on success.
SSL
Apply SSL Snippet
POST /api/proxy/ssl-apply
Write the global nginx SSL snippet configuration.
Response: data is null on success.
Domain Management
List All Domains
GET /api/proxy/domains
Return all configured proxy domains. The response is flattened by path — each path within a domain produces a separate entry.
Response:
| Field | Type | Description |
|---|---|---|
data |
[object, ...] |
Array of path-level domain configuration objects |
Each entry contains domain (string), path (string), backend (object with host, port, proto), online (boolean), force_ssl (boolean), and optionally is_management or is_websocket flags.
Add Domain
POST /api/proxy/domains
Add a new reverse proxy domain. Accepts two modes:
Paths mode (preferred):
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string |
Yes | Domain name to proxy |
paths |
object |
Yes | Path-to-config map. Each path entry must have a backend key with host, port, proto. |
cert |
string |
No | Certificate type |
force_ssl |
boolean |
No | HTTPS redirect flag (default true) |
Legacy mode (backward compatible):
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string |
Yes | Domain name to proxy |
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 type |
extra_headers |
object |
No | Extra proxy headers |
Response (data):
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain name |
Returns HTTP 400 if the domain is already configured.
Get Domain Details
GET /api/proxy/domains/<domain>
Return the configuration for a single proxy domain.
Response (data): Domain name plus backend configuration fields.
Returns HTTP 404 if the domain is not configured.
Update Domain
PUT /api/proxy/domains/<domain>
Update one or more fields of an existing domain entry. Only fields present in the body are modified. Supports both domain-level keys (paths, force_ssl, cert, auth) and path-level shorthand (backend, headers for the root path).
Request Body: Any subset of (paths, backend, backend_host, backend_port, backend_proto, cert, extra_headers, force_ssl, auth).
Response (data):
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain name |
Returns HTTP 404 if the domain is not configured.
Remove Domain
DELETE /api/proxy/domains/<domain>
Remove a proxy domain and its nginx configuration.
Response (data):
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain name |
Returns HTTP 404 if the domain is not configured.
Apply / Test
Apply Configuration
POST /api/proxy/apply
Regenerate nginx configuration files for all proxy domains and reload the nginx service.
Response: data is null on success.
Returns HTTP 500 if nginx config generation fails or the reload fails.
Test Configuration
POST /api/proxy/test
Run nginx -t against the generated configuration without reloading.
Response (valid):
| Field | Type | Description |
|---|---|---|
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 Proxy
The legacy
POST /api/proxy/managementendpoint has been removed. The management WebUI proxy is now configured as a regular domain entry withis_management: trueon the root path andis_websocket: trueon the/wspath. Use the standard domain add/update endpoints to configure it.
Certificate API
Endpoints prefixed with /api/certs/.... Manage TLS certificates via ACME (ZeroSSL, Let's Encrypt, etc.).
Listing & Details
List All Certificates
GET /api/certs/list
Return all managed certificates with metadata.
Response:
| Field | Type | Description |
|---|---|---|
data |
[object, ...] |
Array of certificate objects |
Each certificate object contains domain, expires_at, days_until_expiry, cert_path, key_path.
Get Certificate Details
GET /api/certs/<domain>
Return details for a single certificate.
Response (data): Fields: domain, expires_at, days_until_expiry, cert_path, key_path.
Returns HTTP 404 if no certificate is found for the domain.
Operations
Issue Certificate
POST /api/certs/issue
Request a new certificate for a domain. Returns HTTP 500 if issuance fails.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string |
Yes | Domain to issue the certificate for |
email |
string |
No | ACME contact email — deprecated, ignored in favor of the registered account email |
webroot |
string |
No | Custom webroot path for HTTP-01 validation |
Response: data is null on success.
Returns HTTP 400 if the domain is missing. An ACME account must be registered before issuance (verified by the account_registered blocking check in the validation pipeline).
Renew Certificate
POST /api/certs/<domain>/renew
Force-renew an existing certificate.
Response: data is null on success.
Returns HTTP 404 if the certificate is not found. Returns HTTP 500 if renewal fails.
Remove Certificate
DELETE /api/certs/<domain>
Delete a certificate and remove it from auto-renewal tracking.
Response: data is null on success.
Returns HTTP 404 if the certificate is not found.
Account
Get ACME Account Status
GET /api/certs/account
Return the ACME account registration status.
Response (data):
| Field | Type | Description |
|---|---|---|
registered |
boolean |
Whether an ACME account is registered |
email |
string |
Registered contact email (empty if unregistered) |
ca |
string |
CA provider (e.g., "let's encrypt", "ZeroSSL") (empty if unregistered) |
Returns HTTP 500 if the account status cannot be determined.
Register ACME Account
POST /api/certs/account/register
Register a new ACME account with the specified email and CA provider.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string |
Yes | Contact email address |
server |
string |
No | CA provider: "letsencrypt" or "zerossl". Default: "letsencrypt" |
Response (data):
| Field | Type | Description |
|---|---|---|
registered |
boolean |
Always true on success |
email |
string |
Registered contact email |
ca |
string |
CA provider |
Returns HTTP 400 if the email is missing or invalid. Returns HTTP 500 if registration fails. An ACME account must be registered before certificates can be issued.
Deactivate ACME Account
DELETE /api/certs/account
Deactivate the ACME account via acme.sh --deactivate-account. Clears the email and ca fields from config/acme/config.json.
Response (data):
| Field | Type | Description |
|---|---|---|
email |
string |
Empty string indicating the account was deactivated |
Returns HTTP 500 if deactivation fails.
Set ACME Contact Email
POST /api/certs/email
Set or update the ACME account contact email.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string |
Yes | Contact email address |
Response (data): Returns the set email field.
Generate Self-Signed Certificate
POST /api/certs/self-signed
Generate a self-signed certificate for a domain. Idempotent — skips if fullchain.cer and <domain>.key already exist at data/acme/<domain>/.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string |
Yes | Domain name for the certificate CN |
days |
number |
No | Validity in days; defaults to 365 |
Response (data):
| Field | Type | Description |
|---|---|---|
domain |
string |
Domain name |
cert |
string |
Path to fullchain.cer |
key |
string |
Path to <domain>.key |
generated |
boolean |
true if a new cert was created, false if existing cert was reused |
WireGuard API
Endpoints prefixed with /api/wireguard/.... Manage the WireGuard VPN server, peers, and client configuration.
Configuration
Get Configuration
GET /api/wireguard/config
Return the current WireGuard server configuration. The private_key field is stripped.
Response:
| Field | Type | Description |
|---|---|---|
data |
object |
WireGuard config (private_key omitted) |
Replace Configuration
POST /api/wireguard/config
Replace the entire WireGuard configuration. The private_key field is stripped from the response.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
| (entire body) | object |
Yes | Complete WireGuard configuration object |
Response: data is null on success.
Partial Update Configuration
PATCH /api/wireguard/config
Deep-merge the provided fields into the existing configuration.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
| (any subset) | any |
Yes | Fields to merge into the existing config |
Response: data is null on success.
Tunnel Control
Apply Configuration
POST /api/wireguard/apply
Write the current configuration to wg0.conf and bring the tunnel up.
Response: data is null on success.
Start Tunnel
POST /api/wireguard/up
Alias for /api/wireguard/apply — write config and bring the tunnel up.
Response: data is null on success.
Bring Tunnel Down
POST /api/wireguard/down
Bring down the WireGuard tunnel interface (wg0).
Response: data is null on success.
Status
Tunnel Status
GET /api/wireguard/status
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) |
peers |
[object, ...] |
Per-peer stats (handshake, bytes, endpoint) |
Initialize
POST /api/wireguard/initialize
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/peers
Add a new WireGuard peer. A key pair is auto-generated. Private key stripped from response.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
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 (seconds) |
preshared_key |
string |
No | Preshared key |
Response (data): Peer object with name, public_key, allowed_ips, etc. (no private_key).
Remove Peer
DELETE /api/wireguard/peers/<name>
Remove a configured peer by name.
Response (data):
| Field | Type | Description |
|---|---|---|
name |
string |
Peer name |
Returns HTTP 404 if the peer is not found.
Peer Connection Status
GET /api/wireguard/peer-status
Return live per-peer connection status from wg show.
Response:
| Field | Type | Description |
|---|---|---|
data |
[object, ...] |
Live peer status (handshake time, bytes, endpoint) |
Client Configuration
Generate Client Config
POST /api/wireguard/generate-client
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") |
Response (data):
| Field | Type | Description |
|---|---|---|
config |
string |
Complete client config text ([Interface] + [Peer]) |
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.
Network API
Endpoints prefixed with /api/network/.... Manage systemd-networkd interface configuration including static addresses, routes, DNS, DHCP client settings, and link parameters.
Interface Management
List All Interfaces
GET /api/network/interfaces
Return all configured interfaces with their network config and runtime state from networkctl.
Response:
| Field | Type | Description |
|---|---|---|
data.interfaces |
object |
Map of interface name to {config, runtime} |
data.timestamp |
string |
Timestamp of runtime data collection |
Get Interface Details
GET /api/network/interfaces/<name>
Return config and runtime state for a specific interface.
Response (data):
| Field | Type | Description |
|---|---|---|
name |
string |
Interface name |
config |
object |
Full networkd config entry for this interface |
runtime |
object |
Runtime state from networkctl (addresses, gateway, DNS, state) |
Returns HTTP 400 if the interface name is invalid (contains path components, spaces, or characters outside ^[a-zA-Z0-9][a-zA-Z0-9._-]*$). Returns HTTP 404 if the interface is not found in config.
Save and Apply Interface
POST /api/network/interfaces/<name>
Save network config for an interface, render the .network file, copy it to /etc/systemd/network/, and reload networkd for that interface.
Request Body: Any networkd config keys (e.g., addresses, gateway, dns, routes, dhcp, link, dhcp_client).
Response (data):
| Field | Type | Description |
|---|---|---|
name |
string |
Interface name |
applied |
boolean |
Always true on success |
Returns HTTP 400 if the interface name is invalid.
Reload Interface
POST /api/network/interfaces/<name>/reload
Reload networkd for a single interface (runs networkctl reload <name>).
Response (data):
| Field | Type | Description |
|---|---|---|
name |
string |
Interface name |
reloaded |
boolean |
Always true on success |
Returns HTTP 400 if the interface name is invalid.
Full Sync
Apply All Interfaces
POST /api/network/apply
Full sync: generate all .network files, remove stale files, copy to /etc/systemd/network/, reload all interfaces, and sync DNS upstreams to dnsmasq.
Response (data):
| Field | Type | Description |
|---|---|---|
applied |
number |
Number of interfaces applied |
files |
[string, ...] |
Paths of generated files |
cleaned |
[string, ...] |
Paths of removed stale files |
Helpers
Infer DHCP Ranges
GET /api/network/infer-dhcp-ranges
Suggest candidate DHCP ranges based on static interface IPs. For each interface with a static IPv4 address, calculates a usable address range in the subnet.
Response:
| Field | Type | Description |
|---|---|---|
data.ranges |
object |
Map of interface name to {subnet, prefix, start, end} |
Infer Firewall Zones
GET /api/network/infer-zones
Suggest firewalld zone assignments for configured interfaces based on heuristics:
- Interface name contains
wg→wan - DHCP-enabled or public-facing IP →
wan - Has explicit routes →
management - Everything else →
lan
Response:
| Field | Type | Description |
|---|---|---|
data.zones |
object |
Map of interface name to suggested zone ("lan", "wan", "management") |
Sysctl
Set Kernel Parameter
POST /api/sysctl/set
Set a sysctl kernel parameter value via sysctl -w, then verify by reading it back.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Kernel parameter name (e.g., "net.ipv4.ip_forward") |
value |
string |
Yes | Value to set |
Response (data):
| Field | Type | Description |
|---|---|---|
name |
string |
Parameter name |
value |
string |
Value set |
Returns HTTP 500 if the value cannot be verified after write.
Logs API
Endpoints prefixed with /api/logs/.... These endpoints do not follow the standard JSON {"ok": true, "data": ...} response contract — they return HTML <div> elements directly. Errors are rendered inline as (error reading ...) text rather than returning JSON error responses.
System Journal
Get Journal Entries
GET /api/logs/journal
Return recent system journal entries as rendered HTML log lines.
Response: HTML fragment of <div class="log-line"> elements.
Nginx Logs
Nginx Access Log
GET /api/logs/nginx/access
Return recent nginx access log entries as rendered HTML.
Response: HTML fragment of <div class="log-line"> elements.
Nginx Error Log
GET /api/logs/nginx/error
Return recent nginx error log entries as rendered HTML.
Response: HTML fragment of <div class="log-line"> elements.
Dnsmasq Log
Dnsmasq Entries
GET /api/logs/dnsmasq
Return recent dnsmasq journal entries as rendered HTML.
Response: HTML fragment of <div class="log-line"> elements.
Application Log
App Log Entries
GET /api/logs/app
Return recent application log entries as rendered HTML.
Response: HTML fragment of <div class="log-line"> elements.
WebSocket Protocol
The daemon exposes a WebSocket at /ws (port 9091) for real-time state change notifications. On connect, the server sends:
{"type": "init", "versions": {"firewall": 0, "dnsmasq": 0, ...}}
Message Types
versions— Structural state change.updatedcontains subsystem names whose version counters changed. Triggers full re-fetch.tick— Volatile-only change (stats, counters, DHCP IPs).subsystemscontains affected subsystem names. Triggers lightweight per-subsystem re-fetch.notify— Single-topic notification.topicis the subsystem name.