]> git.giorgioravera.it Git - network-manager.git/commitdiff
Merged entrypoint with app core
authorGiorgio Ravera <giorgio.ravera@gmail.com>
Mon, 5 Jan 2026 18:15:36 +0000 (19:15 +0100)
committerGiorgio Ravera <giorgio.ravera@gmail.com>
Mon, 5 Jan 2026 18:15:36 +0000 (19:15 +0100)
.gitignore [new file with mode: 0644]
Dockerfile
backend/config.py [new file with mode: 0644]
backend/db/db.py
backend/db/hosts.py
backend/db/users.py
backend/main.py
backend/models.py [deleted file]
backend/utils.py [new file with mode: 0644]
entrypoint.py

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..db2fc0d
--- /dev/null
@@ -0,0 +1 @@
+secrets
index efa18e0d1832d9bb31e83ab78cf20b389e309a81..c9e17758461e74cd8af1a454b06d7be6a67b1071 100644 (file)
@@ -39,19 +39,15 @@ WORKDIR /app
 # Copy application
 COPY --from=builder /app/backend backend
 COPY --from=builder /app/frontend frontend
-COPY --from=builder /app/entrypoint.py /usr/local/bin/entrypoint.py
+COPY --from=builder /app/entrypoint.py entrypoint.py
 
 # Ensure Python sees the installed packages 
 ENV PYTHONPATH="/usr/local/lib/python3.12/site-packages"
 
 ENV HTTP_PORT=8000
-ENV LOGIN_MAX_ATTEMPTS=5
-ENV LOGIN_WINDOW_SECONDS=600
-ENV DOMAIN=example.com
-ENV PUBLIC_IP=127.0.0.1
 
 # Expose the port dynamically
 EXPOSE ${HTTP_PORT}
 
-ENTRYPOINT ["/usr/local/bin/entrypoint.py"]
+ENTRYPOINT ["/app/entrypoint.py"]
 CMD ["python3", "-u", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]
diff --git a/backend/config.py b/backend/config.py
new file mode 100644 (file)
index 0000000..d8c4f36
--- /dev/null
@@ -0,0 +1,28 @@
+# backend/config.py
+
+# Import standard modules
+import os
+import secrets
+# Import local modules
+from backend.utils import load_hash
+
+# Database related settings
+DB_FILE = os.environ.get("DB_FILE", "/data/database.db")
+DB_RESET = os.environ.get("DB_RESET", "0") == "1"
+
+# Hosts related settings
+DOMAIN = os.environ.get("DOMAIN", "example.com")
+PUBLIC_IP = os.environ.get("PUBLIC_IP", "127.0.0.1")
+
+# Web server related settings
+SECRET_KEY = os.getenv("SESSION_SECRET", secrets.token_urlsafe(64))
+HTTP_PORT = os.getenv("HTTP_PORT", "8000")
+LOGIN_MAX_ATTEMPTS = int(os.getenv("LOGIN_MAX_ATTEMPTS", "5"))
+LOGIN_WINDOW_SECONDS = int(os.getenv("LOGIN_WINDOW_SECONDS", "600"))
+
+# User related settings
+ADMIN_USER = os.environ.get("ADMIN_USER", "admin")
+ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "admin")
+ADMIN_HASH = os.environ.get("ADMIN_HASH", "")
+if not ADMIN_HASH:
+  ADMIN_HASH = load_hash("ADMIN_HASH_FILE")
index f7336f369715d295fc9ee4e46dfda687e10ee8d0..ac5e392d4aac401f60ef89bc32ba2c90a2b03e37 100644 (file)
@@ -1,11 +1,17 @@
 # backend/db/db.py
 
+# Import standard modules
 import os
 import sqlite3
-
-DB_PATH = os.environ.get("DB_PATH", "/data/database.db")
+# Import local modules
+from backend.config import DB_FILE
 
 _connection = None
+_init_functions = []
+
+def register_init(func):
+    _init_functions.append(func)
+    return func
 
 # -----------------------------
 # Connect to the database
