docs: update documentation and project structure
- Update AGENTS.md, README.md, and docs/* with revisions - Refactor lib/acme.py and lib/state.py - Add tests for acme module - Remove install.sh and restart-services.sh (moved to scripts/) - Normalize vendor files (acme.sh, htm.js)
This commit is contained in:
+140
-42
@@ -376,6 +376,22 @@ Toggle masquerade (source NAT) for a zone.
|
||||
| `zone` | `string` | Zone name |
|
||||
| `masquerade` | `boolean` | Whether masquerade is now enabled |
|
||||
|
||||
### State
|
||||
|
||||
#### Get Firewall State
|
||||
|
||||
```
|
||||
GET /api/firewall/state
|
||||
```
|
||||
|
||||
Return current firewall state from the daemon state store. Provides live firewall state data including active zones, services, and interfaces as polled by the daemon.
|
||||
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `object` | Firewall state data from the state collector |
|
||||
|
||||
### Info
|
||||
|
||||
#### Available Services
|
||||
@@ -476,12 +492,7 @@ POST /api/dhcp/apply
|
||||
|
||||
Write the in-memory configuration to `/etc/dnsmasq.d/vacuum-wall.conf` and reload the dnsmasq service.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `applied` | `boolean` | Always `true` on success |
|
||||
| `synced` | `[string, ...]` | Subsystems that were automatically updated by the sync event bus |
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
### Status
|
||||
|
||||
@@ -777,20 +788,6 @@ 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
|
||||
|
||||
```
|
||||
@@ -902,15 +899,35 @@ Return details for a single certificate.
|
||||
|
||||
Returns HTTP `404` if no certificate is found for the domain.
|
||||
|
||||
### Validation
|
||||
|
||||
#### Validate Certificate Issuance
|
||||
|
||||
```
|
||||
POST /api/certs/validate
|
||||
```
|
||||
|
||||
Run pre-flight checks before certificate issuance. Verifies domain format, ACME account registration, DNS resolution, and port 80 accessibility.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `domain` | `string` | Yes | Domain to validate |
|
||||
|
||||
**Response (`data`):** Validation results object with per-check status.
|
||||
|
||||
Returns HTTP `400` if the domain is missing.
|
||||
|
||||
### Operations
|
||||
|
||||
#### Issue Certificate
|
||||
#### Start Certificate Issuance
|
||||
|
||||
```
|
||||
POST /api/certs/issue
|
||||
POST /api/certs/issue/start
|
||||
```
|
||||
|
||||
Request a new certificate for a domain. Returns HTTP `500` if issuance fails.
|
||||
Create a new certificate issuance request. Issuance runs asynchronously in the background.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
@@ -920,12 +937,30 @@ Request a new certificate for a domain. Returns HTTP `500` if issuance fails.
|
||||
| `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.
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `request_id` | `string` | Unique identifier for polling issuance status |
|
||||
|
||||
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).
|
||||
|
||||
---
|
||||
|
||||
#### Poll Certificate Issuance Status
|
||||
|
||||
```
|
||||
GET /api/certs/issue/<request_id>
|
||||
```
|
||||
|
||||
Poll the status of a certificate issuance request started by `POST /api/certs/issue/start`.
|
||||
|
||||
**Response (`data`):** Issuance status object containing progress, logs, and result.
|
||||
|
||||
Returns HTTP `404` if the request ID is not found. The frontend uses `poll()` to repeatedly fetch this endpoint until issuance completes or fails.
|
||||
|
||||
---
|
||||
|
||||
#### Renew Certificate
|
||||
|
||||
```
|
||||
@@ -1125,12 +1160,7 @@ POST /api/wireguard/apply
|
||||
|
||||
Write the current configuration to `wg0.conf` and bring the tunnel up.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `applied` | `boolean` | Always `true` on success |
|
||||
| `synced` | `[string, ...]` | Subsystems that were automatically updated by the sync event bus |
|
||||
**Response:** `data` is `null` on success.
|
||||
|
||||
---
|
||||
|
||||
@@ -1429,12 +1459,54 @@ 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.
|
||||
|
||||
---
|
||||
|
||||
## Status API
|
||||
|
||||
Endpoints prefixed with `/api/status/...`. Aggregate status across all subsystems.
|
||||
|
||||
### Pending Changes
|
||||
|
||||
#### Check All Pending Changes
|
||||
|
||||
```
|
||||
GET /api/status/pending
|
||||
```
|
||||
|
||||
Aggregate pending changes across all subsystems. Useful for the dashboard to show which subsystems need configuration applied.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `subsystems` | `object` | Map of subsystem name to pending status |
|
||||
| `total_changes` | `number` | Total count of pending changes across all subsystems |
|
||||
|
||||
---
|
||||
|
||||
#### Apply All Pending Changes
|
||||
|
||||
```
|
||||
POST /api/status/apply-all
|
||||
```
|
||||
|
||||
Apply pending changes for all subsystems in dependency order.
|
||||
|
||||
**Response (`data`):**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `applied` | `[string, ...]` | List of subsystems that were applied |
|
||||
| `errors` | `[object, ...]` | Any errors encountered during apply |
|
||||
|
||||
### Sysctl
|
||||
|
||||
#### Set Kernel Parameter
|
||||
|
||||
```
|
||||
POST /api/sysctl/set
|
||||
POST /api/network/sysctl/set
|
||||
```
|
||||
|
||||
Set a sysctl kernel parameter value via `sysctl -w`, then verify by reading it back.
|
||||
@@ -1459,7 +1531,7 @@ 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.
|
||||
Endpoints prefixed with `/api/logs/...`. Return log lines as JSON strings, wrapped in the standard `{"ok": true, "data": ...}` response contract.
|
||||
|
||||
### System Journal
|
||||
|
||||
@@ -1469,9 +1541,13 @@ Endpoints prefixed with `/api/logs/...`. These endpoints **do not** follow the s
|
||||
GET /api/logs/journal
|
||||
```
|
||||
|
||||
Return recent system journal entries as rendered HTML log lines.
|
||||
Return recent system journal entries.
|
||||
|
||||
**Response:** HTML fragment of `<div class="log-line">` elements.
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `string` | Raw journal log text |
|
||||
|
||||
### Nginx Logs
|
||||
|
||||
@@ -1481,9 +1557,15 @@ Return recent system journal entries as rendered HTML log lines.
|
||||
GET /api/logs/nginx/access
|
||||
```
|
||||
|
||||
Return recent nginx access log entries as rendered HTML.
|
||||
Return recent nginx access log entries.
|
||||
|
||||
**Response:** HTML fragment of `<div class="log-line">` elements.
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `string` | Raw access log text |
|
||||
|
||||
Returns HTTP `404` if the log file does not exist.
|
||||
|
||||
---
|
||||
|
||||
@@ -1493,9 +1575,15 @@ Return recent nginx access log entries as rendered HTML.
|
||||
GET /api/logs/nginx/error
|
||||
```
|
||||
|
||||
Return recent nginx error log entries as rendered HTML.
|
||||
Return recent nginx error log entries.
|
||||
|
||||
**Response:** HTML fragment of `<div class="log-line">` elements.
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `string` | Raw error log text |
|
||||
|
||||
Returns HTTP `404` if the log file does not exist.
|
||||
|
||||
### Dnsmasq Log
|
||||
|
||||
@@ -1505,9 +1593,13 @@ Return recent nginx error log entries as rendered HTML.
|
||||
GET /api/logs/dnsmasq
|
||||
```
|
||||
|
||||
Return recent dnsmasq journal entries as rendered HTML.
|
||||
Return recent dnsmasq journal entries.
|
||||
|
||||
**Response:** HTML fragment of `<div class="log-line">` elements.
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `string` | Raw dnsmasq journal text |
|
||||
|
||||
### Application Log
|
||||
|
||||
@@ -1517,9 +1609,15 @@ Return recent dnsmasq journal entries as rendered HTML.
|
||||
GET /api/logs/app
|
||||
```
|
||||
|
||||
Return recent application log entries as rendered HTML.
|
||||
Return recent application log entries.
|
||||
|
||||
**Response:** HTML fragment of `<div class="log-line">` elements.
|
||||
**Response:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `data` | `string` | Raw application log text |
|
||||
|
||||
Returns HTTP `404` if the log file does not exist.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -54,13 +54,13 @@ Vacuum Wall uses two distinct system users bridged by a shared group:
|
||||
|
||||
This design isolates privilege escalation entirely within the daemon, so a compromised Flask process cannot invoke sudo directly. The `lib/` modules no longer contain sudo calls; all privileged command execution lives in `daemon/handlers/*.py`.
|
||||
|
||||
**Dev mode variant**: When `install.sh --dev` is used, the repo owner (e.g., `wall`) becomes the WebUI user. The project directory remains owned by the repo owner, preserving git operations and code editing. The daemon user (`vacuum-walld`) has the repo owner's primary group as its own primary group, granting read access to project files. All subdirectories carry the setgid bit (`g+s`) so new files inherit the group regardless of the creator's primary group.
|
||||
**Dev mode variant**: When `scripts/install.sh --dev` is used, the repo owner (e.g., `wall`) becomes the WebUI user. The project directory remains owned by the repo owner, preserving git operations and code editing. The daemon user (`vacuum-walld`) has the repo owner's primary group as its own primary group, granting read access to project files. All subdirectories carry the setgid bit (`g+s`) so new files inherit the group regardless of the creator's primary group.
|
||||
|
||||
The `lib/` modules auto-discover the project root at runtime via `Path(__file__).resolve().parent.parent`. This works because `install.sh` performs an editable pip install (`pip install -e .`), keeping module files in the project directory rather than copying them to `site-packages/`.
|
||||
The `lib/` modules auto-discover the project root at runtime via `Path(__file__).resolve().parent.parent`. This works because `scripts/install.sh` performs an editable pip install (`pip install -e .`), keeping module files in the project directory rather than copying them to `site-packages/`.
|
||||
|
||||
## Install-Time Templating
|
||||
|
||||
System configuration files in `system/` are Jinja2 templates rendered by `install.sh` at install time:
|
||||
System configuration files in `system/` are Jinja2 templates rendered by `scripts/install.sh` at install time:
|
||||
|
||||
- **`systemd/vacuum-wall.service`**, **`systemd/vacuum-walld.service`**, **`systemd/vacuum-wall-acme.service`** — `{{ USER_NAME }}`, `{{ USER_DAEMON_NAME }}`, `{{ USER_GROUP }}`, `{{ PROJECT_DIR }}`, `{{ ACME_HOME }}` are substituted to produce the final systemd unit files installed to `/etc/systemd/system/`. The `PROJECT_DIR` template variable is set from the `INSTALL_DIR` environment variable (defaults to the repo root).
|
||||
- **`sudoers.d/vacuum-walld`** — `{{ USER_DAEMON_NAME }}` is substituted to produce the sudoers whitelist for the daemon user.
|
||||
|
||||
+9
-9
@@ -25,13 +25,13 @@ Download the Vacuum Wall repository onto the target machine, then run the instal
|
||||
# Production: all env vars
|
||||
MGMT_DOMAIN=wall.example.com \
|
||||
MGMT_PASS="strongpassword" \
|
||||
./install.sh --user vacuum-wall
|
||||
./scripts/install.sh --user vacuum-wall
|
||||
|
||||
# Dev mode: CLI flags, auto-detects repo owner
|
||||
./install.sh --dev --mgmt-pass strongpassword
|
||||
./scripts/install.sh --dev --mgmt-pass strongpassword
|
||||
|
||||
# mDNS (LAN-only, no DNS record needed)
|
||||
./install.sh --mgmt-domain vacuum-wall.local --mgmt-pass strongpass
|
||||
./scripts/install.sh --mgmt-domain vacuum-wall.local --mgmt-pass strongpass
|
||||
```
|
||||
|
||||
### Options
|
||||
@@ -51,7 +51,7 @@ All settings that can be passed as an environment variable also have a CLI flag
|
||||
| `--lan-ifaces` | `LAN_IFACES` | No | LAN interface names, comma-separated. Auto-detected from non-loopback, non-WAN interfaces. |
|
||||
| `--force-venv` | — | No | Force recreation of the Python virtual environment. |
|
||||
|
||||
Run `./install.sh --help` for full usage.
|
||||
Run `./scripts/install.sh --help` for full usage.
|
||||
|
||||
---
|
||||
|
||||
@@ -71,7 +71,7 @@ In dev mode, the ownership model preserves the developer's ability to work with
|
||||
### Running the Installer in Dev Mode
|
||||
|
||||
```bash
|
||||
./install.sh --dev --mgmt-pass strongpassword
|
||||
./scripts/install.sh --dev --mgmt-pass strongpassword
|
||||
```
|
||||
|
||||
The script detects the repo owner (e.g., `wall`), creates the `vacuum-walld` daemon user with the repo owner's primary group, and sets up the ownership model described above.
|
||||
@@ -88,7 +88,7 @@ You can deploy Vacuum Wall in a container or at any custom path. Use `--path` (o
|
||||
|
||||
```bash
|
||||
# Docker volume mount example
|
||||
./install.sh --path /app/vacuum-wall --user ww-app \
|
||||
./scripts/install.sh --path /app/vacuum-wall --user ww-app \
|
||||
--mgmt-domain proxy.internal --mgmt-pass strongpassword
|
||||
```
|
||||
|
||||
@@ -96,7 +96,7 @@ The systemd service unit files and sudoers whitelist are rendered from Jinja2 te
|
||||
|
||||
---
|
||||
|
||||
## What install.sh Does
|
||||
## What scripts/install.sh Does
|
||||
|
||||
The installer performs the following steps automatically:
|
||||
|
||||
@@ -130,7 +130,7 @@ The installer performs the following steps automatically:
|
||||
|
||||
### Idempotent Re-Runs
|
||||
|
||||
`install.sh` is fully idempotent and safe to run multiple times. Re-running the script:
|
||||
`scripts/install.sh` is fully idempotent and safe to run multiple times. Re-running the script:
|
||||
|
||||
- Skips the Python venv (use `--force-venv` to rebuild)
|
||||
- Restarts `vacuum-walld`, `vacuum-wall`, and reloads `nginx` to pick up changes
|
||||
@@ -138,7 +138,7 @@ The installer performs the following steps automatically:
|
||||
- Preserves existing `config.json` files (skips initial write if file exists)
|
||||
- Safely updates `htpasswd` (uses update mode instead of create mode)
|
||||
|
||||
This makes it safe for development workflows: simply run `bash install.sh` again to update an existing installation.
|
||||
This makes it safe for development workflows: simply run `bash scripts/install.sh` again to update an existing installation.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+8
-5
@@ -43,22 +43,24 @@ WireGuard support provides server-side VPN tunnel management. Peers are added th
|
||||
|
||||
## Quick Start
|
||||
|
||||
To install Vacuum Wall on a Debian 13 system, run `install.sh` as root with required settings (CLI flags or environment variables):
|
||||
To install Vacuum Wall on a Debian 13 system, run `scripts/install.sh` as root with required settings (CLI flags or environment variables):
|
||||
|
||||
```bash
|
||||
# Production
|
||||
./install.sh --mgmt-pass yourpassword
|
||||
./scripts/install.sh --mgmt-pass yourpassword
|
||||
|
||||
# Development (auto-detects your user)
|
||||
./install.sh --dev --mgmt-pass yourpassword
|
||||
./scripts/install.sh --dev --mgmt-pass yourpassword
|
||||
```
|
||||
|
||||
After installation, access the management interface at `https://<hostname>.local` using the credentials you configured. The `install.sh` script auto-detects the system hostname, network interfaces, and provisions nginx, authentication, an initial self-signed certificate, and all services. Run `./install.sh --help` for all options.
|
||||
After installation, access the management interface at `https://<hostname>.local` using the credentials you configured. The `scripts/install.sh` script auto-detects the system hostname, network interfaces, and provisions nginx, authentication, an initial self-signed certificate, and all services. Run `./scripts/install.sh --help` for all options.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
├── install.sh # Deployment script (renders Jinja2 templates)
|
||||
├── scripts/ # Utility scripts
|
||||
│ ├── install.sh # Deployment script (renders Jinja2 templates)
|
||||
│ └── update-vendor.sh # Download vendored libraries (acme.sh, htm)
|
||||
├── pyproject.toml # Project metadata + dependencies
|
||||
├── .venv/ # Python virtual environment
|
||||
├── config/ # Declarative JSON configuration (source of truth)
|
||||
@@ -136,6 +138,7 @@ After installation, access the management interface at `https://<hostname>.local
|
||||
│ ├── config.md
|
||||
│ └── hoover.md # Hoover SPA framework
|
||||
└── scripts/ # Utility scripts
|
||||
├── install.sh # Deployment script (renders Jinja2 templates)
|
||||
└── update-vendor.sh # Download vendored libraries (acme.sh, htm)
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user