works enough for now

This commit is contained in:
Benjamin Tayehanpour 2026-06-16 22:47:04 +02:00
commit c04a9a752a
12 changed files with 178 additions and 0 deletions

5
compiler/__init__.py Normal file
View file

@ -0,0 +1,5 @@
from .parser import load_services, load_globals
from .nat import build_nat_rules
from .firewall import build_firewall_rules
from .dns import build_dns
from .render import render_nat

6
compiler/dns.py Normal file
View file

@ -0,0 +1,6 @@
def build_dns(services, cfg):
return([
{"name": s.name, "ip": s.internal_ip}
for s in services
if "dns" in s.exposure
])

9
compiler/firewall.py Normal file
View file

@ -0,0 +1,9 @@
def build_firewall_rules(services, cfg):
rules = []
for s in services:
if "nat" in s.exposure:
rules.append({
"service": s.name,
"allow_port": s.public_port,
})
return(rules)

17
compiler/models.py Normal file
View file

@ -0,0 +1,17 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class Service:
name: str
internal_ip: str
internal_port: int
public_port: int
protocol: str
exposure: set[str]
@dataclass(frozen=True)
class GlobalConfig:
wan_interface: str
public_ip: str
lan_interface: str
internal_cidr: str

29
compiler/nat.py Normal file
View file

@ -0,0 +1,29 @@
import hashlib
def rule_id(service_name: str, kind: str) -> int:
h = hashlib.sha1(f"{service_name}:{kind}".encode()).hexdigest()
return(int(h[:4], 16)) # here's hoping
def build_nat_rules(services, cfg):
rules = []
for s in services:
rid_wan = rule_id(s.name, "dnat_wan")
rid_hairpin = rule_id(s.name, "dnat_hairpin")
rules.append({
"id": rid_wan,
"service": s,
"cfg": cfg,
"kind": "dnat_wan",
})
if "nat" in s.exposure:
rules.append({
"id": rid_hairpin,
"service": s,
"cfg": cfg,
"kind": "dnat_hairpin",
})
return(rules)

27
compiler/parser.py Normal file
View file

@ -0,0 +1,27 @@
import yaml
from .models import GlobalConfig, Service
def load_services(path: str) -> list[Service]:
raw = yaml.safe_load(open(path))["services"]
services = []
for name, s in raw.items():
ip, port = s["endpoint"].split(":")
services.append(Service(
name=name,
internal_ip=ip,
internal_port=int(port),
public_port=int(s["public_port"]),
protocol=s["protocol"],
exposure=set(s.get("exposure", [])),
))
return(services)
def load_globals(path: str) -> GlobalConfig:
g = yaml.safe_load(open(path))["wan"]
return GlobalConfig(
wan_interface=g["wan_interface"],
public_ip=g["public_ip"],
lan_interface=g["lan_interface"],
internal_cidr=g["internal_cidr"],
)

7
compiler/render.py Normal file
View file

@ -0,0 +1,7 @@
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("templates"))
def render_nat(rules):
tmpl = env.get_template("vyos_nat.j2")
return(tmpl.render(rules=rules))