@@ -13,8 +19,8 @@ _connection = None
 def get_db():
     global _connection
     if _connection is None:
-        os.makedirs(os.path.dirname(DB_PATH) or ".", exist_ok=True)
-        _connection = sqlite3.connect(DB_PATH, check_same_thread=False)
+        os.makedirs(os.path.dirname(DB_FILE) or ".", exist_ok=True)
+        _connection = sqlite3.connect(DB_FILE, check_same_thread=False)
         _connection.row_factory = sqlite3.Row
         _connection.execute("PRAGMA foreign_keys = ON;")
     return _connection
@@ -23,6 +29,15 @@ def get_db():
 # Init Database
 # -----------------------------
 def init_db():
+    print(f"INFO:     Starting DB Initialization.")
+
     conn = get_db()
     cur = conn.cursor()
+
+    for func in _init_functions:
+        func(cur)
+
     conn.commit()
+    conn.close()
+
+    print(f"INFO:     DB Initialization Completed.")
\ No newline at end of file
index 44a3744b17dadb5956939f22754b6ccf32407346..9f0e98f0cf6204edea3e9bb0c43c59f4597f2da1 100644 (file)
@@ -1,8 +1,11 @@
 # backend/db/hosts.py
 
+# Import standard modules
 import ipaddress
 import os
+# Import local modules
 from backend.db.db import get_db
+from backend.db.db import register_init
 
 # -----------------------------
 # SELECT ALL HOSTS
@@ -70,3 +73,64 @@ def delete_host(host_id: int):
     conn.execute("DELETE FROM hosts WHERE id = ?", (host_id,))
     conn.commit()
     return True
+
+# -----------------------------
+# Initialize Hosts DB Table
+# -----------------------------
+@register_init
+def init_db_hosts_table(cur):
+    from backend.config import DOMAIN
+    from backend.config import PUBLIC_IP
+
+    # GLOBAL SETTINGS
+    cur.execute("""
+        CREATE TABLE settings (
+            key TEXT PRIMARY KEY,
+            value TEXT
+        );
+    """)
+    cur.execute("INSERT INTO settings (key, value) VALUES (?, ?)", ("domain", DOMAIN))
+    cur.execute("INSERT INTO settings (key, value) VALUES (?, ?)", ("external_ipv4", PUBLIC_IP))
+
+    # HOSTS
+    cur.execute("""
+        CREATE TABLE hosts (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            name TEXT NOT NULL UNIQUE,
+            ipv4 TEXT,
+            ipv6 TEXT,
+            mac TEXT,
+            note TEXT,
+            ssl_enabled INTEGER NOT NULL DEFAULT 0
+        );
+    """)
+    cur.execute("CREATE INDEX idx_hosts_name ON hosts(name);")
+
+    # ALIASES
+    cur.execute("""
+        CREATE TABLE aliases (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            host_id INTEGER NOT NULL,
+            alias TEXT NOT NULL,
+            note TEXT,
+            ssl_enabled INTEGER NOT NULL DEFAULT 0,
+            FOREIGN KEY (host_id) REFERENCES hosts(id)
+        );
+    """)
+    cur.execute("CREATE INDEX idx_aliases_host ON aliases(host_id);")
+
+    # TXT RECORDS
+    cur.execute("""
+        CREATE TABLE txt_records (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            name TEXT NOT NULL,
+            value TEXT NOT NULL,
+            note TEXT,
+            host_id INTEGER,
+            FOREIGN KEY (host_id) REFERENCES hosts(id)
+        );
+    """)
+    cur.execute("CREATE INDEX idx_txt_host ON txt_records(host_id);")
+
+    print(f"INFO:     - HOSTS DB: Database initialized successfully for {DOMAIN}.")
+    print(f"INFO:     - HOSTS DB: Public IP: {PUBLIC_IP}.")
index 0b4762a4fbf959ee38f8b767f557b278ab53c4c2..cc99512b06ded7195694fd3e819cd8b51c012348 100644 (file)
@@ -1,8 +1,20 @@
 # backend/db/users.py
 
