main
Build and Push Container / build-and-push (push) Successful in 18s
UniFi Firewall Updater
Automatically updates UniFi Dream Machine firewall rules with your current public IPv4 and IPv6 addresses via the UniFi Network API.
Overview
This tool:
- Fetches your public IPv4 and IPv6 addresses
- Reads firewall rule definitions from a YAML config file
- Creates or updates policies on your UniFi controller to use those addresses
- Optionally sends notifications via NTFY when rules change
Designed to run as a one-shot script (e.g., via cron or systemd timer) whenever your public IP changes.
Quick Start
- Copy
.env.exampleto.envand fill in your UniFi credentials - Edit
config/rules.yamlto define your firewall rules - Install dependencies and run:
uv sync
uv run main.py
Configuration
Environment Variables
| Variable | Required | Description |
|---|---|---|
UNIFI_HOST |
Yes | UniFi controller URL (e.g., https://10.1.0.1) |
UNIFI_SITE_ID |
Yes | UniFi site ID |
UNIFI_API_TOKEN |
Yes | UniFi Network API token |
UNIFI_VERIFY_SSL |
No | Verify SSL certificates (default: false) |
CONFIG_FILE |
No | Path to rules YAML (default: config/rules.yaml) |
LOG_LEVEL |
No | Log level: DEBUG, INFO, WARNING, ERROR (default: INFO) |
DEBUG |
No | Attach debugpy on port 5678 (default: false) |
NTFY_URL |
No | NTFY server URL for notifications |
NTFY_TOPIC |
No | NTFY topic to publish to |
NTFY_API_KEY |
No | NTFY API key for authentication |
Rules YAML
Each rule defines a firewall policy that will use your public IP as the source:
rules:
- name: "Allow External to Gateway HTTP(S)"
source_zone: "External"
dest_zone: "DMZ"
ip_version: "IPV6"
action: "ALLOW"
allow_return_traffic: true
protocol: "tcp"
dest_ports: [80, 443]
dest_port_ranges:
- start: 8000
stop: 8080
logging_enabled: false
enabled: true
| Field | Required | Description |
|---|---|---|
name |
Yes | Unique policy name |
source_zone |
Yes | Source zone name (e.g., WAN, External) |
dest_zone |
Yes | Destination zone name (e.g., LAN, DMZ) |
ip_version |
No | IPV4 or IPV6 (default: IPV4) |
action |
No | ALLOW, BLOCK, or REJECT (default: ALLOW) |
allow_return_traffic |
No | Allow return traffic (default: true) |
protocol |
No | Protocol filter (e.g., tcp, udp) |
dest_ports |
No | List of destination ports |
dest_port_ranges |
No | List of {start, stop} port ranges |
logging_enabled |
No | Enable policy logging (default: false) |
enabled |
No | Enable the policy (default: true) |
Development
uv run pytest -v # Run tests (all mocked, no live API calls)
uv run ruff check # Lint
uv run pyright # Type check (strict mode)
Run a single test:
uv run pytest tests/test_unifi_firewall.py::TestBuildPolicyPayload::test_with_dest_ports -v
Container
Build and run with Podman:
podman build -t unifi-firewall .
podman run --env-file .env -v $(pwd)/config:/app/config unifi-firewall
Config is mounted at runtime via volume. Environment variables are passed at runtime.
How It Works
- Loads rules from
config/rules.yaml - Fetches public IPv4/IPv6 via
curl ifconfig.me - Lists zones from UniFi to resolve zone names to IDs
- For each rule:
- Finds existing policy by name
- Skips if IP already matches
- Updates if policy exists with different IP
- Creates if policy does not exist
- Sends NTFY notification if rules were created, updated, or failed
API Details
Uses UniFi Network API v1 at:
{UNIFI_HOST}/proxy/network/integration/v1/sites/{UNIFI_SITE_ID}
Authentication via X-API-Key header. See network_v10.4.57_openapi.json for the full schema.
Languages
Python
98.8%
Dockerfile
1.2%