docs: full refresh per DOCSPLAN (auth subsystem, backends model, access classes, sudo table, state-model mechanics) + 3 stale docstrings
This commit is contained in:
+348
-123
@@ -1,6 +1,6 @@
|
||||
# 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. Authentication is handled at the Flask layer via JWT — the `Authorization: Bearer <token>` header. Public endpoints (login, WebAuthn authenticate) do not require a token.
|
||||
All endpoints are served by the management WebUI Flask application bound to `127.0.0.1:9090`, proxied through nginx with SSL termination. Authentication is handled at the Flask layer via JWT — the `Authorization: Bearer <token>` header. Public endpoints — `POST /api/auth/login`, `POST /api/auth/refresh`, and the WebAuthn authenticate endpoints — do not require a token. Every other request must send both the `Authorization: Bearer <token>` header and the mandatory `X-Session-Id` header (a missing/invalid token **or** a missing `X-Session-Id` yields HTTP `401`).
|
||||
|
||||
Every request and response uses `Content-Type: application/json`.
|
||||
|
||||
@@ -17,7 +17,7 @@ Most endpoints require a valid JWT access token. The token is obtained by loggin
|
||||
|
||||
### Permission Checks
|
||||
|
||||
Each request is checked against per-subsystem permissions. `GET` requires `"read"` or `"rw"` on the subsystem. `POST`/`PATCH`/`DELETE` requires `"rw"`. User management endpoints (`/api/auth/users/*`) require `auth: "rw"`.
|
||||
Each request is checked against per-subsystem permissions. `GET` requires `"read"` or `"rw"` on the subsystem. `POST`/`PATCH`/`DELETE` requires `"rw"`. A request with no permission entry for its subsystem (or a method/level mismatch) is rejected with HTTP `403`. User management endpoints (`/api/auth/users/*`) and credential counts (`/api/auth/webauthn/credential-counts`) follow the same rule: `GET` needs only `auth: "read"`, while `POST`/`PATCH`/`DELETE` need `auth: "rw"`.
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -46,6 +46,8 @@ Error responses carry one of the following HTTP status codes:
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `400` | Bad request — invalid body, missing required field, or malformed value |
|
||||
| `401` | Unauthorized — missing/invalid `Bearer` token, missing `X-Session-Id` header, or invalid/expired/blacklisted token |
|
||||
| `403` | Forbidden — the caller lacks the required subsystem permission (`auth: "rw"` where needed, or no entry for the subsystem) |
|
||||
| `404` | Not found — the requested resource does not exist |
|
||||
| `409` | Conflict — the requested operation conflicts with an existing resource |
|
||||
| `500` | Internal server error — unexpected failure in the backend |
|
||||
@@ -123,7 +125,7 @@ Invalidate the current session by blacklisting the access token.
|
||||
|
||||
**Auth:** Access token required.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
**Response:** `data` is `{}` (an empty object) on success.
|
||||
|
||||
#### Refresh Tokens
|
||||
|
||||
@@ -131,9 +133,16 @@ Invalidate the current session by blacklisting the access token.
|
||||
POST /api/auth/refresh
|
||||
```
|
||||
|
||||
Rotate token pair. Validates the refresh token, blacklists the old pair, and issues new access and refresh tokens.
|
||||
Rotate token pair. Validates the refresh token, blacklists the old pair, and issues new access and refresh tokens. The request body must carry **both** `refresh_token` and `session_id` (session binding).
|
||||
|
||||
**Auth:** Refresh token required.
|
||||
**Auth:** Public — no JWT required (this is a public endpoint, so the `X-Session-Id` header is not sent).
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `refresh_token` | `string` | Yes | The refresh token to rotate |
|
||||
| `session_id` | `string` | Yes | Session ID from the token pair (session binding) |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -162,11 +171,11 @@ Change the current user's password.
|
||||
|---|---|---|---|
|
||||
| `username` | `string` | No | Auto-injected from JWT context |
|
||||
| `oldPassword` | `string` | Yes | Current password |
|
||||
| `newPassword` | `string` | Yes | New password |
|
||||
| `newPassword` | `string` | Yes | New password (minimum 8 characters) |
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
**Response:** `data` is `{"ok": true}` on success.
|
||||
|
||||
Returns HTTP `400` if old password is incorrect.
|
||||
Returns HTTP `400` for any failure — missing fields, incorrect old password, or a new password shorter than 8 characters.
|
||||
|
||||
---
|
||||
|
||||
@@ -178,15 +187,11 @@ Returns HTTP `400` if old password is incorrect.
|
||||
GET /api/auth/users
|
||||
```
|
||||
|
||||
List all users. Requires admin permission (`auth: "rw"`).
|
||||
List all users.
|
||||
|
||||
**Auth:** `auth: "rw"` required.
|
||||
**Auth:** `auth: "read"` required (read-only endpoint).
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `users` | `[object, ...]` | Array of user summaries (`id`, `username`, `permissions`) |
|
||||
**Response (`data`):** the array of user summaries directly (no `users` wrapper). Each entry has `id`, `username`, `permissions` (`{ subsystem: "read" | "rw" }`), and `created_at`.
|
||||
|
||||
#### Create User
|
||||
|
||||
@@ -203,7 +208,7 @@ Create a new user with password and per-subsystem permissions.
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `username` | `string` | Yes | Username |
|
||||
| `password` | `string` | Yes | Plain-text password |
|
||||
| `password` | `string` | Yes | Plain-text password (minimum 8 characters) |
|
||||
| `permissions` | `object` | No | Per-subsystem permissions (`{ subsystem: "read" \| "rw" }`) |
|
||||
|
||||
**Response (`data`):**
|
||||
@@ -214,7 +219,7 @@ Create a new user with password and per-subsystem permissions.
|
||||
| `username` | `string` | Username |
|
||||
| `permissions` | `object` | Per-subsystem permissions (`{ subsystem: "read" \| "rw" }`) |
|
||||
|
||||
Returns HTTP `409` if username already exists.
|
||||
Returns HTTP `409` if the username already exists. Returns HTTP `400` if the password is missing or shorter than 8 characters.
|
||||
|
||||
#### Update User
|
||||
|
||||
@@ -254,12 +259,37 @@ Delete a user and all associated permissions and WebAuthn credentials (CASCADE).
|
||||
|
||||
**Response:** `data` is `{"ok": true}` on success.
|
||||
|
||||
Returns HTTP `404` if user not found.
|
||||
Returns HTTP `403` if the user attempts to delete their own account. Returns HTTP `404` if the user is not found.
|
||||
|
||||
---
|
||||
|
||||
### WebAuthn
|
||||
|
||||
#### Check WebAuthn Capability
|
||||
|
||||
```
|
||||
GET /api/auth/webauthn/capable
|
||||
```
|
||||
|
||||
Check whether WebAuthn is available on the current request domain (the relying-party ID is derived from the request host).
|
||||
|
||||
**Auth:** Access token required.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
When enabled:
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `enabled` | `boolean` | Always `true` |
|
||||
| `rp_id` | `string` | Relying-party ID (request host) |
|
||||
| `rp_name` | `string` | Relying-party display name |
|
||||
| `origin` | `string` | Resolved WebAuthn origin (`scheme://host`) |
|
||||
|
||||
When unavailable: `{"enabled": false, "reason": "<reason>"}`.
|
||||
|
||||
---
|
||||
|
||||
#### Begin Registration
|
||||
|
||||
```
|
||||
@@ -274,9 +304,9 @@ Start WebAuthn credential registration. Returns options for `navigator.credentia
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `username` | `string` | Yes | Username to register for |
|
||||
| `username` | `string` | No | Auto-injected from the JWT (the authenticated user); any value in the body is overridden |
|
||||
|
||||
**Response (`data`):**
|
||||
**Response (`data`):** Standard WebAuthn registration options.
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
@@ -299,13 +329,19 @@ Complete WebAuthn credential registration. Verifies the attestation response and
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `username` | `string` | Yes | Username |
|
||||
| `response` | `object` | Yes | WebAuthn authenticator attestation response |
|
||||
| `username` | `string` | No | Auto-injected from the JWT; any value in the body is overridden |
|
||||
| `credential_response` | `object` | Yes | WebAuthn authenticator attestation response |
|
||||
| `registration_options` | `object` | Yes | The registration options returned by `register-begin` |
|
||||
| `name` | `string` | No | Display name for this credential |
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
**Response (`data`):**
|
||||
|
||||
Returns HTTP `400` if verification fails.
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `ok` | `boolean` | Always `true` |
|
||||
| `credential` | `object` | The stored credential (`id`, `name`, `transports`, `sign_count`) |
|
||||
|
||||
Returns HTTP `400` if verification fails or required fields are missing.
|
||||
|
||||
#### Begin Authentication
|
||||
|
||||
@@ -359,7 +395,7 @@ Complete WebAuthn authentication. Verifies the assertion and issues tokens on su
|
||||
| `user` | `object` | User info (`username`, `id`) |
|
||||
| `permissions` | `object` | Per-subsystem permissions |
|
||||
|
||||
Returns HTTP `400` if verification fails.
|
||||
Returns HTTP `401` if verification fails or required fields are missing.
|
||||
|
||||
#### List Credentials
|
||||
|
||||
@@ -373,7 +409,7 @@ List WebAuthn credentials for the current user.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
Array of credential objects (`id`, `name`, `transports`, `credentialId`, `signCount`, `createdAt`).
|
||||
Array of credential objects (`id`, `name`, `transports`, `sign_count`).
|
||||
|
||||
#### Credential Counts
|
||||
|
||||
@@ -381,15 +417,11 @@ Array of credential objects (`id`, `name`, `transports`, `credentialId`, `signCo
|
||||
GET /api/auth/webauthn/credential-counts
|
||||
```
|
||||
|
||||
Return credential counts for all users. Admin endpoint.
|
||||
Return credential counts for all users.
|
||||
|
||||
**Auth:** `auth: "rw"` required.
|
||||
**Auth:** `auth: "read"` required (read-only endpoint).
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `counts` | `object` | Dict mapping usernames to credential counts (`{"alice": 2, "bob": 1}`) |
|
||||
**Response (`data`):** The dict directly (no `counts` wrapper) — a mapping of usernames to credential counts (`{"alice": 2, "bob": 1}`).
|
||||
|
||||
#### Remove Credential
|
||||
|
||||
@@ -401,9 +433,9 @@ Remove a WebAuthn credential.
|
||||
|
||||
**Auth:** Access token required.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
**Response:** `data` is `{"ok": true}` on success.
|
||||
|
||||
Returns HTTP `404` if credential not found.
|
||||
Returns HTTP `404` if the credential is not found.
|
||||
|
||||
---
|
||||
|
||||
@@ -454,7 +486,7 @@ POST /api/firewall/config/apply
|
||||
|
||||
Apply the declarative config to live firewalld. Applies targets, services, interfaces, masquerade, rich rules, and forward ports.
|
||||
|
||||
**Request Body:** Optional. Send `{"force": true}` to override the management-lockout and interface-coverage guards.
|
||||
**Request Body:** None. The webui route accepts no body — the `{"force": true}` override of the management-lockout and interface-coverage guards is a **daemon-only** capability and cannot be sent through this webui endpoint. (To force an apply through the webui, use `POST /api/status/apply-all` with `{"force": true}`, which forwards `force` to the firewall apply.)
|
||||
|
||||
**Errors:** Returns HTTP `409` when the apply is refused by the management-lockout guard (https+ssh stripped from the default zone) or the interface-coverage invariant (a network-managed interface has no zone coverage and is not `unmanaged`). See `docs/config.md`.
|
||||
|
||||
@@ -472,9 +504,18 @@ Apply the declarative config to live firewalld. Applies targets, services, inter
|
||||
GET /api/firewall/config/pending
|
||||
```
|
||||
|
||||
Compare declarative config against live firewalld state. Returns diff for interfaces, services, targets, masquerade, rich rules, and forward ports.
|
||||
Compare declarative config against live firewalld state. Returns the diff for interfaces, services, targets, masquerade, rich rules, and forward ports.
|
||||
|
||||
**Response:** Same structure as POST /config response.
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `pending` | `[object, ...]` | List of pending changes |
|
||||
| `needs_apply` | `boolean` | Whether changes need to be applied |
|
||||
| `unmanaged_zones` | `object` | Zones active on the system but not present in the config |
|
||||
| `pending_summary` | `[string, ...]` | Human-readable summary string per pending change |
|
||||
|
||||
Unlike the `POST`/`PATCH /config` save response, this endpoint does **not** include `config_saved`; instead it adds `pending_summary`.
|
||||
|
||||
#### Partial Update Config
|
||||
|
||||
@@ -528,13 +569,18 @@ Return detailed configuration for a single zone.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `string` | Zone name |
|
||||
| `target` | `string` | Zone target (e.g., `"default"`, `"ACCEPT"`, `"REJECT"`) |
|
||||
| `interfaces` | `[string, ...]` | Interfaces assigned to this zone |
|
||||
| `sources` | `[string, ...]` | Source IPs addressed by this zone |
|
||||
| `services` | `[string, ...]` | Services allowed through the zone |
|
||||
| `ports` | `[string, ...]` | Explicit port rules (format: `"443/tcp"`) |
|
||||
| `protocols` | `[string, ...]` | Protocols to accept |
|
||||
| `icmp-blocks` | `[string, ...]` | ICMP types blocked |
|
||||
| `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 |
|
||||
| `ics` | `boolean` | Whether ICMP redirect (ICS) is enabled |
|
||||
| `forward-ports` | `[{port, proto, toaddr, toport}, ...]` | Port forward rules (key is hyphenated) |
|
||||
| `rich-rules` | `[string, ...]` | Rich rule strings (key is hyphenated) |
|
||||
|
||||
Returns HTTP `404` if the zone does not exist.
|
||||
|
||||
@@ -596,6 +642,8 @@ Replace all interfaces assigned to the zone with the provided list.
|
||||
| `zone` | `string` | Zone name |
|
||||
| `interfaces` | `[string, ...]` | List of interface names now assigned |
|
||||
|
||||
Returns HTTP `404` if the zone does not exist.
|
||||
|
||||
---
|
||||
|
||||
#### Set Zone Services
|
||||
@@ -611,6 +659,7 @@ Replace all services allowed in the zone with the provided list.
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `services` | `[string, ...]` | Yes | List of firewalld service names |
|
||||
| `force` | `boolean` | No | Override the management-lockout guard |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -619,6 +668,8 @@ Replace all services allowed in the zone with the provided list.
|
||||
| `zone` | `string` | Zone name |
|
||||
| `services` | `[string, ...]` | List of services now allowed |
|
||||
|
||||
Returns HTTP `404` if the zone does not exist. Returns HTTP `409` if the change would strip both https and ssh from the default zone (the management-lockout guard) and `force` is not set.
|
||||
|
||||
### Rich Rules
|
||||
|
||||
#### Add Rich Rule
|
||||
@@ -671,13 +722,13 @@ Returns HTTP `404` if the rule ID is not found.
|
||||
GET /api/firewall/rich-rules/<zone>
|
||||
```
|
||||
|
||||
Return all rich rules for the specified zone, each with an `id` and `rule` string.
|
||||
Return all rich rules for the specified zone. Each entry carries a `rule` string; rules that are tracked in the declarative config also carry an `id`.
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `[{id, rule}, ...]` | Rich rules with IDs |
|
||||
| `data` | `[{id?, rule}, ...]` | Rich rules; `id` is present only for rules that have a matching config entry (live-only rules are returned without `id`) |
|
||||
|
||||
### Port Forwarding
|
||||
|
||||
@@ -752,6 +803,8 @@ Toggle masquerade (source NAT) for a zone.
|
||||
| `zone` | `string` | Zone name |
|
||||
| `masquerade` | `boolean` | Whether masquerade is now enabled |
|
||||
|
||||
Returns HTTP `400` when attempting to enable masquerade on the `public` zone (it is not supported there — use `internal` or `vpn`).
|
||||
|
||||
### State
|
||||
|
||||
#### Get Firewall State
|
||||
@@ -866,7 +919,7 @@ Deep-merge the provided fields into the existing configuration. Useful for targe
|
||||
POST /api/dhcp/apply
|
||||
```
|
||||
|
||||
Write the in-memory configuration to `/etc/dnsmasq.d/vacuum-wall.conf` and reload the dnsmasq service.
|
||||
Write the in-memory configuration to `/etc/dnsmasq.d/vacuum-wall.conf` and **restart** the dnsmasq service (`systemctl restart dnsmasq`, not a reload).
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
@@ -878,22 +931,17 @@ Write the in-memory configuration to `/etc/dnsmasq.d/vacuum-wall.conf` and reloa
|
||||
GET /api/dhcp/status
|
||||
```
|
||||
|
||||
Return the current service status, config summary, and active lease count.
|
||||
Return the current service status and pending-change summary.
|
||||
|
||||
**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 |
|
||||
| `config_file_exists` | `boolean` | Whether the config file exists on disk |
|
||||
| `active_leases` | `number` | Number of active leases |
|
||||
| `leases` | `[object, ...]` | Active lease objects |
|
||||
| `pending_changes` | `boolean` | Whether the saved config differs from the last applied state |
|
||||
| `pending_diff` | `[object, ...]` | Per-field pending changes (diff of config vs applied baseline) |
|
||||
|
||||
### DHCP Ranges
|
||||
|
||||
@@ -930,12 +978,14 @@ Remove a DHCP range. Body contains identifying fields.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `interface` | `string` | Yes | Interface name |
|
||||
| `interface` | `string` | No | Interface name; defaults to `""` (all interfaces) |
|
||||
| `start` | `string` | Yes | Start of IP range |
|
||||
| `end` | `string` | Yes | End of IP range |
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
Returns HTTP `404` if no range matches the given interface/start/end.
|
||||
|
||||
### Static Leases
|
||||
|
||||
#### Add Static Lease
|
||||
@@ -1034,6 +1084,26 @@ Returns HTTP `404` if no matching record is found.
|
||||
|
||||
---
|
||||
|
||||
### DNS Search Domain
|
||||
|
||||
#### Set Search Domain
|
||||
|
||||
```
|
||||
POST /api/dhcp/domain
|
||||
```
|
||||
|
||||
Set or clear the DNS search domain. Pass `domain` to set it, or `null` to clear it.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `domain` | `string` | No | DNS search domain; `null` clears it |
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
---
|
||||
|
||||
## Proxy API
|
||||
|
||||
Endpoints prefixed with `/api/proxy/...`. Manage reverse proxy domains, nginx configuration generation, and the management WebUI proxy.
|
||||
@@ -1122,7 +1192,7 @@ Return all configured proxy domains. The response is flattened by path — each
|
||||
|-------|------|-------------|
|
||||
| `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.
|
||||
Each entry contains `domain` (string), `path` (string), `backend` (object with `host`, `port`, `proto`), `backend_name` (string — the name of the referenced backend), `cert` (string or `null`), `online` (boolean), `force_ssl` (boolean), and optionally `is_management` or `is_websocket` flags.
|
||||
|
||||
---
|
||||
|
||||
@@ -1132,27 +1202,17 @@ Each entry contains `domain` (string), `path` (string), `backend` (object with `
|
||||
POST /api/proxy/domains
|
||||
```
|
||||
|
||||
Add a new reverse proxy domain. Accepts two modes:
|
||||
Add a new reverse proxy domain that routes to a named backend. The "paths mode" / "legacy mode" split no longer exists — domains reference a backend by name and per-path routing lives on the backend itself.
|
||||
|
||||
**Paths mode (preferred):**
|
||||
**Request Body:**
|
||||
|
||||
| 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`. |
|
||||
| `backend` | `string` | Yes | Name of an existing backend (a key under `backends`) |
|
||||
| `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 |
|
||||
| `auth` | `object` | No | Basic auth as `{user, pass}`; when both are present a `.htpasswd` file is written as a side-effect and the raw password is **not** persisted (only `{user, htpasswd: <path>}` is stored) |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -1160,7 +1220,7 @@ Add a new reverse proxy domain. Accepts two modes:
|
||||
|-------|------|-------------|
|
||||
| `domain` | `string` | Domain name |
|
||||
|
||||
Returns HTTP `400` if the domain is already configured.
|
||||
Returns HTTP `400` if the domain is already configured, if `domain` or `backend` is missing, or if the referenced backend does not exist.
|
||||
|
||||
---
|
||||
|
||||
@@ -1170,9 +1230,14 @@ Returns HTTP `400` if the domain is already configured.
|
||||
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).
|
||||
Update one or more fields of an existing domain entry. Only fields present in the body are modified.
|
||||
|
||||
**Request Body:** Any subset of (`paths`, `backend`, `backend_host`, `backend_port`, `backend_proto`, `cert`, `extra_headers`, `force_ssl`, `auth`).
|
||||
**Request Body:** Any subset of (`backend`, `cert`, `force_ssl`, `auth`).
|
||||
|
||||
- `backend` — re-point the domain at a different existing backend name.
|
||||
- `cert` — set a new certificate type, or `null` to remove it.
|
||||
- `force_ssl` — toggle the HTTPS redirect flag.
|
||||
- `auth` — set basic auth (see Add Domain for the `.htpasswd` side-effect), or `null` to remove it.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -1180,7 +1245,7 @@ Update one or more fields of an existing domain entry. Only fields present in th
|
||||
|-------|------|-------------|
|
||||
| `domain` | `string` | Domain name |
|
||||
|
||||
Returns HTTP `404` if the domain is not configured.
|
||||
Returns HTTP `404` if the domain is not configured. Returns HTTP `400` if the body is empty or the new `backend` does not exist.
|
||||
|
||||
---
|
||||
|
||||
@@ -1200,6 +1265,86 @@ Remove a proxy domain and its nginx configuration.
|
||||
|
||||
Returns HTTP `404` if the domain is not configured.
|
||||
|
||||
### Backend Management
|
||||
|
||||
Backends define the per-path routing (`paths`) and any basic auth; proxy domains reference a backend by name.
|
||||
|
||||
#### List All Backends
|
||||
|
||||
```
|
||||
GET /api/proxy/backends
|
||||
```
|
||||
|
||||
Return all configured backends. Secret material is stripped — each backend carries a `has_auth` boolean instead of its `auth` object.
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `object` | Map of backend name to `{label, paths, has_auth, builtin?}` (auth stripped) |
|
||||
|
||||
---
|
||||
|
||||
#### Update Backend
|
||||
|
||||
```
|
||||
PATCH /api/proxy/backends
|
||||
```
|
||||
|
||||
Deep-merge a partial update into an existing backend entry. Built-in backends cannot be modified.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Backend name to update |
|
||||
| `label` | `string` | No | New display label |
|
||||
| `paths` | `object` | No | New path-to-backend map |
|
||||
| `auth` | `object` \| `false` \| `null` | No | Set basic auth, or `false`/`null` to remove it |
|
||||
|
||||
**Response (`data`):** `{"backend": "<name>"}`.
|
||||
|
||||
Returns HTTP `400` if `name` is missing or the backend is built-in. Returns HTTP `500` if the backend name does not exist.
|
||||
|
||||
---
|
||||
|
||||
#### Add Backend
|
||||
|
||||
```
|
||||
POST /api/proxy/backends
|
||||
```
|
||||
|
||||
Add a new backend.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Backend name (must be unique) |
|
||||
| `label` | `string` | Yes | Display label |
|
||||
| `paths` | `object` | Yes | Path-to-backend map; each entry must carry `host`, `port`, `proto` |
|
||||
| `auth` | `object` | No | Basic auth configuration |
|
||||
|
||||
**Response (`data`):** `{"backend": "<name>"}`.
|
||||
|
||||
Returns HTTP `400` if `name`, `label`, or `paths` is missing, if the backend already exists, or if the `paths` schema is invalid.
|
||||
|
||||
---
|
||||
|
||||
#### Remove Backend
|
||||
|
||||
```
|
||||
DELETE /api/proxy/backends/<name>
|
||||
```
|
||||
|
||||
Remove a non-builtin backend.
|
||||
|
||||
**Response (`data`):** `{"backend": "<name>"}`.
|
||||
|
||||
Returns HTTP `409` if one or more domains reference the backend. Returns HTTP `400` if the backend is built-in.
|
||||
|
||||
---
|
||||
|
||||
### Apply / Test
|
||||
|
||||
#### Apply Configuration
|
||||
@@ -1259,7 +1404,7 @@ Return all managed certificates with metadata.
|
||||
|-------|------|-------------|
|
||||
| `data` | `[object, ...]` | Array of certificate objects |
|
||||
|
||||
Each certificate object contains `domain`, `expires_at`, `days_until_expiry`, `cert_path`, `key_path`.
|
||||
Each certificate object contains `domain`, `issuer` (the CA/issuer name), `san_domains` (array of subject-alternative names), `expires_at`, `days_until_expiry`, `cert_path`, `key_path`, `ca_path`, and `auto_renew`.
|
||||
|
||||
---
|
||||
|
||||
@@ -1269,11 +1414,13 @@ Each certificate object contains `domain`, `expires_at`, `days_until_expiry`, `c
|
||||
GET /api/certs/<domain>
|
||||
```
|
||||
|
||||
Return details for a single certificate.
|
||||
Return details for a single certificate. Matches on the main domain **or** any of the certificate's `san_domains`.
|
||||
|
||||
**Response (`data`):** Fields: `domain`, `expires_at`, `days_until_expiry`, `cert_path`, `key_path`.
|
||||
**Response (`data`):** The full certificate object (`domain`, `issuer`, `san_domains`, `expires_at`, `days_until_expiry`, `cert_path`, `key_path`, `ca_path`, `auto_renew`). When an issuance is currently running for the domain, an additional `issuance` field (the issuance status object) is embedded.
|
||||
|
||||
Returns HTTP `404` if no certificate is found for the domain.
|
||||
If no certificate exists yet but an issuance is in progress, the response is `{"domain": <domain>, "status": "issuing", "issuance": {...}}`.
|
||||
|
||||
Returns HTTP `404` if no certificate is found and no issuance is in progress.
|
||||
|
||||
### Validation
|
||||
|
||||
@@ -1318,6 +1465,8 @@ Create a new certificate issuance request. Issuance runs asynchronously in the b
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `request_id` | `string` | Unique identifier for polling issuance status |
|
||||
| `domain` | `string` | Domain being issued |
|
||||
| `status` | `string` | Only present when an issuance for this domain is already running — `"existing"` (the existing `request_id` is returned) |
|
||||
|
||||
Returns HTTP `400` if the domain is missing. Returns HTTP `409` if a valid certificate already exists for the domain (renew instead). An ACME account must be registered before issuance (verified by the `account_registered` blocking check in the validation pipeline).
|
||||
|
||||
@@ -1347,7 +1496,11 @@ Start an async certificate renewal for an existing certificate. The renewal
|
||||
runs in the background and is polled via
|
||||
`GET /api/certs/renew/<request_id>`.
|
||||
|
||||
**Request Body:** none (domain is taken from the path).
|
||||
**Request Body:** Optional. The domain is taken from the path.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `force` | `boolean` | No | Force renewal even if the certificate's renewal window has not been reached (default `false`) |
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -1392,11 +1545,11 @@ is skipped, or fails.
|
||||
DELETE /api/certs/<domain>
|
||||
```
|
||||
|
||||
Delete a certificate and remove it from auto-renewal tracking.
|
||||
Delete a certificate and remove it from auto-renewal tracking. There is **no** existence check — the certificate may or may not exist.
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
Returns HTTP `404` if the certificate is not found.
|
||||
Returns HTTP `400` if the domain is missing. Failures (e.g. `acme.sh --remove` failing) surface as HTTP `500`; the endpoint never returns `404`.
|
||||
|
||||
### Account
|
||||
|
||||
@@ -1453,7 +1606,7 @@ Returns HTTP `400` if the email is missing or invalid. Returns HTTP `500` if reg
|
||||
DELETE /api/certs/account
|
||||
```
|
||||
|
||||
Deactivate the ACME account via `acme.sh --deactivate-account`. Clears the `email` and `ca` fields from `config/acme/config.json`.
|
||||
Deactivate the ACME account via `acme.sh --deactivate-account`. Clears the `email` and `ca` fields from `config/acme/config.json`. A failure in the `acme.sh` call is caught and logged but **does not** fail the endpoint — the config cleanup always runs.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -1461,8 +1614,6 @@ Deactivate the ACME account via `acme.sh --deactivate-account`. Clears the `emai
|
||||
|-------|------|-------------|
|
||||
| `email` | `string` | Empty string indicating the account was deactivated |
|
||||
|
||||
Returns HTTP `500` if deactivation fails.
|
||||
|
||||
---
|
||||
|
||||
#### Set ACME Contact Email
|
||||
@@ -1481,13 +1632,11 @@ Set or update the ACME account contact email.
|
||||
|
||||
**Response (`data`):** Returns the set `email` field.
|
||||
|
||||
#### Generate Self-Signed Certificate
|
||||
#### Generate Self-Signed Certificate (daemon-only)
|
||||
|
||||
```
|
||||
POST /api/certs/self-signed
|
||||
```
|
||||
There is **no** `POST /api/certs/self-signed` webui route. Self-signed generation is a daemon-only endpoint, `POST /acme/self-signed` (reached directly over the daemon socket, not via the WebUI).
|
||||
|
||||
Generate a self-signed certificate for a domain. Idempotent — skips if `fullchain.cer` and `<domain>.key` already exist at `data/acme/<domain>/`.
|
||||
It generates a self-signed certificate for a domain and is idempotent — it skips generation if `<domain>.crt` and `<domain>.key` already exist at `data/certs/`.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
@@ -1501,9 +1650,9 @@ Generate a self-signed certificate for a domain. Idempotent — skips if `fullch
|
||||
| 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 |
|
||||
| `cert` | `string` | Path to `data/certs/<domain>.crt` |
|
||||
| `key` | `string` | Path to `data/certs/<domain>.key` |
|
||||
| `generated` | `boolean` | `true` if a new cert was created, `false` if the existing cert was reused |
|
||||
|
||||
## WireGuard API
|
||||
|
||||
@@ -1593,14 +1742,9 @@ Alias for `/api/wireguard/apply` — write config and bring the tunnel up.
|
||||
POST /api/wireguard/down
|
||||
```
|
||||
|
||||
Bring down the WireGuard tunnel interface (`wg0`).
|
||||
Bring down the WireGuard tunnel interface(s) (all class interfaces plus the legacy `wg0`).
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `down` | `boolean` | Always `true` on success |
|
||||
| `synced` | `[string, ...]` | Subsystems auto-synced as a result |
|
||||
**Response:** `data` is `null` on success (the webui route discards the daemon payload). The daemon itself returns `{"down": true}` — it does not include a `synced` field.
|
||||
|
||||
### Status
|
||||
|
||||
@@ -1619,6 +1763,7 @@ Return live tunnel state with interface metrics and per-peer connection statisti
|
||||
| `up` | `boolean` | Whether the tunnel interface is up |
|
||||
| `interface` | `object` | Interface info (listen port, public key) |
|
||||
| `peers` | `[object, ...]` | Per-peer stats (handshake, bytes, endpoint) |
|
||||
| `classes` | `object` | Per-class runtime status keyed by class key (`{up, interface, peers}`) |
|
||||
|
||||
---
|
||||
|
||||
@@ -1656,7 +1801,7 @@ Return all configured peers. Private keys are stripped.
|
||||
POST /api/wireguard/peers
|
||||
```
|
||||
|
||||
Add a new WireGuard peer. A key pair is auto-generated. Private key stripped from response.
|
||||
Add a new WireGuard peer, or **upsert** an existing one — if the `name` is already configured, the provided fields update that peer in place (a key pair is only generated for genuinely new peers). Private key stripped from response.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
@@ -1664,11 +1809,13 @@ Add a new WireGuard peer. A key pair is auto-generated. Private key stripped fro
|
||||
|-------|------|----------|-------------|
|
||||
| `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"]` |
|
||||
| `allowed_ips` | `[string, ...]` | No | Allowed IPs; defaults to `[]` |
|
||||
| `persistent_keepalive` | `number` | No | Persistent keepalive interval (seconds) |
|
||||
| `preshared_key` | `string` | No | Preshared key |
|
||||
| `description` | `string` | No | Peer description |
|
||||
| `access_class` | `string` | No | Access class key this peer belongs to |
|
||||
|
||||
**Response (`data`):** Peer object with `name`, `public_key`, `allowed_ips`, etc. (no `private_key`).
|
||||
**Response (`data`):** The peer object with `public_key`, `endpoint`, `allowed_ips`, `persistent_keepalive`, `preshared_key`, `description`, `access_class` (no `private_key`).
|
||||
|
||||
---
|
||||
|
||||
@@ -1741,11 +1888,11 @@ Manage VPN access classes that categorize peers by access level (e.g., full LAN
|
||||
GET /api/wireguard/classes
|
||||
```
|
||||
|
||||
Return all configured access classes.
|
||||
Return all configured access classes. Private keys are stripped.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
Object keyed by class identifier, each with `name` and `description` fields.
|
||||
Object keyed by class identifier, each entry carrying `name`, `description`, `subnet`, `listen_port`, `lan_access`, and `public_key` (private key omitted).
|
||||
|
||||
#### Create Access Class
|
||||
|
||||
@@ -1759,19 +1906,16 @@ Create a new access class.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `key` | `string` | Yes | Class identifier (alphanumeric) |
|
||||
| `key` | `string` | Yes | Class identifier (lowercase alphanumeric) |
|
||||
| `name` | `string` | No | Display name (defaults to key) |
|
||||
| `description` | `string` | No | Description text |
|
||||
| `subnet` | `string` | No | Class subnet (CIDR) |
|
||||
| `listen_port` | `number` | No | Listen port for the class interface |
|
||||
| `lan_access` | `boolean` | No | Whether peers get LAN access (default `false`) |
|
||||
|
||||
**Response (`data`):**
|
||||
**Response (`data`):** The created class object (`name`, `description`, `subnet`, `listen_port`, `lan_access`, `public_key`) — note there is **no** `key` field in the response; the class is keyed by the request `key`.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `key` | `string` | Class key |
|
||||
| `name` | `string` | Display name |
|
||||
| `description` | `string` | Description |
|
||||
|
||||
Returns HTTP `409` if the key already exists.
|
||||
Returns HTTP `400` if the `key` is missing or is not lowercase alphanumeric. Returns HTTP `409` if the key already exists.
|
||||
|
||||
#### Update Access Class
|
||||
|
||||
@@ -1779,7 +1923,7 @@ Returns HTTP `409` if the key already exists.
|
||||
PATCH /api/wireguard/classes
|
||||
```
|
||||
|
||||
Update an existing access class.
|
||||
Update an existing access class. Only the fields present in the body are changed.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
@@ -1788,8 +1932,11 @@ Update an existing access class.
|
||||
| `key` | `string` | Yes | Class identifier |
|
||||
| `name` | `string` | No | New display name |
|
||||
| `description` | `string` | No | New description |
|
||||
| `subnet` | `string` | No | New subnet (CIDR) |
|
||||
| `listen_port` | `number` | No | New listen port |
|
||||
| `lan_access` | `boolean` | No | New LAN access flag |
|
||||
|
||||
**Response (`data`):** Updated class object with `key`, `name`, `description`.
|
||||
**Response (`data`):** The updated class object — `key` plus `name`, `description`, `subnet`, `listen_port`, `lan_access`, and `public_key`.
|
||||
|
||||
Returns HTTP `404` if the class is not found.
|
||||
|
||||
@@ -1813,6 +1960,62 @@ Returns HTTP `404` if the class is not found. Returns HTTP `409` if peers refere
|
||||
|
||||
---
|
||||
|
||||
#### Bring Class Tunnel Up
|
||||
|
||||
```
|
||||
POST /api/wireguard/classes/<key>/up
|
||||
```
|
||||
|
||||
Bring up a single class's tunnel interface (renders the class config and runs `wg-quick up`).
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
Returns HTTP `404` if the class does not exist. Returns HTTP `400` if the class has no assigned peers.
|
||||
|
||||
---
|
||||
|
||||
#### Bring Class Tunnel Down
|
||||
|
||||
```
|
||||
POST /api/wireguard/classes/<key>/down
|
||||
```
|
||||
|
||||
Bring down a single class's tunnel interface. Note: the webui exposes this as `POST`, while the underlying daemon endpoint is a `DELETE` (`/wireguard/classes/<key>/down`).
|
||||
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
Returns HTTP `404` if the class does not exist.
|
||||
|
||||
---
|
||||
|
||||
#### Get Class Status
|
||||
|
||||
```
|
||||
GET /api/wireguard/classes/<key>/status
|
||||
```
|
||||
|
||||
Return live status for a single class's tunnel interface.
|
||||
|
||||
**Response (`data`):** The class status object (`up`, `interface`, `peers`).
|
||||
|
||||
Returns HTTP `404` if the class does not exist.
|
||||
|
||||
---
|
||||
|
||||
#### Generate Class Keys
|
||||
|
||||
```
|
||||
POST /api/wireguard/classes/keys/<key>
|
||||
```
|
||||
|
||||
Generate a key pair for a class (idempotent — reports `generated: false` if keys already exist).
|
||||
|
||||
**Response:** `data` is `null` on success via the webui (the webui route discards the daemon payload). The daemon itself returns `{generated, class_key, public_key}` (or `{generated: false, class_key, reason}` when keys already exist).
|
||||
|
||||
Returns HTTP `404` if the class does not exist.
|
||||
|
||||
---
|
||||
|
||||
## Network API
|
||||
|
||||
Endpoints prefixed with `/api/network/...`. Manage systemd-networkd interface configuration including static addresses, routes, DNS, DHCP client settings, and link parameters.
|
||||
@@ -1871,8 +2074,9 @@ Save network config for an interface, render the `.network` file, copy it to `/e
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | `string` | Interface name |
|
||||
| `applied` | `boolean` | `true` if deploy to systemd-networkd succeeded, `false` if the system call was unavailable |
|
||||
| `synced` | `[string, ...]` | Subsystems that were automatically updated by the sync event bus |
|
||||
| `applied` | `boolean` | Always `true` |
|
||||
|
||||
The webui response is a fixed `{ "name": ..., "applied": true }` — it never reports `false` and carries no `synced` field (the daemon returns `applied`/`synced` internally, but the webui transform flattens it to this).
|
||||
|
||||
Returns HTTP `400` if the interface name is invalid.
|
||||
|
||||
@@ -1884,7 +2088,7 @@ Returns HTTP `400` if the interface name is invalid.
|
||||
POST /api/network/interfaces/<name>/reload
|
||||
```
|
||||
|
||||
Reload networkd for a single interface (runs `networkctl reload <name>`).
|
||||
Reload networkd for a single interface (runs `networkctl reconfigure <name>`, not `networkctl reload`).
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
@@ -1950,7 +2154,7 @@ Suggest firewalld zone assignments for configured interfaces based on heuristics
|
||||
|-------|------|-------------|
|
||||
| `data.zones` | `object` | Map of interface name to suggested zone (`"lan"`, `"wan"`, `"management"`) |
|
||||
|
||||
Returns HTTP `500` if the value cannot be verified after write.
|
||||
This is a read-only suggestion endpoint; it does not write anything and returns no write-verification errors.
|
||||
|
||||
---
|
||||
|
||||
@@ -2082,21 +2286,42 @@ Re-collect state from the daemon, optionally filtered by subsystem. Proxies the
|
||||
|
||||
Returns HTTP `500` if the daemon is unreachable.
|
||||
|
||||
### Sysctl
|
||||
### System Metrics
|
||||
|
||||
#### Set Kernel Parameter
|
||||
#### Get System Metrics
|
||||
|
||||
```
|
||||
POST /api/network/sysctl/set
|
||||
GET /api/status/system-metrics
|
||||
```
|
||||
|
||||
Set a sysctl kernel parameter value via `sysctl -w`, then verify by reading it back.
|
||||
Return system-wide CPU load, memory, swap, and per-interface network traffic metrics, read from the daemon's pre-collected `system` state.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `load` | `object` | Load averages (`load1`, `load5`, `load15`) |
|
||||
| `memory` | `object` | Memory usage (`total`, `available`, `used`, `used_pct`) |
|
||||
| `swap` | `object` | Swap usage (`total`, `used`, `used_pct`) |
|
||||
| `traffic` | `object` | Per-interface network traffic stats (interface name → counters) |
|
||||
|
||||
---
|
||||
|
||||
### Sysctl (daemon-only)
|
||||
|
||||
There is **no** `POST /api/network/sysctl/set` webui route. Setting a sysctl kernel parameter is a daemon-only endpoint, `POST /network/sysctl/set` (reached directly over the daemon socket, not via the WebUI).
|
||||
|
||||
It sets the value via `sysctl -w` and verifies by reading it back. Only a fixed allowlist of nine keys is permitted:
|
||||
|
||||
| `net.ipv4.ip_forward` | `net.ipv4.conf.all.forwarding` | `net.ipv4.conf.all.accept_redirects` |
|
||||
| `net.ipv4.conf.default.accept_redirects` | `net.ipv4.conf.all.send_redirects` | `net.ipv4.conf.default.send_redirects` |
|
||||
| `net.ipv4.conf.all.rp_filter` | `net.ipv4.icmp_echo_ignore_all` | `net.ipv4.tcp_syncookies` |
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Kernel parameter name (e.g., `"net.ipv4.ip_forward"`) |
|
||||
| `name` | `string` | Yes | Kernel parameter name (must be one of the nine allowed keys) |
|
||||
| `value` | `string` | Yes | Value to set |
|
||||
|
||||
**Response (`data`):**
|
||||
@@ -2106,7 +2331,7 @@ Set a sysctl kernel parameter value via `sysctl -w`, then verify by reading it b
|
||||
| `name` | `string` | Parameter name |
|
||||
| `value` | `string` | Value set |
|
||||
|
||||
Returns HTTP `500` if the value cannot be verified after write.
|
||||
Returns HTTP `400` if `name`/`value` is missing, `name` is malformed, or `name` is not in the allowlist. Returns HTTP `500` if the value cannot be verified after write.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user