+# Import standard modules
+import bcrypt
 import json
+import os
+# Import local modules
 from backend.db.db import get_db
-from bcrypt import checkpw
+from backend.db.db import register_init
+
+# ================================
+# Create hash password
+# ================================
+def hash_password(password: str) -> str:
+    salt = bcrypt.gensalt(rounds=12)
+    hashed = bcrypt.hashpw(password.encode(), salt)
+    return hashed.decode()
 
 # -----------------------------
 # Get User from DB by username
@@ -13,6 +25,55 @@ def get_user_by_username(username):
     cur.execute("SELECT * FROM users WHERE username = ?", (username,))
     return cur.fetchone()
 
+# -----------------------------
+# Create Users Table
+# -----------------------------
+@register_init
+def init_db_users_table(cur):
+    from backend.config import ADMIN_USER
+    from backend.config import ADMIN_PASSWORD
+    from backend.config import ADMIN_HASH
+
+    cur.execute("""
+        CREATE TABLE IF NOT EXISTS users (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            username TEXT UNIQUE NOT NULL,
+            password_hash TEXT NOT NULL,
+            email TEXT UNIQUE,
+            is_admin INTEGER NOT NULL DEFAULT 0,
+            modules TEXT, -- JSON: ["dns","dhcp","vpn"]
+            status TEXT NOT NULL DEFAULT 'active', -- active, disabled, locked
+            failed_attempts INTEGER NOT NULL DEFAULT 0,
+            last_failed_at INTEGER,
+            last_login_at INTEGER,
+            password_changed_at INTEGER,
+            notes TEXT,
+            created_at INTEGER NOT NULL,
+            updated_at INTEGER NOT NULL
+        );
+    """)
+    cur.execute("CREATE INDEX idx_users_username ON users(username);")
+    # Insert default admin user
+    if not ADMIN_HASH:
+        ADMIN_HASH = hash_password(ADMIN_PASSWORD)
+    else:
+        ADMIN_PASSWORD = "(hidden)"
+    cur.execute("""
+        INSERT INTO users (
+            username, password_hash, email, is_admin, modules, status,
+            created_at, updated_at, password_changed_at
+        ) VALUES (?, ?, ?, ?, ?, ?, strftime('%s','now'), strftime('%s','now'), strftime('%s','now'));
+    """, (
+        ADMIN_USER,
+        ADMIN_HASH,
+        "admin@example.com",
+        1,
+        '["dns","dhcp"]',
+        "active"
+    ))
+
+    print(f"INFO:     - USERS DB: Admin user: {ADMIN_USER} with password {ADMIN_PASSWORD} - {ADMIN_HASH}.")
+
 # -----------------------------
 # Create User
 # -----------------------------
@@ -46,8 +107,7 @@ def verify_login(username, password):
     if user["status"] != "active":
         return False
 
-    if not checkpw(password.encode(), user["password_hash"].encode()):
+    if not bcrypt.checkpw(password.encode(), user["password_hash"].encode()):
         return False
 
-    print(f"DEBUG: Checking password for user {user['username']} with status {user['status']}: {user['password_hash']}")
     return True
index 50ad5b0c61fa9ec38e8ef7edba9548b8818a6e79..4b3a9bc86b50073dfe6253dd97196422ff5e1a22 100644 (file)
@@ -1,14 +1,15 @@
+# backend/main.py
+
+# import standard modules
 from fastapi import FastAPI
 from fastapi import Request, Response, HTTPException
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import FileResponse, RedirectResponse
 from itsdangerous import TimestampSigner
-import secrets
 import os
 import ipaddress
 import time
