Files
vacuum-wall/docs/config.md
T
mteehan e2f56b8cc8 Initial commit: SSL proxy / firewall appliance
Flask WebUI behind nginx reverse proxy with zone-based firewall, DHCP,
WireGuard, and ACME certificate management.
2026-05-07 22:24:24 +00:00

12 KiB

Configuration Reference

This document describes the JSON configuration files used by Vacuum Wall to manage each subsystem. All configuration is stored in the data/ directory as declarative JSON. The application renders these declarations into the format expected by each underlying service.

DHCP/DNS Configuration

File: data/dnsmasq/config.json

This file defines all DHCP server settings and DNS resolution behavior for the dnsmasq service. The application renders it into /etc/dnsmasq.d/vacuum-wall.conf.

{
  "dhcp": {
    "ranges": [
      {
        "interface": "eth1",
        "start": "192.168.2.100",
        "end": "192.168.2.200",
        "lease_time": "12h",
        "gateway": "192.168.2.1",
        "dns": "192.168.2.1"
      }
    ],
    "static_leases": [
      {
        "mac": "aa:bb:cc:dd:ee:ff",
        "ip": "192.168.2.50",
        "hostname": "printer"
      }
    ]
  },
  "dns": {
    "upstreams": ["8.8.8.8", "1.1.1.1"],
    "domain": "lan",
    "custom_records": [
      {
        "name": "nas.lan",
        "address": "192.168.2.10",
        "hostname": "nas"
      }
    ]
  }
}

DHCP Fields

Field Type Required Description
ranges array Yes One or more DHCP address pools. Each range defines a subnet from which addresses are leased.
ranges[].interface string Yes Network interface on which to serve this DHCP range (e.g., eth1).
ranges[].start string Yes First IP address in the pool.
ranges[].end string Yes Last IP address in the pool.
ranges[].lease_time string No DHCP lease duration. Accepts values like 12h, 1d, 30m. Default: 1h.
ranges[].gateway string No Default gateway advertised to DHCP clients. Typically the router's LAN IP.
ranges[].dns string No DNS server address advertised to DHCP clients. Typically the Vacuum Wall host's LAN IP.
static_leases array No Fixed IP assignments tied to MAC addresses. Clients with matching MACs always receive the specified IP.
static_leases[].mac string Yes MAC address of the client (colon-separated lowercase hex).
static_leases[].ip string Yes The IP address to assign to this MAC. Must be outside the dynamic pool ranges.
static_leases[].hostname string No Hostname to associate with the lease. Used for reverse DNS and mDNS.

DNS Fields

Field Type Required Description
upstreams array Yes Upstream DNS servers to forward unresolved queries to. Supports IPv4 and IPv6 addresses.
domain string Yes Local domain suffix. Hostnames without a FQDN are resolved within this domain (e.g., printer becomes printer.lan).
custom_records array No Static DNS A records for internal services and devices.
custom_records[].name string Yes Fully qualified domain name (e.g., nas.lan).
custom_records[].address string Yes The IP address to resolve the name to.
custom_records[].hostname string No Short hostname without the domain suffix. Adds a reverse DNS entry as well.

Additional dnsmasq directives can be appended verbatim by placing plain-text files in data/dnsmasq/fragments/. Each file's contents are concatenated into the generated config. This is useful for advanced options not covered by the JSON schema (e.g., bogus-priv, cache-size, log-queries).

Nginx Configuration

File: data/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.

{
  "domains": {
    "app.example.com": {
      "backend": {
        "host": "192.168.2.50",
        "port": 8080,
        "proto": "http"
      },
      "force_ssl": true,
      "headers": {
        "X-Forwarded-Proto": "https",
        "X-Real-IP": "$remote_addr"
      },
      "cert": {
        "type": "acme",
        "email": "admin@example.com"
      }
    }
  },
  "management": {
    "domain": "wall.lan",
    "backend": {
      "host": "127.0.0.1",
      "port": 9090,
      "proto": "http"
    },
    "auth": {
      "user": "admin",
      "htpasswd": "/home/wall/vacuum-wall/data/nginx/.htpasswd"
    }
  },
  "ssl": {
    "protocols": "TLSv1.2 TLSv1.3",
    "ciphers": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384",
    "prefer_server_ciphers": false
  }
}

Domain Entries

The domains object maps domain names (keys) to proxy configurations. Each entry produces a separate nginx server block.

Field Type Required Description
backend object Yes The upstream service that receives proxied traffic.
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 Yes Protocol for the backend connection: http or https.
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 object No Certificate configuration for this domain. Required unless the management domain shares its cert.
cert.type string Yes (if cert) Certificate provisioning method. One of: acme, file, or selfsigned.
cert.email string Yes (if acme) ACME account email used by Let's Encrypt.
cert.path object Yes (if file) Paths to certificate files.
cert.path.certificate string Yes (if file) Full path to the public certificate file (PEM).
cert.path.key string Yes (if file) Full path to the private key file (PEM).

