# manage-proxmox-ipset Keep Proxmox cluster firewall IPSets in sync with DNS. Hostnames are resolved repeatedly (A and AAAA) until no new IP addresses show up, so that all IPs behind a round-robin or anycast DNS record are collected. Afterwards the IPSet is synchronized: IPs that are gone are deleted, new IPs are added. Only the difference is written — no flush-and-refill. One script, one config file. Python 3 standard library only — no pip installs needed. Requires `pvesh` (from `pve-manager`) and `dig` (from `dnsutils`) on the host. ## Installation Copy the script and the config to a Proxmox host (or any host with `pvesh` access to the cluster), e.g. to `/etc/pve`: ``` cp manage_ipset.py manage_ipset.conf /etc/pve/ chmod +x /etc/pve/manage_ipset.py ``` `manage_ipset.conf` is looked up next to the script unless `-c` is given. ## Usage ``` # sync every IPSet from the config ./manage_ipset.py # use a different config file ./manage_ipset.py -c /path/to/manage_ipset.conf # sync a single IPSet directly ./manage_ipset.py github github.com github.io ./manage_ipset.py docker-io registry-1.docker.io --round 30 ``` ### Cron ``` # every hour 0 * * * * /etc/pve/manage_ipset.py ``` ## Config (`manage_ipset.conf`) ``` [defaults] round=50 stale_after=10 # dns_server=1.1.1.1 # optional, default: system resolver # timeout=15 golang host=go.dev github host=github.com host=github.io round=10 stale_after=3 ``` - **`[defaults]`** section sets the global defaults. Optional. - An indented line `host=...` adds a host to the current IPSet. An IPSet may hold several hosts; their IPs are merged into one set. - Per-host options (optional): `round`, `stale_after`, `dns_server`, `timeout`. - Lines starting with `#` are comments. ### Options | Option | Meaning | Default | |---------------|------------------------------------------------------|---------| | `round` | max. DNS query rounds per host | 50 | | `stale_after` | rounds without a new IP before stopping early | 10 | | `dns_server` | resolver to query (`dig @server`) | system | | `timeout` | `dig` timeout in seconds | 15 | Hosts with many rotating IPs (big CDNs, `registry-1.docker.io`) benefit from a higher `round`; `round=1` behaves like a single query. ## How it works 1. `manage_ipset.py` reads `manage_ipset.conf` and groups hosts by IPSet. 2. For each host, `dig +short host A` / `AAAA` is run up to `round` times. As soon as `stale_after` consecutive rounds return no new IP, the loop stops. 3. The collected IPs are compared against the current IPSet content (`pvesh get /cluster/firewall/ipset/ --output-format json`). 4. Missing IPs are added (`pvesh create ... --cidr`), stale IPs are removed (`pvesh delete .../`). If a host in an IPSet does not resolve, that IPSet is left untouched — a single broken hostname cannot strip the IPs of its siblings. ## Notes - IPSet membership is matched by plain IP, ignoring the netmask that Proxmox stores per entry. - The script exits non-zero if any IPSet was skipped. - Legacy bash versions of both scripts are kept for reference in the `bash/` directory. ## License GPLv3 — see [LICENSE](LICENSE). Copyright (C) 2026 Paul Klumpp