-
-# Import local models
+# Import local modules
 from backend.db.hosts import (
     get_hosts,
     get_host,
@@ -16,16 +17,14 @@ from backend.db.hosts import (
     update_host,
     delete_host
 )
-
 from backend.db.users import (
     verify_login
 )
-
-# Read Variables
-SECRET_KEY = os.getenv("SESSION_SECRET", secrets.token_urlsafe(64))
-HTTP_PORT = os.getenv("HTTP_PORT", "8000")
-LOGIN_MAX_ATTEMPTS = int(os.getenv("LOGIN_MAX_ATTEMPTS", "5"))
-LOGIN_WINDOW_SECONDS = int(os.getenv("LOGIN_WINDOW_SECONDS", "600"))
+# Import config variables
+from backend.config import SECRET_KEY
+from backend.config import HTTP_PORT
+from backend.config import LOGIN_MAX_ATTEMPTS
+from backend.config import LOGIN_WINDOW_SECONDS
 
 # IP → lista timestamp tentativi 
 login_attempts = {}
diff --git a/backend/models.py b/backend/models.py
deleted file mode 100644 (file)
index 3b8107c..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-import sqlite3
-import os
-
-DB_PATH = os.environ.get("DB_PATH", "/app/database.db")
-
-def init_db():
-    conn = sqlite3.connect(DB_PATH)
-    conn.execute("""
-        CREATE TABLE IF NOT EXISTS hosts (
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            name TEXT NOT NULL UNIQUE,
-            ipv4 TEXT,
-            ipv6 TEXT,
-            mac TEXT,
-            note TEXT,
-            ssl_enabled INTEGER NOT NULL DEFAULT 0
-        )
-    """)
-    conn.commit()
-    conn.close()
diff --git a/backend/utils.py b/backend/utils.py
new file mode 100644 (file)
index 0000000..1551249
--- /dev/null
@@ -0,0 +1,14 @@
+# backend/db/utils.py
+
+# Import standard modules
+import os
+
+# -----------------------------
+# Load hash from file
+# -----------------------------
+def load_hash(hash_file: str):
+    path = os.environ.get(hash_file)
+    if path and os.path.exists(path):
+        with open(path, "r") as f:
+            return f.read().strip()
+    return None
index 806c9e0aa49b592f6890e7967ba58adde03ee782..1e2fd80a8b936ce20365779c7096f414cf48cf28 100755 (executable)
@@ -1,41 +1,26 @@
 #!/usr/local/bin/python3
 
-import bcrypt
+# Import standard modules
 import os
 import sys
-import sqlite3
-import subprocess
+# Import local modules
+from backend.db.db import init_db
+import backend.db.hosts
+import backend.db.users
 
 # ================================
 # Variables
 # ================================
-DB_FILE = os.environ.get("DB_PATH", "/data/database.db")
-DB_RESET = os.environ.get("DB_RESET", "0") == "1"
-DOMAIN = os.environ.get("DOMAIN", "example.com")
-PUBLIC_IP = os.environ.get("PUBLIC_IP", "127.0.0.1")
-ADMIN_USER = os.environ.get("ADMIN_USER", "admin")
-ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "admin")
-ADMIN_HASH = os.environ.get("ADMIN_HASH", "")
-
 IMAGE_NAME = "network-manager-distroless"
 IMAGE_VERSION = "1.0"
 
-import bcrypt
-
-# ================================
-# Create hash password
-# ================================
-def hash_password(password: str) -> str:
-    salt = bcrypt.gensalt(rounds=12)
-    hashed = bcrypt.hashpw(password.encode(), salt)
-    return hashed.decode()
+from backend.config import DB_FILE
+from backend.config import DB_RESET
 
 # ================================
 # Create DB if needed
 # ================================
-def create_db():
-    global DB_FILE, DB_RESET, DOMAIN, PUBLIC_IP, ADMIN_USER, ADMIN_PASSWORD, ADMIN_HASH
-
+def docker_create_db():
     # Reset database if requested
     if DB_RESET and os.path.exists(DB_FILE):
         print("INFO:     Removing existing database.")
@@ -51,107 +36,8 @@ def create_db():
     # Ensure directory exists
     os.makedirs(os.path.dirname(DB_FILE) or ".", exist_ok=True)
 
