Refactor nginx to path-based domain model with config migration
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
This commit is contained in:
+86
-36
@@ -76,35 +76,64 @@ Additional dnsmasq directives can be appended verbatim by placing plain-text fil
|
||||
|
||||
**File**: `config/nginx/config.json`
|
||||
|
||||
This file defines reverse proxy domains, the management interface, and global SSL settings. The application renders it into per-domain server block files in `data/nginx/sites-enabled/` and into the shared SSL snippet at `/etc/nginx/snippets/vacuum-wall-ssl.conf`.
|
||||
This file defines reverse proxy domains with path-based routing, and global SSL settings. The application renders it into per-domain server block files in `data/nginx/sites-enabled/` and into the shared SSL snippet at `/etc/nginx/snippets/vacuum-wall-ssl.conf`.
|
||||
|
||||
```json
|
||||
{
|
||||
"domains": {
|
||||
"app.example.com": {
|
||||
"backend": {
|
||||
"host": "192.168.2.50",
|
||||
"port": 8080,
|
||||
"proto": "http"
|
||||
},
|
||||
"force_ssl": true,
|
||||
"cert": "acme",
|
||||
"headers": {
|
||||
"X-Forwarded-Proto": "https",
|
||||
"X-Real-IP": "$remote_addr"
|
||||
"auth": {
|
||||
"user": "admin",
|
||||
"htpasswd": "/home/wall/vacuum-wall/data/nginx/.htpasswd"
|
||||
},
|
||||
"paths": {
|
||||
"/": {
|
||||
"backend": {
|
||||
"host": "192.168.2.50",
|
||||
"port": 8080,
|
||||
"proto": "http"
|
||||
},
|
||||
"headers": {
|
||||
"X-Forwarded-Proto": "https"
|
||||
}
|
||||
},
|
||||
"/api": {
|
||||
"backend": {
|
||||
"host": "192.168.2.51",
|
||||
"port": 3000,
|
||||
"proto": "http"
|
||||
},
|
||||
"auth": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"management": {
|
||||
"domain": "vacuum-wall.local",
|
||||
"backend": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 9090,
|
||||
"proto": "http"
|
||||
},
|
||||
"auth": {
|
||||
"user": "admin",
|
||||
"htpasswd": "/home/wall/vacuum-wall/data/nginx/.htpasswd"
|
||||
"mgmt.example.com": {
|
||||
"force_ssl": true,
|
||||
"cert": "acme",
|
||||
"paths": {
|
||||
"/": {
|
||||
"backend": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 9090,
|
||||
"proto": "http"
|
||||
},
|
||||
"is_management": true,
|
||||
"auth": {
|
||||
"user": "admin",
|
||||
"htpasswd": "/home/wall/vacuum-wall/data/nginx/.htpasswd"
|
||||
}
|
||||
},
|
||||
"/ws": {
|
||||
"backend": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 9091,
|
||||
"proto": "http"
|
||||
},
|
||||
"is_websocket": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ssl": {
|
||||
@@ -117,17 +146,40 @@ This file defines reverse proxy domains, the management interface, and global SS
|
||||
|
||||
### Domain Entries
|
||||
|
||||
The `domains` object maps domain names (keys) to proxy configurations. Each entry produces a separate nginx `server` block.
|
||||
The `domains` object maps domain names (keys) to proxy configurations. Each entry produces a separate nginx `server` block. All routing is path-based — a domain can proxy multiple paths to different backends.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `backend` | object | Yes | The upstream service that receives proxied traffic. |
|
||||
| `paths` | object | Yes | Path-to-config map. Each key is a URL path (e.g., `"/"`, `"/api"`). No catch-all unless `"/"` is explicitly defined. |
|
||||
| `force_ssl` | boolean | No | Enable HTTPS redirect. HTTP requests to this domain receive a 301 redirect to HTTPS. Default: `true`. |
|
||||
| `cert` | string | No | Certificate provisioning method. One of: `"acme"`, `"file"`, or `"selfsigned"`. Omit for domains that don't need a dedicated cert. |
|
||||
| `auth` | object | No | Domain-level HTTP basic auth configuration (`{ user, htppasswd }`). Applies to all paths unless overridden at the path level. |
|
||||
|
||||
### Path Entries
|
||||
|
||||
Each entry under `paths` defines a location block and its proxy backend.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `backend` | object | Yes | The upstream service for this path. |
|
||||
| `backend.host` | string | Yes | IP address or hostname of the backend service. |
|
||||
| `backend.port` | integer | Yes | Port the backend service is listening on. |
|
||||
| `backend.proto` | string | No | Protocol for the backend connection: `http` or `https`. Default: `http`. |
|
||||
| `force_ssl` | boolean | No | Enable HTTPS redirect. HTTP requests to this domain receive a 301 redirect to HTTPS. Default: `true`. |
|
||||
| `headers` | object | No | Custom headers to set on proxied requests. Supports nginx variable interpolation (e.g., `$remote_addr`). |
|
||||
| `cert` | string | No | Certificate provisioning method. One of: `"acme"`, `"file"`, or `"selfsigned"`. Omit for domains that don't need a dedicated cert. |
|
||||
| `backend.proto` | string | No | Protocol: `http` or `https`. Default: `http`. |
|
||||
| `headers` | object | No | Custom proxy headers (key-value pairs). Supports nginx variable interpolation. |
|
||||
| `auth` | object \| null | No | Path-level auth override. `{ user, htppasswd }` replaces domain-level auth. `null` disables auth for this path. |
|
||||
| `is_management` | boolean | No | Marks this path as the Vacuum Wall WebUI backend. Suppresses security headers (X-Frame-Options, etc.) so the SPA works correctly. |
|
||||
| `is_websocket` | boolean | No | Marks this path as a WebSocket pass-through. Disables auth, sets Upgrade/Connection headers, uses extended timeouts. |
|
||||
|
||||
### Auth Inheritance Rules
|
||||
|
||||
- Domain-level `auth` applies to all paths unless overridden.
|
||||
- Path-level `auth: null` means "no auth" for that path.
|
||||
- Path-level `auth: { ... }` overrides domain-level for that path.
|
||||
- No other domain-level settings inherit — `headers` is path-only.
|
||||
|
||||
### Path ordering
|
||||
|
||||
Nginx evaluates `location` blocks by specificity: more specific prefixes (e.g., `/api`) always match before `/` by nginx's own priority rules. The order of keys in the `paths` dict does not affect routing behavior.
|
||||
|
||||
### Certificate Types
|
||||
|
||||
@@ -141,22 +193,20 @@ The `cert` field is a string that selects the provisioning method:
|
||||
|
||||
### Management Domain
|
||||
|
||||
The `management` block configures the Vacuum Wall admin interface itself. It follows the same structure as a domain entry but can include an `auth` block for HTTP Basic Authentication.
|
||||
The Vacuum Wall admin interface is configured as a regular domain entry under `domains`, with `is_management: true` on the path pointing to the Flask app. A second path (`/ws`) with `is_websocket: true` provides WebSocket pass-through for real-time state updates. This replaces the legacy `management` top-level key.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| `domain` | string | Yes | The hostname used to access the management WebUI (e.g., `<hostname>.local`). |
|
||||
| `backend` | object | Yes | Points to the Flask app at `127.0.0.1:9090`. |
|
||||
| `auth` | object | No | HTTP Basic Authentication configuration. Only created if `auth_user` is provided when setting the management proxy. |
|
||||
| `auth.user` | string | Yes | Username for the `.htpasswd` file. |
|
||||
| `auth.htpasswd` | string | Yes | Full path to the `.htpasswd` file containing the username and hashed password. |
|
||||
|
||||
The application can create the `.htpasswd` file programmatically via `write_htpasswd()` (using passlib's `apache_passwd` with Apache-Round-12, falling back to SHA-256 crypt). Manual creation is also possible:
|
||||
The application can create the `.htpasswd` file programmatically via `write_htpasswd()` (using passlib's SHA-256 crypt). Manual creation is also possible:
|
||||
|
||||
```bash
|
||||
htpasswd -bc data/nginx/.htpasswd admin yourpassword
|
||||
```
|
||||
|
||||
### Backward Compatibility
|
||||
|
||||
Config files using the legacy format are auto-migrated on first load:
|
||||
- Domain entries with a top-level `backend` key are wrapped into `paths["/"]`.
|
||||
- A legacy `management` top-level key is migrated into `domains[management.domain]` with `is_management` on the root path and a `/ws` WebSocket path.
|
||||
|
||||
### Global SSL Settings
|
||||
|
||||
The `ssl` block defines TLS parameters applied to all HTTPS server blocks via the shared snippet.
|
||||
|
||||
Reference in New Issue
Block a user