From: Giorgio Ravera Date: Wed, 11 Mar 2026 18:38:38 +0000 (+0100) Subject: migrated to alpine docker image X-Git-Url: http://git.giorgioravera.it/?a=commitdiff_plain;ds=inline;p=network-manager.git migrated to alpine docker image --- diff --git a/Dockerfile b/Dockerfile index c46c04c..db0c261 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,52 +1,45 @@ -# ---------- STAGE 1: BUILD ---------- -FROM python:3.12-slim AS builder +# ---------- STAGE 1: Alpine Build ---------- +FROM python:3.12-alpine AS builder # Install build dependencies -RUN apt-get update && \ - apt-get install -y --no-install-recommends sqlite3 && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* +RUN apk add --no-cache build-base libffi-dev openssl-dev +WORKDIR /app + +# Copy dependency list COPY requirements.txt . -RUN pip install --prefix=/install -r requirements.txt -WORKDIR /app +# Build wheels to avoid building in final image +RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt -# Copy backend, frontend, entrypoint +# Copy full application COPY backend backend COPY frontend frontend -COPY entrypoint.py entrypoint.py COPY log log COPY settings settings -RUN chmod 755 entrypoint.py -# ---------- STAGE 2: DISTROLESS ---------- -FROM gcr.io/distroless/base-debian13 +# ---------- STAGE 2: Alpine Runtime ---------- +FROM python:3.12-alpine -# Copy Python runtime from builder -COPY --from=builder /usr/local /usr/local +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + LANG=C.UTF-8 -# Copy libs -COPY --from=builder /lib/x86_64-linux-gnu/libsqlite3.so.0 /lib/x86_64-linux-gnu/ -COPY --from=builder /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/ -COPY --from=builder /lib/x86_64-linux-gnu/libbz2.so.1.0 /lib/x86_64-linux-gnu/ -COPY --from=builder /lib/x86_64-linux-gnu/liblzma.so.5 /lib/x86_64-linux-gnu/ -COPY --from=builder /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib/x86_64-linux-gnu/ - -# Copy installed Python packages -COPY --from=builder /install /usr/local +# librerie runtime +RUN apk add --no-cache libffi openssl sqlite-libs WORKDIR /app -# Copy application -COPY --from=builder /app/backend backend -COPY --from=builder /app/frontend frontend -COPY --from=builder /app/entrypoint.py entrypoint.py -COPY --from=builder /app/log log -COPY --from=builder /app/settings settings +# Copy application and deps +COPY --from=builder /app /app +COPY --from=builder /wheels /wheels + +# Install dependencies inside distroless environment +RUN pip install --no-cache-dir --no-compile /wheels/* && \ + rm -rf /wheels && \ + find /usr/local/lib/python3.12/site-packages -regex '.*\(tests\|test\|docs\|examples\).*' -type d -prune -exec rm -rf {} + && \ + rm -rf /root/.cache -# Ensure Python sees the installed packages -ENV PYTHONPATH="/usr/local/lib/python3.12/site-packages" +RUN find /usr/local/lib/python3.12 -name '__pycache__' -type d -exec rm -rf {} + -ENTRYPOINT ["/app/entrypoint.py"] -CMD ["python3", "-u", "-m", "backend.main"] +ENTRYPOINT ["python", "-u", "-m", "backend.main"] diff --git a/backend/bootstrap.py b/backend/bootstrap.py new file mode 100644 index 0000000..ca3261e --- /dev/null +++ b/backend/bootstrap.py @@ -0,0 +1,114 @@ +# backend/bootstrap.py + +# Import standard modules +import logging +import os +# Import backend modules +from backend.db.db import init_db +import backend.db.users +import backend.db.hosts +import backend.db.aliases +# Import Settings & Logging +from settings.settings import settings +from log.log import setup_logging, get_logger + +# ------------------------------------------------------------------------------ +# Welcome log +# ------------------------------------------------------------------------------ +def print_welcome(logger): + masked_secret = "****" if settings.SECRET_KEY else "undefined" + masked_admin_pwd = "****" if settings.ADMIN_PASSWORD else "undefined" + masked_admin_hash = "****" if settings.ADMIN_PASSWORD_HASH else "undefined" + + logger.info( + "%s starting | app_version=%s", + settings.APP_NAME, settings.APP_VERSION + ) + logger.info( + "App settings: frontend=%s | host=%s | port=%d | secret=%s", + settings.FRONTEND_DIR, settings.HTTP_HOST, settings.HTTP_PORT, masked_secret + ) + logger.info( + "Database: file=%s | reset=%s", + settings.DB_FILE, settings.DB_RESET + ) + logger.info( + "Log: level=%s, to_file=%s, file=%s", + settings.LOG_LEVEL, settings.LOG_TO_FILE, settings.LOG_FILE + ) + logger.info( + "Users: admin=%s | password=%s | hash=%s | hash_file=%s", + settings.ADMIN_USER, masked_admin_pwd, masked_admin_hash, settings.ADMIN_PASSWORD_HASH_FILE + ) + logger.info( + "DNS: host file=%s | alias file=%s | reverse file=%s", + settings.DNS_HOST_FILE, settings.DNS_ALIAS_FILE, settings.DNS_REVERSE_FILE + ) + logger.info( + "DHCP: ipv4 host file=%s | ipv4 leases file=%s | ipv6 host file=%s | ipv6 leases file=%s", + settings.DHCP4_HOST_FILE, settings.DHCP4_LEASES_FILE, settings.DHCP6_HOST_FILE, settings.DHCP6_LEASES_FILE + ) + +# ------------------------------------------------------------------------------ +# Shutdown log +# ------------------------------------------------------------------------------ +def print_goodbye(logger): + logger.info( + "Application %s shutting down | app_version=%s", + settings.APP_NAME, settings.APP_VERSION + ) + +# ================================ +# Create DB if needed +# ================================ +def docker_create_db(logger): + # Reset database if requested + if settings.DB_RESET and os.path.exists(settings.DB_FILE): + logger.info("Removing existing database: %s", settings.DB_FILE) + os.remove(settings.DB_FILE) + + # Skip creation if DB already exists + if os.path.exists(settings.DB_FILE): + logger.info("Database already exists. Nothing to do.") + return + + logger.info("Creating database: %s", settings.DB_FILE) + + # Ensure directory exists + os.makedirs(os.path.dirname(settings.DB_FILE) or ".", exist_ok=True) + + # Initialize DB tables + init_db() + +# ------------------------------------------------------------------------------ +# Bootstrap: setup logging, print welcome, create DB, etc. +# ------------------------------------------------------------------------------ +def bootstrap(): + + # Log Setup + setup_logging(level=settings.LOG_LEVEL, to_file=settings.LOG_TO_FILE, log_file=settings.LOG_FILE, log_access_file=settings.LOG_ACCESS_FILE) + logger = get_logger(__name__) + + print_welcome(logger) + + # Create or update database + docker_create_db(logger) + + #os.makedirs(DATA_DIR, exist_ok=True) + #os.makedirs(BIND_DIR, exist_ok=True) + #os.makedirs(KEA_DIR, exist_ok=True) + + #if not os.path.exists(DB_PATH): + # conn = sqlite3.connect(DB_PATH) + # # eventuale executescript(schema) qui + # conn.close() + + #named_conf = os.path.join(BIND_DIR, "named.conf") + #if not os.path.exists(named_conf): + # with open(named_conf, "w") as f: + # f.write("// generated by network-manager\n") + + #kea_conf = os.path.join(KEA_DIR, "kea-dhcp4.conf") + #if not os.path.exists(kea_conf): + # with open(kea_conf, "w") as f: + # f.write("{\n // generated by network-manager\n}\n") diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..67d407a --- /dev/null +++ b/backend/main.py @@ -0,0 +1,25 @@ +# backend/main.py + +# Import standard modules +import os +# Import backend modules +from backend.bootstrap import bootstrap +from backend.app import create_app +from backend.server import run_server + +# ------------------------------------------------------------------------------ +# Main: entry point of the application +# ------------------------------------------------------------------------------ +def main(): + + # 1) System Initialization (Settings, Logging, DB, etc.) + bootstrap() + + # 2) Costruzione app FastAPI + app = create_app() + + # 4) Uvicorn Start + run_server(app) + +if __name__ == "__main__": + main() diff --git a/backend/server.py b/backend/server.py new file mode 100644 index 0000000..3b73281 --- /dev/null +++ b/backend/server.py @@ -0,0 +1,39 @@ +# backend/server.py + +# import standard modules +import uvicorn + +# Import Settings & Logging +from settings.settings import settings +from log.log import get_logger + +# Logger initialization +logger = get_logger(__name__) + +# ------------------------------------------------------------------------------ +# Starting the server with Uvicorn +# ------------------------------------------------------------------------------ +def run_server(app): + + # Uvicorn config da settings with fallback + host=(settings.HTTP_HOST or "0.0.0.0") + port=int(settings.HTTP_PORT or 8000) + log_level=(settings.LOG_LEVEL or "info").lower() + workers = 1 # GRGR in prod valuta gunicorn+uvicorn workers + #reload = os.getenv("UVICORN_RELOAD", "false").lower() == "true" + reload = bool(getattr(settings, "DEV_RELOAD", False)) + + logger.info(f"Server running on http://{host}:{port} (reload={reload}, log_level={log_level})") + + uvicorn.run( + app, + host=host, + port=port, + proxy_headers=True, + forwarded_allow_ips="*", + log_level=log_level, + #access_log=True, + log_config=None, + workers=(1 if reload else workers), + reload=reload, + ) diff --git a/entrypoint.py b/entrypoint.py deleted file mode 100755 index 4a99c65..0000000 --- a/entrypoint.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/local/bin/python3 - -# Import standard modules -import logging -import os -import sys -import argparse -# Import local modules -from backend.db.db import init_db -import backend.db.users -import backend.db.hosts -import backend.db.aliases -# Import Settings -from settings.settings import settings -# Import Log -from log.log import setup_logging, get_logger - -# ================================ -# Parse CLI arguments -# ================================ -def parse_args(): - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--reset", action="store_true") - parser.add_argument("--domain") - parser.add_argument("--external-name") - parser.add_argument("cmd", nargs=argparse.REMAINDER) - return parser.parse_args() - -# ================================ -# Create DB if needed -# ================================ -def docker_create_db(logger): - # Reset database if requested - if settings.DB_RESET and os.path.exists(settings.DB_FILE): - logger.info("Removing existing database: %s", settings.DB_FILE) - os.remove(settings.DB_FILE) - - # Skip creation if DB already exists - if os.path.exists(settings.DB_FILE): - logger.info("Database already exists. Nothing to do.") - return - - logger.info("Creating database: %s", settings.DB_FILE) - - # Ensure directory exists - os.makedirs(os.path.dirname(settings.DB_FILE) or ".", exist_ok=True) - - # Initialize DB tables - init_db() - -# ================================ -# Entry Point -# ================================ -def main(): - # Enable logging - setup_logging() - logger = get_logger("baseimg") - - # Log startup docker image - logger.info("Starting docker image %s version %s", settings.BASEIMG_NAME, settings.BASEIMG_VERSION) - - # Parse arguments - args = parse_args() - - # Apply arguments into settings - if args.reset: - settings.DB_RESET = True - if args.domain: - settings.DOMAIN = args.domain - if args.external_name: - settings.EXTERNAL_NAME = args.EXTERNAL_NAME - - # Create or update database - docker_create_db(logger) - - # If no command provided -> error - if not args.cmd: - logger.error("No command provided. Exiting.") - sys.exit(1) - - cmd = args.cmd[0] - rest = args.cmd[1:] - - logger.info("Docker image initialization completed — executing: %s %s", cmd, " ".join(rest)) - - try: - os.execvp(cmd, [cmd, *rest]) - except FileNotFoundError: - logger.critical("Command not found: %s", cmd) - sys.exit(1) - -if __name__ == "__main__": - main()