412 lines
25 KiB
Markdown
412 lines
25 KiB
Markdown
# Deployment Guide
|
|
|
|
This guide walks through deploying Vacuum Wall on a real appliance or server. Vacuum Wall is an SSL proxy firewall appliance that combines edge proxying, firewall management, DHCP, DNS, and WireGuard in a single device.
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
- **OS**: Clean Debian 13 (Trixie) system. Also works on Debian 12 with backports for firewalld.
|
|
- **git**: Required for cloning the repository.
|
|
- **Access**: Root access to the machine.
|
|
- **Networking**:
|
|
- One public-facing network interface (external/edge). This receives inbound traffic and serves the management UI.
|
|
- At least one LAN network interface (internal). This connects to your downstream network and will serve DHCP/DNS.
|
|
- **DNS**: A DNS record pointing to the appliance's public IP for the management domain (e.g., `wall.example.com`).
|
|
- **Minimum hardware**: 1 CPU, 512 MB RAM, 4 GB disk.
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
Download the Vacuum Wall repository onto the target machine, then run the installer with required settings. All options accept both CLI flags and environment variables (CLI takes precedence).
|
|
|
|
```bash
|
|
# Production: all env vars
|
|
MGMT_DOMAIN=wall.example.com \
|
|
MGMT_PASS="strongpassword" \
|
|
./scripts/install.sh --user vacuum-wall
|
|
|
|
# Dev mode: CLI flags, auto-detects repo owner
|
|
./scripts/install.sh --dev --mgmt-pass strongpassword
|
|
|
|
# mDNS (LAN-only, no DNS record needed)
|
|
./scripts/install.sh --mgmt-domain vacuum-wall.local --mgmt-pass strongpass
|
|
```
|
|
|
|
### Options
|
|
|
|
All settings that can be passed as an environment variable also have a CLI flag equivalent. CLI flags take precedence over environment variables.
|
|
|
|
| Flag | Env Var | Required | Description |
|
|
|---|---|---|---|
|
|
| -- | `MGMT_DOMAIN` | No | Domain for the management WebUI. Auto-detected from the system hostname; defaults to `$(hostname -f \|\| hostname).local` (FQDN first, falling back to the short hostname; mDNS-served on the LAN). **Errors if the hostname is undetectable and this is not set.** |
|
|
| `--mgmt-domain` | `MGMT_DOMAIN` | No | (same as above) |
|
|
| `--mgmt-pass` | `MGMT_PASS` | Yes | Password for the initial admin user (default: `admin`). Creates the admin user in the SQLite database with full `rw` permissions on all subsystems. |
|
|
| `--mgmt-user` | `MGMT_USER` | No | Username for WebUI access. Defaults to `admin`. |
|
|
| `--user, -u` | `USER_NAME` | Yes* | WebUI service user (created if it does not exist). Required for non-dev mode. In `--dev` mode, auto-detected from repo owner. |
|
|
| `--path, -p` | `INSTALL_DIR` | No | Install directory. Defaults to repo root. Set to deploy from a custom path (e.g., `/opt/vacuum-wall`). |
|
|
| `--dev` | -- | No | Development mode: auto-detects repo owner as service user, skips safety warning. |
|
|
| `--wan-iface` | `WAN_IFACE` | No | WAN interface name. Auto-detected from default gateway. |
|
|
| `--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 `./scripts/install.sh --help` for full usage.
|
|
|
|
---
|
|
|
|
## Dev Mode
|
|
|
|
The `--dev` flag is designed for developers working in a git clone. It auto-detects the repo owner and uses that user as the WebUI service user.
|
|
|
|
### Ownership Model
|
|
|
|
In dev mode, the ownership model preserves the developer's ability to work with the repository:
|
|
|
|
- **Project directory**: Owned by the repo owner (e.g., `wall`), group is the repo owner's primary group (e.g., `wall`). The developer retains full control — `git add`, `git commit`, editing code and config files all work normally.
|
|
- **Daemon access**: The daemon user (`walld`, i.e. `${USER_NAME}d`) has the repo owner's primary group as its own primary group, granting read access to all project files. The project directory has the setgid bit (`g+s`) on all subdirectories, ensuring new files inherit the group.
|
|
- **`.venv/` and `data/`**: Owned by the repo owner, group is the repo owner's primary group. The developer can run `pip install`, inspect logs, and manage runtime artifacts. The daemon reads `.venv/` (Python interpreter) and writes to `data/` (runtime files) via group permissions.
|
|
- **Daemon socket** (`data/daemon.sock`): Owned by `walld:<group>` (mode `0660`). The repo owner accesses it via primary group membership.
|
|
|
|
### Running the Installer in Dev Mode
|
|
|
|
```bash
|
|
./scripts/install.sh --dev --mgmt-pass strongpassword
|
|
```
|
|
|
|
The script detects the repo owner (e.g., `wall`), creates the `walld` daemon user (`${USER_NAME}d`) with the repo owner's primary group, and sets up the ownership model described above.
|
|
|
|
### Idempotent Re-Runs
|
|
|
|
Running `--dev` again is safe. The ownership is idempotent (`chown -R` to the same owner), supplementary group membership is deduplicated by the OS, and setgid bits are applied recursively each time.
|
|
|
|
---
|
|
|
|
## Container / Custom Deployment
|
|
|
|
You can deploy Vacuum Wall in a container or at any custom path. Use `--path` (or `INSTALL_DIR`) for the mount or bind path, and `--user` (or `USER_NAME`) for whatever system user exists:
|
|
|
|
```bash
|
|
# Docker volume mount example
|
|
./scripts/install.sh --path /app/vacuum-wall --user ww-app \
|
|
--mgmt-domain proxy.internal --mgmt-pass strongpassword
|
|
```
|
|
|
|
The systemd `.service` unit files and the sudoers whitelist are rendered from Jinja2 templates at install time, substituting `USER_NAME`, `USER_DAEMON_NAME`, `USER_GROUP`, `PROJECT_DIR`, and `ACME_HOME`. This means no hardcoded paths remain after installation.
|
|
|
|
---
|
|
|
|
## What scripts/install.sh Does
|
|
|
|
The installer performs the following steps automatically:
|
|
|
|
- **Package installation**: Installs firewalld, nginx, dnsmasq, avahi-daemon, wireguard-tools, python3, python3-pip, jq, curl, iptables, nftables, and apache2-utils.
|
|
- **WebUI user creation**: Creates the WebUI user (from `--user`) as a system user if it does not exist.
|
|
- **Shared group**: Uses the WebUI user's primary group as the shared group between both service users.
|
|
- **Daemon user creation**: Creates the daemon user `${USER_NAME}d` (the literal `vacuum-walld` only when the WebUI user is `vacuum-wall`) — a system user with `NOPASSWD` sudo access for privileged operations. Owns the daemon socket and, outside `--dev` mode, the project directory (in dev mode the repo owner keeps project ownership).
|
|
- **Python venv**: Creates the Python virtual environment and installs project dependencies. Skips if already present (use `--force-venv` to recreate).
|
|
- **acme.sh installation**: Fetches the vendored acme.sh (via `scripts/update-vendor.sh`) and installs it to `data/acme/acme.sh`, skipping if it is already present. Also installs the `system/acme-deploy.sh` deploy hook into `data/acme/deploy/acme-deploy.sh` (acme.sh only resolves hooks from its own deploy directory) and repairs ownership of the acme.sh runtime conf files under `data/acme/` — including `account.conf`, which is chmodded to `0640` — to the daemon user, so the first acme.sh run cannot fail on owner-only files.
|
|
- **Directory setup**: Creates config directories under `config/` for each subsystem's declarative JSON, and data directories under `data/` for generated files (nginx sites, dnsmasq fragments, firewall backup, WireGuard config).
|
|
- **System-directory ownership repair**: Checks top-level system directories (`/`, `/bin`, `/boot`, `/etc`, `/home`, `/opt`, `/root`, `/srv`, `/usr`, `/var`, …) for non-root ownership — some appliance images ship with system paths owned by a regular user, which trips systemd-tmpfiles' "unsafe path transition" check. Mis-owned top-level directories are chown'd to `root:root`; if deeper mis-ownership is detected, the installer warns with a full-repair command to run before re-running.
|
|
- **Static-asset permissions**: `chmod a+rX` on `webui/static/` (plus `a+x` up the parent directory chain) so nginx's `www-data` workers can serve the management UI's static assets directly from disk, regardless of checkout umask.
|
|
- **Template rendering**: Renders the systemd `.service` files and the sudoers whitelist via Jinja2, substituting `USER_NAME`, `USER_DAEMON_NAME`, `USER_GROUP`, `PROJECT_DIR`, and `ACME_HOME`. Installed systemd and sudoers files contain no hardcoded values.
|
|
- **Sudoers whitelist**: Installs a restrictive sudoers file at `/etc/sudoers.d/vacuum-walld` granting the daemon user `NOPASSWD` sudo for only the specific privileged commands needed: firewalld management (`firewall-cmd`), nginx (config test/reload, copying/removing the generated conf files), dnsmasq (restart, lease-file reads, fragment install), WireGuard (`wg`, `wg-quick`, installing `wg0.conf`), network interface queries (`ip -o link/addr show`), systemd-networkd (`networkctl` status/reload/reconfigure, managing `/etc/systemd/network`), sysctl writes, group-permission repair on the ACME home, and journal/log reads (`journalctl`, `cat /var/log/nginx/*`). Validates syntax with `visudo -cf`.
|
|
- **IP forwarding**: Enables `net.ipv4.ip_forward=1` in sysctl.conf and applies it at runtime, required for routing traffic between zones. Appends only if not already present.
|
|
- **Firewalld initialization**: Starts and enables firewalld. Opens HTTP, HTTPS, and SSH services on the public zone for management access.
|
|
- **Dnsmasq initialization**: Starts and enables dnsmasq for future DHCP/DNS serving on internal interfaces.
|
|
- **mDNS broadcast**: Enables and starts avahi-daemon so the appliance advertises its hostname (`<hostname>.local`) on the local network.
|
|
- **Self-signed certificate**: Generates a temporary self-signed X.509 certificate for the management domain via `POST /acme/self-signed` (CN set to the domain), written to `data/certs/<domain>.crt` and `data/certs/<domain>.key` — not under `data/acme/`, where acme.sh stores issued certs. Idempotent: skips generation when both files already exist.
|
|
- **Management proxy configuration**: Registers the management domain via `POST /nginx/domains/update` (falling back to `POST /nginx/domains/add`) as the special built-in `webui` backend entry (cert `selfsigned`, forced SSL). The `/` → 127.0.0.1:9090 (Flask) and `/ws` → 127.0.0.1:9091 (daemon WebSocket) mapping is derived by the daemon from the built-in webui backend — it is not passed as paths config. Then applies nginx via `POST /nginx/apply`.
|
|
- **Admin user**: Creates the admin user (default username `admin`) with the password provided via `--mgmt-pass` in the SQLite database (`data/auth.db`), with `rw` permissions on all subsystems, and writes `config/auth/config.json` (JWT + WebAuthn settings) if missing. On re-run, updates the admin password if already present. The bootstrap runs with `VACUUM_WALL_SEED_BUILTIN_ADMIN=0`, suppressing the last-resort builtin admin seed so exactly one account exists on a fresh install.
|
|
- **Initial configs**: Once the daemon socket is up, the installer writes initial state over the daemon API: `POST /acme/self-signed` (management cert), the management domain plus `POST /nginx/apply`, and firewall zone assignment — `POST /firewall/zones/interfaces` (WAN interface → `public`) and `POST /firewall/zones/services` (http/https/ssh on `public`) when a WAN interface was detected, and `POST /firewall/zones/interfaces` (LAN interfaces → `internal`) when LAN interfaces were detected. These writes are not skipped; the only skip-if-exists rule applies to the auth config (see **Admin user**).
|
|
- **System config import**: On startup, the daemon reconciles any live system configurations (dnsmasq, wireguard, networkd, nginx, firewall) with the declarative JSON configs. This prevents drift when system files were edited manually.
|
|
- **Systemd units**: Installs four units — three `.service` files are rendered from Jinja2 templates; the `.timer` is installed verbatim:
|
|
- `vacuum-walld.service` — the privileged background daemon (aiohttp, daemon socket).
|
|
- `vacuum-wall.service` — the Flask WebUI backend.
|
|
- `vacuum-wall-acme.service` — the certificate renewal oneshot.
|
|
- `vacuum-wall-acme.timer` — periodic timer that triggers cert renewals (no template variables).
|
|
|
|
A fifth file, `system/tmpfiles.d/vacuum-wall.conf`, is installed to `/etc/tmpfiles.d/vacuum-wall.conf` and `systemd-tmpfiles --create` is run immediately — load-bearing for the hardened unit: it provisions the volatile `/run` entries the daemon needs before `vacuum-walld` spawns (restored at every boot by `systemd-tmpfiles-setup.service`).
|
|
- **Firewalld zones**: Assigns initial zones via the daemon API (the installer does not create zones directly):
|
|
- `public` — the WAN interface is assigned here and the `http`, `https`, and `ssh` services are opened for management access (only when a WAN interface was detected).
|
|
- `internal` — the LAN interfaces are assigned here (only when LAN interfaces were detected); no services are added at install time.
|
|
- `vpn` — **not** created by the installer. It is managed dynamically by `lib/sync.py` only while WireGuard peers exist (interface assignment, masquerade, and rich rules), and is cleaned up again when WireGuard is deactivated.
|
|
- **Legacy nginx config cleanup**: Removes the old nginx bootstrap configs (`/etc/nginx/conf.d/vacuum-wall-map.conf` and `/etc/nginx/conf.d/vacuum-wall-mgmt.conf`), which are replaced by the daemon-generated nginx configuration.
|
|
- **Service startup**: Enables and starts/restarts nginx, the daemon (`vacuum-walld`), the WebUI (`vacuum-wall`), and the ACME renewal timer. nginx is reloaded (or restarted) to pick up any config changes.
|
|
- **ACME account**: No account registration during install. Register the account via the WebUI after first login.
|
|
|
|
### Idempotent Re-Runs
|
|
|
|
`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
|
|
- Preserves existing SSL certificates (skips self-signed generation if a cert exists)
|
|
- Preserves the existing auth config (`config/auth/config.json` is only written if missing — the only skip-if-exists config rule)
|
|
- Updates admin user password if changed
|
|
|
|
This makes it safe for development workflows: simply run `bash scripts/install.sh` again to update an existing installation.
|
|
|
|
---
|
|
|
|
## Post-Installation
|
|
|
|
### Verify Services
|
|
|
|
After the installer completes, confirm all services are running:
|
|
|
|
```bash
|
|
systemctl status vacuum-walld vacuum-wall nginx firewalld dnsmasq
|
|
```
|
|
|
|
Each should be active (running). The `vacuum-wall-acme.timer` should also be active (waiting).
|
|
|
|
### Access the WebUI
|
|
|
|
Open a browser and navigate to:
|
|
|
|
```
|
|
https://wall.example.com
|
|
```
|
|
|
|
Log in with the username and password you provided during installation.
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `VACUUM_WALL_DB_BACKEND` | `sqlite` | Database backend selection |
|
|
| `VACUUM_WALL_DB_PATH` | `data/auth.db` | SQLite database file path |
|
|
| `VACUUM_WALLD_SOCKET` | `data/daemon.sock` | Daemon Unix socket path (`daemon/server.py:669`) |
|
|
| `VACUUM_WALLD_WS_PORT` | `9091` | Daemon WebSocket port on `127.0.0.1` for real-time state streaming (`daemon/server.py:31`) |
|
|
| `VACUUM_WALL_POLL_INTERVALS` | built-in per-subsystem defaults | Comma-separated `subsystem:seconds` overrides for the state-poll intervals, e.g. `firewall:60,wireguard:5`; non-integer or ≤ 0 values are skipped with a warning (`daemon/server.py:34`) |
|
|
| `VACUUM_WALL_DEV` | unset (off) | Dev-mode flag: disables aggressive static-asset caching in the WebUI (`webui/server.py:86`) |
|
|
| `VACUUM_WALL_LOG_LEVEL` | `INFO` | Log level for the WebUI and daemon processes (`lib/logging.py:49`) |
|
|
| `VACUUM_WALL_EXTERNAL_IP_URL` | built-in detection | Custom URL for external-IP detection used by ACME (`daemon/handlers/acme.py:285`) |
|
|
| `VACUUM_WALL_SEED_BUILTIN_ADMIN` | `1` | Set to `0` to skip the last-resort builtin admin seed in `get_db()`; `scripts/bootstrap_auth.py` always sets this since bootstrap creates the operator user itself (`lib/db.py:307`) |
|
|
|
|
### Post-Deploy Verification
|
|
|
|
1. Confirm `config/auth/config.json` exists with JWT secret and WebAuthn RP configuration
|
|
2. Confirm `data/auth.db` exists with admin user present
|
|
3. Confirm the management server block has no server-level `auth_basic` directive — the management UI is authenticated by the Flask-layer JWT middleware, not nginx
|
|
4. Confirm `location /ws` has `auth_basic off` — the WebSocket is authenticated by the daemon via the raw-JWT `Sec-WebSocket-Protocol` subprotocol, never by nginx
|
|
5. Access the WebUI at `https://<management-domain>` — should show a login page
|
|
|
|
### Certificate Note
|
|
|
|
The initial certificate is **self-signed** and generated during installation. Your browser will show a security warning. This is expected. Once DNS is pointing to the appliance and port 80 is accessible from the internet, use the **Certs** tab in the WebUI to issue a real ACME certificate for the management domain. After issuance, go to the **Proxy** tab and click **Apply** to reload nginx with the new cert.
|
|
|
|
---
|
|
|
|
## Configuring Your First Network
|
|
|
|
After installation, the appliance has no interfaces assigned to zones and no DHCP ranges configured. Use the WebUI to set up your LAN.
|
|
|
|
### 1. Assign a LAN Interface to the Internal Zone
|
|
|
|
1. Navigate to the **Interfaces** tab.
|
|
2. From the interface list, select your LAN interface (e.g., `eth1`).
|
|
3. Assign it to the `internal` zone.
|
|
4. Click **Apply** to update the firewall configuration.
|
|
|
|
### 2. Enable NAT/Masquerade
|
|
|
|
1. Go to the **NAT** tab.
|
|
2. Enable masquerade on the `internal` zone. This allows devices on your LAN to reach the internet through the appliance's external interface.
|
|
3. Click **Apply**.
|
|
|
|
### 3. Configure DHCP
|
|
|
|
1. Go to the **DHCP** tab.
|
|
2. Click **Add Range**.
|
|
3. Specify:
|
|
- Address range: e.g., `192.168.2.100-192.168.2.200`
|
|
- Lease time: e.g., `12h`
|
|
- Interface: `eth1` (or whichever interface you assigned to internal)
|
|
4. Click **Apply**. This writes the dnsmasq configuration and reloads the service.
|
|
|
|
DNS resolution will also be provided on this interface by dnsmasq, which forwards queries upstream.
|
|
|
|
---
|
|
|
|
## Adding a Proxy Domain
|
|
|
|
Vacuum Wall's primary function is proxying incoming HTTPS traffic to internal backend services.
|
|
|
|
### 1. Add the Domain
|
|
|
|
1. Navigate to the **Proxy** tab.
|
|
2. Click **Add Domain**.
|
|
3. Fill in:
|
|
- **Domain**: The public domain name (e.g., `app.example.com`).
|
|
- **Backend Host**: The internal IP address of the service (e.g., `192.168.2.50`).
|
|
- **Backend Port**: The port the service listens on (e.g., `8080`).
|
|
|
|
### 2. Issue a Certificate
|
|
|
|
1. Go to the **Certs** tab.
|
|
2. Click **Issue Certificate** and enter the domain name.
|
|
3. ACME validation requires that port 80 on the appliance is reachable from the internet and that the domain's DNS A record points to the appliance's public IP.
|
|
|
|
### 3. Reload Nginx
|
|
|
|
1. Return to the **Proxy** tab.
|
|
2. Click **Apply** to write the nginx configuration and reload the service.
|
|
|
|
The proxied domain is now accessible via HTTPS at the configured domain name.
|
|
|
|
---
|
|
|
|
## Setting up WireGuard
|
|
|
|
Vacuum Wall includes integrated WireGuard server support for VPN access.
|
|
|
|
### 1. Initialize the Server
|
|
|
|
1. Navigate to the **WireGuard** tab.
|
|
2. Click **Initialize**. This generates the server's private and public keys and creates the `wg0` interface configuration.
|
|
|
|
### 2. Add a Peer
|
|
|
|
1. Click **Add Peer**.
|
|
2. Enter a peer name (e.g., `alice`).
|
|
3. Optionally set a specific AllowedIPs range for this peer (defaults to `0.0.0.0/0`).
|
|
4. Optionally set an **Endpoint** if you know the peer's static public IP (restricts incoming connections to that IP).
|
|
5. Click **Add**. The peer's public key and preshared key are generated automatically.
|
|
|
|
### 3. Activate the Tunnel
|
|
|
|
1. Click **Apply** to write the WireGuard configuration and bring up the `wg0` interface.
|
|
|
|
### 4. Download Client Configuration
|
|
|
|
1. In the peer list, use the peer actions menu to download the client configuration file for the peer.
|
|
2. Install this configuration on the client device.
|
|
|
|
### 5. Assign WireGuard to a Firewall Zone
|
|
|
|
1. Navigate to the **Interfaces** tab.
|
|
2. Assign `wg0` to the `vpn` zone.
|
|
3. The `vpn` zone allows all traffic by default (target ACCEPT). Adjust firewall rules as needed to restrict VPN access to specific services.
|
|
|
|
### 6. Configure Firewall Rules for VPN Traffic
|
|
|
|
1. Go to the **Firewall** tab or use the **NAT** tab.
|
|
2. Add rules as needed to control what VPN peers can access. For example, you can restrict VPN peers to only reach specific internal services rather than the entire LAN.
|
|
3. Optionally enable masquerade on the `vpn` zone to allow VPN clients to reach the internet through the appliance.
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Services Won't Start
|
|
|
|
Check service logs and configuration:
|
|
|
|
```bash
|
|
journalctl -u vacuum-walld --no-pager -n 50
|
|
journalctl -u vacuum-wall --no-pager -n 50
|
|
nginx -t
|
|
```
|
|
|
|
Both units also keep journal output on disk under `/var/log/vacuum-wall/` (`LogsDirectory=vacuum-wall` on both units). Nginx writes per-domain access/error logs to `/var/log/nginx/wall_mgmt_*.log` for the management domain and `/var/log/nginx/<domain>_*.log` for each proxy domain.
|
|
|
|
Common causes include port conflicts (another service on port 80/443, 9090, or 9091 — the daemon's WebSocket port), missing dependencies, or file permission issues on `data/`.
|
|
|
|
### Firewall Rules Not Applying
|
|
|
|
Verify that firewalld is running:
|
|
|
|
```bash
|
|
firewall-cmd --state
|
|
systemctl status firewalld
|
|
```
|
|
|
|
If firewalld is not running, start it with `systemctl start firewalld`. Check that the sudoers whitelist is valid:
|
|
|
|
```bash
|
|
visudo -cf /etc/sudoers.d/vacuum-walld
|
|
```
|
|
|
|
### Certificate Issuance Fails
|
|
|
|
ACME validation via the ACME provider requires:
|
|
|
|
- The domain's DNS A record points to the appliance's public IP.
|
|
- Port 80 (HTTP-01 challenge) is accessible from the internet on the external interface.
|
|
- An ACME account is registered (check the Certs page in the WebUI). Verify with:
|
|
|
|
```bash
|
|
su -s /bin/bash "$USER_DAEMON_NAME" -c "~/data/acme/acme.sh --list"
|
|
```
|
|
|
|
If port 80 is blocked or the DNS record hasn't propagated yet, wait and retry. The ACME timer will also attempt renewal automatically.
|
|
|
|
### DHCP Not Working
|
|
|
|
Verify that:
|
|
|
|
- The LAN interface is assigned to a firewalld zone (check the **Interfaces** tab or `firewall-cmd --get-active-zones`).
|
|
- Dnsmasq is running: `systemctl status dnsmasq`.
|
|
- A DHCP range is configured for the correct interface. Check dnsmasq config at `data/dnsmasq/`.
|
|
- The firewall allows DHCP traffic on the internal zone: `firewall-cmd --zone=internal --list-services` should include `dhcp` and `dns`.
|
|
|
|
### Locked Out of WebUI
|
|
|
|
If you lose access to the admin account, you can reset the password directly via SQLite:
|
|
|
|
```bash
|
|
# Stop the services
|
|
sudo systemctl stop vacuum-wall vacuum-walld
|
|
|
|
# Reset password (replace 'newpassword' with desired password)
|
|
sqlite3 data/auth.db "UPDATE users SET password_hash='NEW_HASH_HERE' WHERE username='admin';"
|
|
```
|
|
|
|
The password hash must be an Argon2id hash. You can generate one:
|
|
|
|
```bash
|
|
python3 -c "from lib.password import hash_password; print(hash_password('newpassword'))"
|
|
```
|
|
|
|
Alternatively, use the SQLite prompt to directly inspect and modify user data:
|
|
|
|
```bash
|
|
sqlite3 data/auth.db ".tables"
|
|
sqlite3 data/auth.db "SELECT username FROM users;"
|
|
sqlite3 data/auth.db "SELECT * FROM permissions WHERE username='admin';"
|
|
```
|
|
|
|
### Database Corruption
|
|
|
|
If the SQLite database becomes corrupted:
|
|
|
|
1. Stop the services: `sudo systemctl stop vacuum-wall vacuum-walld`
|
|
2. Inspect: `sqlite3 data/auth.db "PRAGMA integrity_check;"`
|
|
3. If the file is unrecoverable, delete it (`rm data/auth.db`) and start the services: `sudo systemctl start vacuum-walld vacuum-wall`. The schema is recreated on startup; if the users table is empty, the last-resort builtin admin is seeded with a random password written to `/var/log/vacuum-wall/auth.log`.
|
|
4. Re-set the password via the WebUI, or use the SQLite steps under "Locked Out of WebUI".
|
|
|
|
### WebUI Not Accessible
|
|
|
|
1. Verify nginx is running: `systemctl status nginx`.
|
|
2. Test nginx configuration: `nginx -t`.
|
|
3. Check the management proxy domain configuration via the WebUI Proxy tab, or by inspecting `config/nginx/config.json`.
|
|
4. Ensure the daemon is running and its Unix socket exists: `systemctl status vacuum-walld` and `ls -l data/daemon.sock` — the WebUI proxies every API call through this socket.
|
|
5. Ensure the WebUI service is listening on port 9090 (`ss -tlnp | grep 9090`) and the daemon's WebSocket endpoint on port 9091 (`ss -tlnp | grep 9091`).
|
|
6. If using the self-signed cert, confirm your browser trusts it or use the WebUI to issue a real ACME certificate.
|
|
|
|
---
|
|
|
|
## Reference
|
|
|
|
| Component | Service | Config Location |
|
|
|---|---|---|
|
|
| Daemon (privileged) | `vacuum-walld.service` | `daemon/` |
|
|
| WebUI backend | `vacuum-wall.service` | `webui/` |
|
|
| Reverse proxy | `nginx` | `config/nginx/config.json` (via daemon) |
|
|
| Firewall | `firewalld` | Managed via WebUI and `firewall-cmd` |
|
|
| DHCP/DNS | `dnsmasq` | `config/dnsmasq/` |
|
|
| VPN | wireguard-tools | `config/wireguard/` |
|
|
| Certificates | `vacuum-wall-acme.timer` | `$PROJECT_DIR/data/acme/` |
|
|
| Sudoers (daemon) | — | `/etc/sudoers.d/vacuum-walld` | |