Add Proxmox host role, WireGuard VPN, and public Gitea via Traefik HTTPS
Ansible Lint / lint (push) Successful in 4s
Ansible Lint / lint (push) Successful in 4s
Proxmox host role: - Admin user setup, SSH hardening (port 2222, no root login) - claude-code SFTP chroot user - Unattended upgrades, DuckDNS dynamic DNS updater (vault-encrypted token) - WireGuard VPN server (10.10.10.0/24) with Fedora client registered - Proxmox firewall management via pvesh API (idempotent Python script) Public Gitea exposure: - Traefik: add HTTPS entrypoint, Let's Encrypt ACME (HTTP-01), StripPrefix middleware for /git subpath, HTTP→HTTPS redirect - Gitea ROOT_URL updated to https://adyrem.duckdns.org/git/ - Pi-hole split DNS: adyrem.duckdns.org → 10.10.1.3 for internal hairpin bypass SSH config fixes: - StrictHostKeyChecking no → accept-new across all hosts - Proxmox jump host: port 2222, user adyrem (root login disabled) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
#!/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': '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')
|
||||
Reference in New Issue
Block a user