]> git.giorgioravera.it Git - network-manager.git/commitdiff
Added DNS reverse & DHCP Configuration & Backup
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Mon, 2 Feb 2026 20:48:17 +0000 (21:48 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Mon, 2 Feb 2026 20:48:17 +0000 (21:48 +0100)
README.md
TODO.md
backend/main.py
backend/routes/backup.py [new file with mode: 0644]
backend/routes/dhcp.py
backend/routes/dns.py
docker-compose.yaml
settings/default.py
settings/settings.py

index 4d639e9cb3dff6f0d274da52b97b34b865c81777..4a81e737ca932ac62868a9d9da64f7a5779625df 100644 (file)
--- a/README.md
+++ b/README.md
@@ -127,12 +127,13 @@ secrets:
 | Variable | Default | Description |
 |----------|---------|-------------|
 | `FRONTEND_DIR` | /app/frontend | Frontend directory |
-| `DB_FILE` | /data/database.db | SQLite file |
+| `DATA_PATH`| /data | Data Path for DB and Backups |
+| `DB_FILE` | 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 |
+| `LOG_FILE` | app.log | Application log file |
+| `LOG_ACCESS_FILE` | access.log | HTTP access log |
 | `DOMAIN` | example.com | Public domain |
 | `PUBLIC_IP` | 127.0.0.1 | Public IP |
 | `HTTP_PORT` | 8000 | Internal HTTP port |
@@ -146,6 +147,9 @@ secrets:
 | `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 |
+| `DHCP_CFG_PATH` | /dns/etc | KEA Configuration folder |
+| `DHCP4_HOST_FILE` | hosts-ipv4.json | KEA-DHCP4 Hosts file |
+| `DHCP6_HOST_FILE` | hosts-ipv6.json | KEA-DHCP6 Hosts file |
 
 ---
 
diff --git a/TODO.md b/TODO.md
index f6b8a928d50a33c72fb9b2d5cc77bb35231436bb..4aadeefedc8d3dc7684811df6e2e4fc8b5d57800 100644 (file)
--- a/TODO.md
+++ b/TODO.md
 - [ ] Perform **commit + push** on Git
 - [ ] Regenerate **from scratch**:
   - [ ] **BIND (DNS)** configuration
-  - [ ] **Kea (DHCP)** configuration
+    - [X] hosts configuration
+    - [ ] alias configuration
+    - [X] reverse configuration
+    - [ ] IPv6 configuration
+  - [X] **Kea (DHCP)** configuration
+    - [X] IPv4 configuration
+    - [X] IPv6 configuration
 - [ ] Reload services:
   - [ ] BIND
   - [ ] Kea
index bb0fcadb97e1943d5a3e0c08bacad163c95c7434..20f971c2a3f8063a20479a9ff26de4c9852380b6 100644 (file)
@@ -9,6 +9,7 @@ import logging
 import os
 # Import Routers
 from backend.routes.about import router as about_router
+from backend.routes.backup import router as backup_router
 from backend.routes.health import router as health_router
 from backend.routes.login import router as login_router
 from backend.routes.hosts import router as hosts_router
@@ -59,6 +60,10 @@ def print_welcome():
         "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
     )
+    logger.info(
+        "DHCP: path=%s | ipv4 host file=%s | ipv6 host file=%s",
+        settings.DHCP_CFG_PATH, settings.DHCP4_HOST_FILE, settings.DHCP6_HOST_FILE
+    )
 
 # ------------------------------------------------------------------------------
 # Shutdown log
@@ -98,6 +103,7 @@ app = FastAPI(
 # Routers
 # ------------------------------------------------------------------------------
 app.include_router(about_router)
+app.include_router(backup_router)
 app.include_router(health_router)
 app.include_router(login_router)
 app.include_router(hosts_router)
diff --git a/backend/routes/backup.py b/backend/routes/backup.py
new file mode 100644 (file)
index 0000000..e2d40b5
--- /dev/null
@@ -0,0 +1,72 @@
+# backend/routes/backup.py
+
+# import standard modules
+from fastapi import APIRouter, Request, Response
+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
+
+# Create Router
+router = APIRouter()
+
+# ---------------------------------------------------------
+# API ENDPOINTS
+# ---------------------------------------------------------
+@router.get("/api/backup")
+async def apt_dns_reload(request: Request):
+    start_ns = time.monotonic_ns()
+
+    # Inizializzazioni
+    error = False
+    message = None
+    code = None
+    status = None
+    dns_hosts = []
+    dns_reverse = []
+
+    try:
+        # Get Hosts List
+        hosts = get_hosts()
+
+        # Backup Hosts DB
+        path = settings.DATA_PATH + "/hosts.json"
+        with open(path, "w", encoding="utf-8") as f:
+            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 = "BACKUP_ERROR"
+        # default del messaggio se vuoto o None
+        if not message:
+            message = "BACKUP error"
+        status = "failure"
+        #http_status = 500
+    else:
+        code = "BACKUP_OK"
+        message = "BACKUP executed successfully"
+        status = "success"
+        #http_status = 200
+
+    took_ms = (time.monotonic_ns() - start_ns) / 1_000_000
+
+    payload = {
+        "code": code,
+        "status": status,
+        "message": message,
+        "details": {
+            "took_ms": took_ms
+        }
+    }
+    return JSONResponse(content=payload)
+    #return JSONResponse(content=payload, status_code=http_status)
index 37f0b5f553c99ca22f982d933455334a618767bc..e9c4c521bc662663ca1a76f2ca4fd0ae7d10f288 100644 (file)
@@ -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,69 @@ router = APIRouter()
 async def apt_dhcp_reload(request: Request):
     start_ns = time.monotonic_ns()
 
-    await asyncio.sleep(0.2)
+    # Inizializzazioni
+    error = False
+    message = None
+    code = None
+    status = None
+    kea4_hosts = []
+    kea6_hosts = []
+
+    try:
+        # Get Hosts List
+        hosts = get_hosts()
+
+        # Convert hosts into the kea structure
+        for h in hosts:
+            if h.get("ipv4") and h.get("mac"):
+                kea4_hosts.append({
+                    "hostname": h.get("name"),
+                    "hw-address": h.get("mac"),
+                    "ip-address": h.get("ipv4"),
+            })
+            if h.get("ipv6") and h.get("mac"):
+                kea6_hosts.append({
+                   "hostname": h.get("name"),
+                    "hw-address": h.get("mac"),
+                    "ip-address": h.get("ipv6"),
+            })
+
+        # Save DHCP4 Configuration
+        path = settings.DHCP4_HOST_FILE
+        with open(path, "w", encoding="utf-8") as f:
+            json.dump(kea4_hosts, f, indent=4, ensure_ascii=False)
+
+        # Save DHCP6 Configuration
+        path = settings.DHCP6_HOST_FILE
+        with open(path, "w", encoding="utf-8") as f:
+            json.dump(kea6_hosts, f, indent=4, ensure_ascii=False)
+
+    except Exception as err:
+        error = True
+        message = str(err).strip()
+
+    if error:
+        code = "DHCP_RELOAD_ERROR"
+        # default del messaggio se vuoto o None
+        if not message:
+            message = "DHCP reload error"
+        status = "failure"
+        #http_status = 500
+    else:
+        code = "DHCP_RELOAD_OK"
+        message = "DHCP 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": "DHCP_RELOAD_OK",
-        "status": "success",
-        "message": "DHCP configuration reload successfully",
+    payload = {
+        "code": code,
+        "status": status,
+        "message": message,
         "details": {
             "took_ms": took_ms
         }
     }
+    return JSONResponse(content=payload)
+    #return JSONResponse(content=payload, status_code=http_status)
index c81d67569c413f185b1305c9ba5c5edaa9f71e01..92bde01f92d5a5b142d5ff60d49648c4d84552e0 100644 (file)
@@ -33,18 +33,23 @@ async def apt_dns_reload(request: Request):
         # Get Hosts List
         hosts = get_hosts()
 
-        # Save DNS Configuration
+        # Save DNS Hosts 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"
+        # Save DNS Reverse Configuration
+        path = settings.DNS_REVERSE_FILE
         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")
+                ip = h.get('ipv4')
+                if ip:
+                    parts = ip.split(".")
+                    rev = f"{parts[-1]}.{parts[-2]}"
+                    line = f"{rev}\t\t IN PTR\t{h.get('name')}.{settings.DOMAIN}\n"
+                    f.write(line)
 
     except Exception as err:
         error = True
@@ -74,3 +79,4 @@ async def apt_dns_reload(request: Request):
         }
     }
     return JSONResponse(content=payload)
+    #return JSONResponse(content=payload, status_code=http_status)
index d2d923be8a05bd15802ebabf37b75e8a76ad9192..0234f4cfd584da5816135e2b11a0e3bff8849383 100644 (file)
@@ -10,6 +10,7 @@ services:
     volumes:
       - ${DOCKER_CFG_DIR}/network:/data
       - ${DOCKER_CFG_DIR}/bind9:/dns
+      - ${DOCKER_CFG_DIR}/kea:/dhcp
     networks:
       - proxy
 
index dc9e9603882a66c114be85d7ed310aeb028fda50..8bddf086e9bbc4de1c6c75eb597d9af7512bcfaa 100644 (file)
@@ -5,10 +5,15 @@
 # ---------------------------------------------------------
 FRONTEND_DIR = "/app/frontend"
 
+# ---------------------------------------------------------
+# Data Path (DB + Backup)
+# ---------------------------------------------------------
+DATA_PATH = "/data"
+
 # ---------------------------------------------------------
 # Database
 # ---------------------------------------------------------
-DB_FILE = "/data/database.db"
+DB_FILE = "database.db"
 DB_RESET = False
 
 # ---------------------------------------------------------
@@ -16,8 +21,8 @@ DB_RESET = False
 # ---------------------------------------------------------
 LOG_LEVEL = "INFO"
 LOG_TO_FILE = False
-LOG_FILE = "/data/app.log"
-LOG_ACCESS_FILE = "/data/access.log"
+LOG_FILE = "app.log"
+LOG_ACCESS_FILE = "access.log"
 
 # ---------------------------------------------------------
 # Host
@@ -46,3 +51,10 @@ 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"
+
+# ---------------------------------------------------------
+# DHCP
+# ---------------------------------------------------------
+DHCP_CFG_PATH="/dhcp/etc"
+DHCP4_HOST_FILE="hosts-ipv4.json"
+DHCP6_HOST_FILE="hosts-ipv6.json"
index 351de22dd86fd10fa402de272608fc4372a5c0ef..c964b2f6ada2dafcca5cd7c47904cb7e5eda40dd 100644 (file)
@@ -51,6 +51,9 @@ class Settings(BaseModel):
     BASEIMG_NAME: str = Field(default_factory=lambda: config.BASEIMG_NAME)
     BASEIMG_VERSION: str = Field(default_factory=lambda: config.BASEIMG_VERSION)
 
+    # DATA_PATH
+    DATA_PATH: str = Field(default_factory=lambda: os.getenv("DATA_PATH", default.DATA_PATH))
+
     # Frontend
     FRONTEND_DIR: str = Field(default_factory=lambda: os.getenv("FRONTEND_DIR", default.FRONTEND_DIR))
 
@@ -89,6 +92,10 @@ class Settings(BaseModel):
     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))
+    # DHCP
+    DHCP_CFG_PATH: str = Field(default_factory=lambda: os.getenv("DHCP_CFG_PATH", default.DHCP_CFG_PATH))
+    DHCP4_HOST_FILE: str = Field(default_factory=lambda: os.getenv("DHCP4_HOST_FILE", default.DHCP4_HOST_FILE))
+    DHCP6_HOST_FILE: str = Field(default_factory=lambda: os.getenv("DHCP6_HOST_FILE", default.DHCP6_HOST_FILE))
 
     def model_post_init(self, __context) -> None:
         if self.DEVEL:
@@ -97,7 +104,12 @@ class Settings(BaseModel):
         else:
             object.__setattr__(self, "APP_VERSION", self.APP_VERSION)
 
-        # Update Files
+        # Database
+        self.DB_FILE         = self.DATA_PATH + "/" + self.DB_FILE
+        self.LOG_FILE        = self.DATA_PATH + "/" + self.LOG_FILE
+        self.LOG_ACCESS_FILE = self.DATA_PATH + "/" + self.LOG_ACCESS_FILE
+
+        # Update DNS 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)
@@ -105,6 +117,11 @@ class Settings(BaseModel):
         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
+
+        # Update DHCP Files
+        self.DHCP4_HOST_FILE  = self.DHCP_CFG_PATH + "/" + self.DHCP4_HOST_FILE
+        self.DHCP6_HOST_FILE  = self.DHCP_CFG_PATH + "/" + self.DHCP6_HOST_FILE
+
 # ---------------------------------------------------------
 # Singleton
 # ---------------------------------------------------------