-    conn = sqlite3.connect(DB_FILE)
-    cur = conn.cursor()
-
-    # Enable foreign keys
-    cur.execute("PRAGMA foreign_keys = ON;")
-
-    # GLOBAL SETTINGS
-    cur.execute("""
-        CREATE TABLE settings (
-            key TEXT PRIMARY KEY,
-            value TEXT
-        );
-    """)
-    cur.execute("INSERT INTO settings (key, value) VALUES (?, ?)", ("domain", DOMAIN))
-    cur.execute("INSERT INTO settings (key, value) VALUES (?, ?)", ("external_ipv4", PUBLIC_IP))
-
-    # HOSTS
-    cur.execute("""
-        CREATE TABLE hosts (
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            name TEXT NOT NULL UNIQUE,
-            ipv4 TEXT,
-            ipv6 TEXT,
-            mac TEXT,
-            note TEXT,
-            ssl_enabled INTEGER NOT NULL DEFAULT 0
-        );
-    """)
-    cur.execute("CREATE INDEX idx_hosts_name ON hosts(name);")
-
-    # ALIASES
-    cur.execute("""
-        CREATE TABLE aliases (
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            host_id INTEGER NOT NULL,
-            alias TEXT NOT NULL,
-            note TEXT,
-            ssl_enabled INTEGER NOT NULL DEFAULT 0,
-            FOREIGN KEY (host_id) REFERENCES hosts(id)
-        );
-    """)
-    cur.execute("CREATE INDEX idx_aliases_host ON aliases(host_id);")
-
-    # TXT RECORDS
-    cur.execute("""
-        CREATE TABLE txt_records (
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            name TEXT NOT NULL,
-            value TEXT NOT NULL,
-            note TEXT,
-            host_id INTEGER,
-            FOREIGN KEY (host_id) REFERENCES hosts(id)
-        );
-    """)
-    cur.execute("CREATE INDEX idx_txt_host ON txt_records(host_id);")
-
-    # Users RECORDS
-    cur.execute("""
-        CREATE TABLE IF NOT EXISTS users (
-            id INTEGER PRIMARY KEY AUTOINCREMENT,
-            username TEXT UNIQUE NOT NULL,
-            password_hash TEXT NOT NULL,
-            email TEXT UNIQUE,
-            is_admin INTEGER NOT NULL DEFAULT 0,
-            modules TEXT, -- JSON: ["dns","dhcp","vpn"]
-            status TEXT NOT NULL DEFAULT 'active', -- active, disabled, locked
-            failed_attempts INTEGER NOT NULL DEFAULT 0,
-            last_failed_at INTEGER,
-            last_login_at INTEGER,
-            password_changed_at INTEGER,
-            notes TEXT,
-            created_at INTEGER NOT NULL,
-            updated_at INTEGER NOT NULL
-        );
-    """)
-    cur.execute("CREATE INDEX idx_users_username ON users(username);")
-    # Insert default admin user
-    if not ADMIN_HASH:
-        ADMIN_HASH = hash_password(ADMIN_PASSWORD)
-    else:
-        ADMIN_PASSWORD = "(hidden)"
-    cur.execute("""
-        INSERT INTO users (
-            username, password_hash, email, is_admin, modules, status,
-            created_at, updated_at, password_changed_at
-        ) VALUES (?, ?, ?, ?, ?, ?, strftime('%s','now'), strftime('%s','now'), strftime('%s','now'));
-    """, (
-        ADMIN_USER,
-        ADMIN_HASH,
-        "admin@example.com",
-        1,
-        '["dns","dhcp"]',
-        "active"
-    ))
-
-    conn.commit()
-    conn.close()
-
-    print(f"INFO:     Database initialized successfully for {DOMAIN}.")
-    print(f"INFO:     Admin user: {ADMIN_USER} with password {ADMIN_PASSWORD} - {ADMIN_HASH}.")
-    print(f"INFO:     Public IP: {PUBLIC_IP}.")
+    # Initialize all registered DB tables
+    init_db()
 
 # ================================
 # Entry Point
@@ -182,7 +68,7 @@ while i < len(args):
         break
 
 # Create DB
-create_db()
+docker_create_db()
 
 # Continue to CMD
 if not args: