bc838dfcb6
Ansible Lint / lint (push) Successful in 7s
- New jellyfin Ansible role: Docker stack (Jellyfin, qBittorrent, Radarr, Sonarr, Prowlarr, FlareSolverr, Jellyseerr) with NVIDIA GPU passthrough - configure_services.yml automates download clients, root folders, Prowlarr indexers/apps, qBittorrent credential pre-seeding, and Jellyseerr setup - Fix TPB Cardigann season search: apibay.org returns 0 for season-level queries (e.g. the.boys.s05); patch strips season/ep from tv-search params and locks the definition file read-only to survive Prowlarr refreshes - Fix Pi-hole update_hosts.py regex: use re.DOTALL + count=1 to handle multi-line arrays without overwriting the DHCP section - Add phone WireGuard peer, devbox Traefik/DNS entries, TLS cert plumbing, firewall rule for Traefik IP to Proxmox web UI Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
51 lines
1.6 KiB
Python
51 lines
1.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Ensure Proxmox host firewall rules exist via pvesh. Idempotent."""
|
|
import json
|
|
import subprocess
|
|
|
|
NODE = subprocess.run(['hostname'], capture_output=True, text=True).stdout.strip()
|
|
|
|
DESIRED = [
|
|
{'proto': 'tcp', 'source': '192.168.1.0/24', 'dport': '2222'},
|
|
{'proto': 'tcp', 'source': '10.10.10.0/24', 'dport': '2222'},
|
|
{'proto': 'tcp', 'source': '192.168.1.0/24', 'dport': '8006'},
|
|
{'proto': 'tcp', 'source': '10.10.10.0/24', 'dport': '8006'},
|
|
{'proto': 'tcp', 'source': '10.10.1.3', 'dport': '8006'},
|
|
{'proto': 'tcp', 'source': '192.168.1.0/24', 'dport': '3128'},
|
|
{'proto': 'udp', 'source': '10.10.0.0/16', 'dport': '53'},
|
|
{'proto': 'tcp', 'source': '10.10.0.0/16', 'dport': '53'},
|
|
{'proto': 'udp', 'dport': '51820'},
|
|
{'proto': 'icmp'},
|
|
]
|
|
|
|
|
|
def pvesh(*args):
|
|
return subprocess.run(['pvesh', *args], capture_output=True, text=True)
|
|
|
|
|
|
def get_rules():
|
|
r = pvesh('get', f'/nodes/{NODE}/firewall/rules', '--output-format', 'json')
|
|
try:
|
|
return json.loads(r.stdout)
|
|
except json.JSONDecodeError:
|
|
return []
|
|
|
|
|
|
def matches(current, desired):
|
|
return all(str(current.get(k, '')) == str(v) for k, v in desired.items())
|
|
|
|
|
|
endpoint = f'/nodes/{NODE}/firewall/rules'
|
|
current = get_rules()
|
|
changed = False
|
|
|
|
for rule in DESIRED:
|
|
if not any(matches(c, rule) for c in current):
|
|
args = ['create', endpoint, '--action', 'ACCEPT', '--type', 'in', '--enable', '1']
|
|
for k, v in rule.items():
|
|
args += [f'--{k}', str(v)]
|
|
pvesh(*args)
|
|
changed = True
|
|
|
|
print('changed' if changed else 'ok')
|