Certificate Types

Type Description
acme Vacuum Wall uses acme.sh to request and renew a Let's Encrypt certificate via the HTTP-01 challenge. The nginx configuration is temporarily modified to serve the ACME challenge files at /.well-known/acme-challenge/. The email field is required.
file Use a pre-existing certificate and private key from the local file system. The path.certificate and path.key fields must point to readable PEM files. Vacuum Wall will not attempt to renew these certificates.
selfsigned Vacuum Wall generates a self-signed certificate and private key on first apply. Useful for internal domains or testing. The generated certificate is stored alongside other acme-managed files in ~/.acme.sh/ with a .selfsigned marker.

Management Domain

The management block configures the Vacuum Wall admin interface itself. It follows the same structure as a domain entry but includes an auth block for HTTP Basic Authentication.

Field Type Required Description
domain string Yes The hostname used to access the management WebUI (e.g., wall.lan).
backend object Yes Points to the Flask app at 127.0.0.1:9090.
auth object Yes HTTP Basic Authentication configuration.
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 .htpasswd file can be created with the htpasswd utility:

htpasswd -bc /home/wall/vacuum-wall/data/nginx/.htpasswd admin yourpassword

Global SSL Settings

The ssl block defines TLS parameters applied to all HTTPS server blocks via the shared snippet.

Field Type Required Description
protocols string No nginx ssl_protocols directive value. Default: TLSv1.2 TLSv1.3.
ciphers string No nginx ssl_ciphers directive value. Default is a curated AEAD-only cipher string.
prefer_server_ciphers boolean No Whether to prefer server cipher order. Default: false.

WireGuard Configuration

File: data/wireguard/config.json

This file defines the WireGuard server interface and all connected peers. The application renders it into /etc/wireguard/wg0.conf and applies it with wg-quick.

{
  "interface": {
    "name": "wg0",
    "listen_port": 51820,
    "private_key": "kOv8lK...',
    "public_key": "YzP3xI...',
    "addresses": ["10.137.0.1/24"],
    "post_up": null,
    "post_down": null
  },
  "peers": {
    "alice": {
      "public_key": "nR7mQ2...',
      "private_key": "xLpDgF...',
      "endpoint": "203.0.113.1:51820",
      "allowed_ips": ["0.0.0.0/0"],
      "persistent_keepalive": 25,
      "preshared_key": null
    }
  }
}

Interface Fields

Field Type Required Description
name string Yes WireGuard interface name. Default: wg0.
listen_port integer Yes Port the WireGuard interface listens on. Default: 51820. Must be opened in the firewall.
private_key string Yes Base64-encoded private key for the server interface. Use wg genkey to generate.
public_key string Yes Corresponding public key. Use wg pubkey to derive from the private key.
addresses array Yes IP address(es) assigned to the server interface in CIDR notation (e.g., 10.137.0.1/24).
post_up string No Shell command to run after the interface is brought up. Common uses: adding NAT rules, enabling IP forwarding for the tunnel. Set to null to omit.
post_down string No Shell command to run after the interface is brought down. Used to clean up rules added by post_up. Set to null to omit.

Peer Fields

Peers are stored in an object keyed by a human-readable identifier (e.g., alice, office-laptop). Each peer entry defines a WireGuard peer configuration.

Field Type Required Description
public_key string Yes The peer's public key.
private_key string No The peer's private key, stored for generating downloadable client configuration files. This value is stripped from all API responses — the WebUI never exposes peer private keys over the network.
endpoint string No The peer's public endpoint (IP:port). Required for server-initiated connections (e.g., the server reaching out to a peer behind a firewall). Leave empty or null for peer-initiated connections where the peer connects to the server.
allowed_ips array Yes CIDR blocks that traffic from this peer is allowed to route. ["0.0.0.0/0"] allows all traffic. ["10.137.0.0/16"] restricts traffic to the VPN subnet.
persistent_keepalive integer No Keepalive interval in seconds. 25 is recommended for peers behind NAT. Set to 0 or null to disable.
preshared_key string No Optional pre-shared key for post-quantum resistance. Use wg genpsk to generate.

Client Configuration Generation

When a peer's private_key is set, the WebUI can generate a complete WireGuard client configuration file that the user can download and import into their WireGuard client app. The generated config includes the peer's interface settings, the server as a [Peer] entry, and the appropriate Endpoint and AllowedIPs values. The private_key field is written into the client config file for download but is never returned by the API.

Applying Configuration

When configuration is saved through the WebUI or API, the application:

  1. Validates all key pairs and IP ranges.
  2. Renders the wg0.conf file from the JSON configuration.
  3. Copies the rendered file to /etc/wireguard/wg0.conf using the sudo whitelist.
  4. Runs sudo wg-quick up wg0 to apply the configuration.
  5. Returns success or error status to the caller.

If the interface is already up, wg-quick up will reconfigure it in place without dropping existing connections.