# 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"]
--- /dev/null
+# 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")
# 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
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
# 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
# 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
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}.")
# 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
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
# -----------------------------
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
+# 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,
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 = {}
+++ /dev/null
-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()
--- /dev/null
+# 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
#!/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.")
# 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
break
# Create DB
-create_db()
+docker_create_db()
# Continue to CMD
if not args: