- DNSSEC
- Auto-generated files should contain a comment saying so
- Automated generation of Null MX
+- Bind: custom config
from nsconfig import Nsc
+from nsconfig.daemon.bind import NscDaemonBind
nsc = Nsc(
admin_email='admin@example.org',
origin_server='ns.example.org',
+ daemon=NscDaemonBind(),
)
for rev in ['10.1.0.0/16', '10.2.0.0/16', 'fd12:3456:789a::/48']:
from pathlib import Path
from texttable import Texttable
-from nsconfig.core import Nsc, ZoneType
+from nsconfig.core import Nsc, NscZonePrimary, NscZoneSecondary
def do_test(nsc: Nsc) -> None:
for z in nsc.get_zones():
print(f'Zone: {z.name}')
print(f'Type: {z.zone_type.name}')
- if z.zone_type == ZoneType.primary:
+ if isinstance(z, NscZonePrimary):
print(f'Old serial: {z.prev_state.serial}')
print(f'Old hash: {z.prev_state.hash}')
print(f'New serial: {z.state.serial}')
print(f'Dumping to: {out_file}')
with open(out_file, 'w') as f:
z.dump(file=f)
- else:
+ elif isinstance(z, NscZoneSecondary):
print(f'Primary: {z.primary_server}')
print()
+ if nsc.daemon:
+ conf_file = test_dir / nsc.daemon.config_path.name
+ print(f'Dumping daemon config to {conf_file}')
+ with open(conf_file, 'w') as f:
+ nsc.daemon.dump_config(file=f)
+
def do_status(nsc: Nsc) -> None:
table = Texttable(max_width=0)
table.set_deco(Texttable.HEADER)
for z in nsc.get_zones():
- if z.zone_type != ZoneType.primary:
+ if not isinstance(z, NscZonePrimary):
table.add_row([z.name, 'secondary', "", "", "", ""])
continue
- if z.state.serial == z.prev_state.serial:
- action = ""
- else:
+ if z.is_changed():
action = '*'
+ else:
+ action = ""
table.add_row([
z.name,
z.prev_state.serial,
def do_update(nsc: Nsc) -> None:
for z in nsc.get_zones():
- if z.zone_type == ZoneType.primary and z.state.serial != z.prev_state.serial:
+ if isinstance(z, NscZonePrimary) and z.is_changed():
print(f'Updating zone {z.name} (serial {z.state.serial})')
z.write_zone()
z.write_state()
from pathlib import Path
import socket
import sys
-from typing import Optional, Dict, List, Self, Tuple, DefaultDict, TextIO
+from typing import Optional, Dict, List, Self, Tuple, DefaultDict, TextIO, TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+ from nsconfig.daemon import NscDaemon
IPAddress = IPv4Address | IPv6Address
def write_state(self) -> None:
self.state.save(self.state_file)
+ def is_changed(self) -> bool:
+ return self.state.serial != self.prev_state.serial
+
class NscZoneSecondary(NscZone):
primary_server: IPAddress
state_dir: Path
zone_dir: Path
secondary_dir: Path
+ daemon: Optional['NscDaemon'] # Set by DaemonConfig class
- def __init__(self, directory: str = '.', **kwargs) -> None:
+ def __init__(self,
+ directory: str = '.',
+ daemon: Optional['NscDaemon'] = None,
+ **kwargs) -> None:
self.start_time = datetime.now()
self.zones = {}
self.default_zone_config = NscZoneConfig(**kwargs)
self.secondary_dir = self.root_dir / 'secondary'
self.secondary_dir.mkdir(parents=True, exist_ok=True)
+ self.daemon = daemon
+ if daemon is not None:
+ daemon.setup(self)
+
def add_zone(self,
name: Optional[str] = None,
reverse_for: str | IPNetwork | None = None,
--- /dev/null
+from pathlib import Path
+from typing import TextIO
+import sys
+
+from nsconfig.core import Nsc
+
+
+class NscDaemon:
+ nsc: Nsc
+ config_path: Path
+
+ def setup(self, nsc: Nsc) -> None:
+ self.nsc = nsc
+
+ def dump_config(self, file: TextIO = sys.stdout) -> None:
+ pass
--- /dev/null
+from pathlib import Path
+import sys
+from typing import TextIO
+
+from nsconfig.core import NscZonePrimary, NscZoneSecondary
+from nsconfig.daemon import NscDaemon
+
+
+class NscDaemonBind(NscDaemon):
+ config_path: Path = Path('named.conf.nsc')
+
+ def dump_config(self, file: TextIO = sys.stdout) -> None:
+ file.write('# Domains managed by NSC\n')
+ file.write('# This file was automatically generated by NSC, please do not edit manually.\n\n')
+ for z in self.nsc.get_zones():
+ file.write(f'zone "{z.name}" in {{\n') # broken editor: }}
+ if isinstance(z, NscZonePrimary):
+ file.write('\ttype master;\n')
+ file.write(f'\tfile "{z.zone_file}";\n')
+ elif isinstance(z, NscZoneSecondary):
+ file.write('\ttype slave;\n')
+ file.write(f'\tfile "zone/{z.secondary_file}";\n')
+ file.write(f'\tmasters {{ {z.primary_server}; }};\n')
+ else:
+ raise NotImplementedError()
+ file.write('}\n\n')