From: Giorgio Ravera Date: Sat, 31 Jan 2026 12:05:09 +0000 (+0100) Subject: Added host saving in BIND9 zone format and JSON output X-Git-Url: http://git.giorgioravera.it/?a=commitdiff_plain;h=41a69b8b4c561d24c1b11379a14d6c6b92fb05b6;p=network-manager.git Added host saving in BIND9 zone format and JSON output --- diff --git a/README.md b/README.md index 31999b9..4d639e9 100644 --- a/README.md +++ b/README.md @@ -127,21 +127,25 @@ secrets: | Variable | Default | Description | |----------|---------|-------------| | `FRONTEND_DIR` | /app/frontend | Frontend directory | -| `DB_FILE` | /data/database.db | SQLite file | -| `DB_RESET` | false | Reset DB on every startup | -| `LOG_LEVEL` | info | Log level | -| `LOG_TO_FILE` | false | Enable file logging | -| `LOG_FILE` | /data/app.log | Application log file | -| `LOG_ACCESS_FILE` | /data/access.log | HTTP access log | -| `DOMAIN` | example.com | Public domain | -| `PUBLIC_IP` | 127.0.0.1 | Public IP | -| `HTTP_PORT` | 8000 | Internal HTTP port | -| `LOGIN_MAX_ATTEMPTS` | 5 | Login attempts | -| `LOGIN_WINDOW_SECONDS` | 600 | Attempt window | +| `DB_FILE` | /data/database.db | SQLite file | +| `DB_RESET` | false | Reset DB on every startup | +| `LOG_LEVEL` | info | Log level | +| `LOG_TO_FILE` | false | Enable file logging | +| `LOG_FILE` | /data/app.log | Application log file | +| `LOG_ACCESS_FILE` | /data/access.log | HTTP access log | +| `DOMAIN` | example.com | Public domain | +| `PUBLIC_IP` | 127.0.0.1 | Public IP | +| `HTTP_PORT` | 8000 | Internal HTTP port | +| `LOGIN_MAX_ATTEMPTS` | 5 | Login attempts | +| `LOGIN_WINDOW_SECONDS` | 600 | Attempt window | | `ADMIN_USER` | admin | Admin username | -| `ADMIN_PASSWORD` | admin | Admin password (development) | +| `ADMIN_PASSWORD` | admin | Admin password (development) | | `ADMIN_PASSWORD_HASH_FILE` | /run/secrets/admin_password_hash | Admin password hash | | `SESSION_SECRET` | (auto-generated) | Session secret | +| `DNS_CFG_PATH` | /dns/etc | Bind9 Configuration folder | +| `DNS_HOST_FILE` | {DOMAIN}/hosts.inc | BIND9 Hosts file | +| `DNS_ALIAS_FILE` | {DOMAIN}/alias.inc | BIND9 Alias file | +| `DNS_REVERSE_FILE` | reverse/hosts.inc | BIND9 Reverse Hosts file | --- diff --git a/backend/db/hosts.py b/backend/db/hosts.py index aacc09d..df68898 100644 --- a/backend/db/hosts.py +++ b/backend/db/hosts.py @@ -16,9 +16,11 @@ from log.log import get_logger # ----------------------------- def get_hosts(): conn = get_db() - cur = conn.execute("SELECT * FROM hosts ORDER BY name") - rows = cur.fetchall() - return [dict(r) for r in rows] + cur = conn.execute("SELECT * FROM hosts") + rows = [dict(r) for r in cur.fetchall()] + # Sort by IPv4 + rows.sort(key=lambda h: ipaddress.IPv4Address(h['ipv4'])) + return rows # ----------------------------- # SELECT SINGLE HOST diff --git a/backend/main.py b/backend/main.py index 6cc0047..bb0fcad 100644 --- a/backend/main.py +++ b/backend/main.py @@ -55,6 +55,10 @@ def print_welcome(): "Users: admin=%s | password=%s | hash=%s | hash_file=%s", settings.ADMIN_USER, safe_admin_pwd, safe_admin_hash, settings.ADMIN_PASSWORD_HASH_FILE ) + logger.info( + "DNS: path=%s | host file=%s | alias file=%s | reverse file=%s", + settings.DNS_CFG_PATH, settings.DNS_HOST_FILE, settings.DNS_ALIAS_FILE, settings.DNS_REVERSE_FILE + ) # ------------------------------------------------------------------------------ # Shutdown log diff --git a/backend/routes/dns.py b/backend/routes/dns.py index fb133a7..c81d675 100644 --- a/backend/routes/dns.py +++ b/backend/routes/dns.py @@ -2,12 +2,14 @@ # import standard modules from fastapi import APIRouter, Request, Response -from fastapi.responses import FileResponse, RedirectResponse +from fastapi.responses import FileResponse, JSONResponse, RedirectResponse import asyncio +import json import os import ipaddress import time - +# Import local modules +from backend.db.hosts import get_hosts # Import Settings from settings.settings import settings @@ -21,16 +23,54 @@ router = APIRouter() async def apt_dns_reload(request: Request): start_ns = time.monotonic_ns() - await asyncio.sleep(0.2) + # Inizializzazioni + error = False + message = None + code = None + status = None + + try: + # Get Hosts List + hosts = get_hosts() + + # Save DNS Configuration + path = settings.DNS_HOST_FILE + with open(path, "w", encoding="utf-8") as f: + for h in hosts: + line = f"{h.get('name')}\t\t IN\tA\t{h.get('ipv4')}\n" + f.write(line) + + path = settings.DNS_HOST_FILE + ".json" + with open(path, "w", encoding="utf-8") as f: + #json.dump(hosts, f, indent=4, ensure_ascii=False) + for h in hosts: + f.write(json.dumps(h, ensure_ascii=False) + "\n") + + except Exception as err: + error = True + message = str(err).strip() + + if error: + code = "DNS_RELOAD_ERROR" + # default del messaggio se vuoto o None + if not message: + message = "DNS reload error" + status = "failure" + #http_status = 500 + else: + code = "DNS_RELOAD_OK" + message = "DNS configuration reload successfully" + status = "success" + #http_status = 200 - end_ns = time.monotonic_ns() - took_ms = (end_ns - start_ns) + took_ms = (time.monotonic_ns() - start_ns) / 1_000_000 - return { - "code": "DNS_RELOAD_OK", - "status": "success", - "message": "DNS configuration reload successfully", + payload = { + "code": code, + "status": status, + "message": message, "details": { "took_ms": took_ms } } + return JSONResponse(content=payload) diff --git a/docker-compose.yaml b/docker-compose.yaml index 3c7a1bf..d2d923b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -9,6 +9,7 @@ services: - TZ=${DOCKER_TZ} volumes: - ${DOCKER_CFG_DIR}/network:/data + - ${DOCKER_CFG_DIR}/bind9:/dns networks: - proxy diff --git a/frontend/js/hosts.js b/frontend/js/hosts.js index 28d4ed4..e71daf1 100644 --- a/frontend/js/hosts.js +++ b/frontend/js/hosts.js @@ -432,7 +432,7 @@ async function handleReloadDNS() { const data = await res.json(); if(data.code !== "DNS_RELOAD_OK"){ - const err = new Error(`Error reloading DNS: ${data.code} ${data.message}`); + const err = new Error(`Error reloading DNS: ${data.message}`); err.status = data.code; throw err; } @@ -465,7 +465,7 @@ async function handleReloadDHCP() { const data = await res.json(); if(data.code !== "DHCP_RELOAD_OK"){ - const err = new Error(`Error reloading DHCP: ${data.code} ${data.message}`); + const err = new Error(`Error reloading DHCP: ${data.message}`); err.status = data.code; throw err; } diff --git a/settings/default.py b/settings/default.py index 1013cad..dc9e960 100644 --- a/settings/default.py +++ b/settings/default.py @@ -38,3 +38,11 @@ LOGIN_WINDOW_SECONDS = "600" ADMIN_USER = "admin" ADMIN_PASSWORD = "admin" ADMIN_PASSWORD_HASH_FILE = "/run/secrets/admin_password_hash" + +# --------------------------------------------------------- +# DNS +# --------------------------------------------------------- +DNS_CFG_PATH="/dns/etc" +DNS_HOST_FILE=f"{DOMAIN}/hosts.inc" +DNS_ALIAS_FILE=f"{DOMAIN}/alias.inc" +DNS_REVERSE_FILE="reverse/hosts.inc" diff --git a/settings/settings.py b/settings/settings.py index 76e6a93..351de22 100644 --- a/settings/settings.py +++ b/settings/settings.py @@ -84,6 +84,12 @@ class Settings(BaseModel): (os.getenv("ADMIN_PASSWORD_HASH") or _read_text_if_exists(os.getenv("ADMIN_PASSWORD_HASH_FILE", default.ADMIN_PASSWORD_HASH_FILE)) or None) )) + # DNS + DNS_CFG_PATH: str = Field(default_factory=lambda: os.getenv("DNS_CFG_PATH", default.DNS_CFG_PATH)) + DNS_HOST_FILE: str = Field(default_factory=lambda: os.getenv("DNS_HOST_FILE", default.DNS_HOST_FILE)) + DNS_ALIAS_FILE: str = Field(default_factory=lambda: os.getenv("DNS_ALIAS_FILE", default.DNS_ALIAS_FILE)) + DNS_REVERSE_FILE: str = Field(default_factory=lambda: os.getenv("DNS_REVERSE_FILE", default.DNS_REVERSE_FILE)) + def model_post_init(self, __context) -> None: if self.DEVEL: ts = datetime.datetime.now().strftime("%Y%m%d-%H%M") @@ -91,6 +97,14 @@ class Settings(BaseModel): else: object.__setattr__(self, "APP_VERSION", self.APP_VERSION) + # Update Files + if self.DOMAIN.lower() != default.DOMAIN.lower(): + self.DNS_HOST_FILE = self.DNS_HOST_FILE.replace(default.DOMAIN, self.DOMAIN) + self.DNS_ALIAS_FILE = self.DNS_ALIAS_FILE.replace(default.DOMAIN, self.DOMAIN) + self.DNS_REVERSE_FILE = self.DNS_REVERSE_FILE.replace(default.DOMAIN, self.DOMAIN) + self.DNS_HOST_FILE = self.DNS_CFG_PATH + "/" + self.DNS_HOST_FILE + self.DNS_ALIAS_FILE = self.DNS_CFG_PATH + "/" + self.DNS_ALIAS_FILE + self.DNS_REVERSE_FILE = self.DNS_CFG_PATH + "/" + self.DNS_REVERSE_FILE # --------------------------------------------------------- # Singleton # ---------------------------------------------------------