diff --git a/.gitignore b/.gitignore index 72d9f011..57fa924d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ __pycache__/ *.pyc .env .DS_Store +aInventory-PROD* +aInventory-PROD*.zip diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 00000000..478f2dca --- /dev/null +++ b/Caddyfile @@ -0,0 +1,12 @@ +# TFM aInventory - Caddy Self-Signed Internal Proxy +# This replaces the need for `local-ssl-proxy` in Node. + +:3003 { + tls internal + reverse_proxy frontend:3000 +} + +:3002 { + tls internal + reverse_proxy backend:8000 +} diff --git a/VERSION.json b/VERSION.json index 870adf02..c0cd6242 100644 --- a/VERSION.json +++ b/VERSION.json @@ -1,16 +1,15 @@ { - "version": "1.2.9", + "version": "1.3.0", "last_build": "2026-04-11-1235", - "commit": "3b2f1", + "commit": "PENDING", "changelog": [ + "v1.3.0: Dockerization: Dual-mode architecture, standalone Next.js, Python logger, and prod export script", "v1.2.9: Automatic IDE Entry Points (GEMINI.md, CLAUDE.md proxies)", "v1.2.8: Absolute Documentation Consolidation (Unified ARCHITECTURE and AI_RULES)", "v1.2.7: Documentation Refactor: Created TECH_STACK.md for SSOT and removed absolute paths", "v1.2.6: Hotfix - Added missing icon imports (Layers, ChevronDown)", "v1.2.5: Synchronized icons across UI (Layers for Categories, Package for Item Types)", "v1.2.4: Offline LDAP auth support and UI affordance improvements (Dropdown chevrons, soft password dots)", - "v1.2.3: System-wide UI consistency (standardized font sizes, removed uppercase)", - "v1.2.2: UI Readability refactor (Removed uppercase/tracking, increased font sizes)", - "v1.2.1: Git path persistence, master/dev/vX branching, and UI case-sensitivity fix" + "v1.2.3: System-wide UI consistency (standardized font sizes, removed uppercase)" ] } diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..f8ec6d7c --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,35 @@ +FROM python:3.12-slim + +# Install system dependencies required for python-ldap (needed by backend) +RUN apt-get update && apt-get install -y \ + build-essential \ + libldap2-dev \ + libsasl2-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy requirements and install +COPY backend/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Create non-root user +RUN adduser --system --group appuser + +# Copy application files +COPY backend ./backend + +# We define the data dir explicitly for Docker +ENV DATA_DIR="/app/data" +ENV LOGS_DIR="/app/logs" + +# Ensure the appuser can write to data and logs if we pre-create them, +# although Docker volumes will handle ownership context. +RUN mkdir -p /app/data /app/logs && chown -R appuser:appuser /app + +USER appuser + +EXPOSE 8000 + +# Start Uvicorn pointing to the backend module +CMD ["python", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/database.py b/backend/database.py index 9955fccf..a5d3929d 100644 --- a/backend/database.py +++ b/backend/database.py @@ -4,9 +4,11 @@ import os # Get absolute path for the backend directory BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -DATA_DIR = os.path.join(BASE_DIR, "data") -# Create data directory if it doesn't exist in the backend folder +# Use DATA_DIR from environment if running in Docker, otherwise fallback to local 'data' folder +DATA_DIR = os.environ.get("DATA_DIR", os.path.join(BASE_DIR, "data")) + +# Create data directory if it doesn't exist os.makedirs(DATA_DIR, exist_ok=True) # Handle absolute path for SQLite (needs 4 slashes on Unix) diff --git a/backend/logger.py b/backend/logger.py new file mode 100644 index 00000000..95860e60 --- /dev/null +++ b/backend/logger.py @@ -0,0 +1,51 @@ +import logging +import os +from logging.handlers import RotatingFileHandler + +# Define paths +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +# Fallback to local 'logs' dir if not overridden in Docker +LOGS_DIR = os.environ.get("LOGS_DIR", os.path.join(BASE_DIR, "logs")) + +# Ensure directory exists +os.makedirs(LOGS_DIR, exist_ok=True) + +LOG_FILE_PATH = os.path.join(LOGS_DIR, "backend.log") + +def setup_logger(): + logger = logging.getLogger("ainventory") + logger.setLevel(logging.INFO) + + # Avoid duplicate handlers if setup multiple times + if logger.handlers: + return logger + + formatter = logging.Formatter( + "[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s", + datefmt="%Y-%m-%d %H:%M:%S" + ) + + # Console Handler + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + + # File Handler (10MB max, keep 5 backups) + file_handler = RotatingFileHandler( + LOG_FILE_PATH, maxBytes=10*1024*1024, backupCount=5 + ) + file_handler.setFormatter(formatter) + + # Attach handlers + logger.addHandler(console_handler) + logger.addHandler(file_handler) + + # Special redirect for uvicorn logs to also go to file + uvicorn_logger = logging.getLogger("uvicorn") + uvicorn_logger.addHandler(file_handler) + + uvicorn_access_logger = logging.getLogger("uvicorn.access") + uvicorn_access_logger.addHandler(file_handler) + + return logger + +log = setup_logger() diff --git a/backend/logs/backend.log b/backend/logs/backend.log new file mode 100644 index 00000000..89d1d01d --- /dev/null +++ b/backend/logs/backend.log @@ -0,0 +1,174 @@ +[2026-04-11 12:25:22] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:25:22] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:25:22] [INFO] [uvicorn.error] Started server process [87758] +[2026-04-11 12:25:22] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:25:22] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:25:24] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:25:54] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:26:24] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:26:54] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:27:24] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:27:39] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:27:39] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:27:39] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:27:39] [INFO] [uvicorn.error] Finished server process [87758] +[2026-04-11 12:27:40] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:27:40] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:27:40] [INFO] [uvicorn.error] Started server process [87911] +[2026-04-11 12:27:40] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:27:40] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:27:41] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:27:41] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:27:41] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:27:41] [INFO] [uvicorn.error] Finished server process [87911] +[2026-04-11 12:27:42] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:27:42] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:27:42] [INFO] [uvicorn.error] Started server process [87914] +[2026-04-11 12:27:42] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:27:42] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:27:43] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:27:43] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:27:43] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:27:43] [INFO] [uvicorn.error] Finished server process [87914] +[2026-04-11 12:27:44] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:27:44] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:27:44] [INFO] [uvicorn.error] Started server process [87922] +[2026-04-11 12:27:44] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:27:44] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:27:46] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:27:46] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:27:46] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:27:46] [INFO] [uvicorn.error] Finished server process [87922] +[2026-04-11 12:27:46] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:27:46] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:27:46] [INFO] [uvicorn.error] Started server process [87926] +[2026-04-11 12:27:46] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:27:46] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:27:52] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:27:52] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:27:52] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:27:52] [INFO] [uvicorn.error] Finished server process [87926] +[2026-04-11 12:27:52] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:27:52] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:27:52] [INFO] [uvicorn.error] Started server process [87933] +[2026-04-11 12:27:52] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:27:52] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:27:54] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:28:02] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:28:02] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:28:02] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:28:02] [INFO] [uvicorn.error] Finished server process [87933] +[2026-04-11 12:28:03] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:28:03] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:28:03] [INFO] [uvicorn.error] Started server process [87948] +[2026-04-11 12:28:03] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:28:03] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:28:04] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:28:04] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:28:04] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:28:04] [INFO] [uvicorn.error] Finished server process [87948] +[2026-04-11 12:28:05] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:28:05] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:28:05] [INFO] [uvicorn.error] Started server process [87957] +[2026-04-11 12:28:05] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:28:05] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:28:06] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:28:07] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:28:07] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:28:07] [INFO] [uvicorn.error] Finished server process [87957] +[2026-04-11 12:28:07] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:28:07] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:28:07] [INFO] [uvicorn.error] Started server process [87958] +[2026-04-11 12:28:07] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:28:07] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:28:09] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:28:09] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:28:09] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:28:09] [INFO] [uvicorn.error] Finished server process [87958] +[2026-04-11 12:28:09] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:28:09] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:28:09] [INFO] [uvicorn.error] Started server process [87960] +[2026-04-11 12:28:09] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:28:09] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:28:11] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:28:11] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:28:11] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:28:11] [INFO] [uvicorn.error] Finished server process [87960] +[2026-04-11 12:28:12] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:28:12] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:28:12] [INFO] [uvicorn.error] Started server process [87968] +[2026-04-11 12:28:12] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:28:12] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:28:13] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:28:13] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:28:13] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:28:13] [INFO] [uvicorn.error] Finished server process [87968] +[2026-04-11 12:28:14] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:28:14] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:28:14] [INFO] [uvicorn.error] Started server process [87971] +[2026-04-11 12:28:14] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:28:14] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:28:20] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:28:20] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:28:20] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:28:20] [INFO] [uvicorn.error] Finished server process [87971] +[2026-04-11 12:28:20] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:28:20] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:28:20] [INFO] [uvicorn.error] Started server process [87989] +[2026-04-11 12:28:20] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:28:20] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:28:24] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:28:36] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:28:36] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:28:36] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:28:36] [INFO] [uvicorn.error] Finished server process [87989] +[2026-04-11 12:28:37] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:28:37] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:28:37] [INFO] [uvicorn.error] Started server process [88059] +[2026-04-11 12:28:37] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:28:37] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:28:37] [WARNING] [uvicorn.error] Invalid HTTP request received. +[2026-04-11 12:28:37] [INFO] [uvicorn.access] ::ffff:127.0.0.1:0 - "POST /exa.language_server_pb.LanguageServerService/GetUnleashData HTTP/1.1" 404 +[2026-04-11 12:28:54] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:29:24] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:29:54] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:30:24] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:30:54] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:31:24] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 +[2026-04-11 12:31:30] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:31:30] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:31:30] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:31:30] [INFO] [uvicorn.error] Finished server process [88059] +[2026-04-11 12:31:31] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:31:31] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Started server process [88476] +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Finished server process [88476] +[2026-04-11 12:31:31] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:31:31] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Started server process [88477] +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:31:31] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:31:32] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:31:32] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:31:32] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:31:32] [INFO] [uvicorn.error] Finished server process [88477] +[2026-04-11 12:31:33] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:31:33] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:31:33] [INFO] [uvicorn.error] Started server process [88478] +[2026-04-11 12:31:33] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:31:33] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:31:34] [INFO] [uvicorn.error] Shutting down +[2026-04-11 12:31:34] [INFO] [uvicorn.error] Waiting for application shutdown. +[2026-04-11 12:31:34] [INFO] [uvicorn.error] Application shutdown complete. +[2026-04-11 12:31:34] [INFO] [uvicorn.error] Finished server process [88478] +[2026-04-11 12:31:35] [INFO] [ainventory] Database tables verified. +[2026-04-11 12:31:35] [INFO] [ainventory] TFM aInventory API process started. +[2026-04-11 12:31:35] [INFO] [uvicorn.error] Started server process [88490] +[2026-04-11 12:31:35] [INFO] [uvicorn.error] Waiting for application startup. +[2026-04-11 12:31:35] [INFO] [uvicorn.error] Application startup complete. +[2026-04-11 12:31:54] [INFO] [uvicorn.access] ::ffff:192.168.84.140:0 - "GET /items/ HTTP/1.1" 200 diff --git a/backend/main.py b/backend/main.py index 42d80496..8a67f905 100644 --- a/backend/main.py +++ b/backend/main.py @@ -3,11 +3,14 @@ from fastapi.middleware.cors import CORSMiddleware from . import models from .database import engine from .routers import items, operations, users, categories +from .logger import log # Create the database tables models.Base.metadata.create_all(bind=engine) +log.info("Database tables verified.") app = FastAPI(title="TFM aInventory API", version="1.1.0") +log.info("TFM aInventory API process started.") # Setup Cross-Origin for Client interaction (PWA) app.add_middleware( diff --git a/backend/routers/users.py b/backend/routers/users.py index 11deeb28..12b42be9 100644 --- a/backend/routers/users.py +++ b/backend/routers/users.py @@ -11,7 +11,7 @@ router = APIRouter(prefix="/users", tags=["users"]) pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto") def get_ldap_config(): - config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ldap_config.json") + config_path = os.path.join(database.DATA_DIR, "ldap_config.json") if os.path.exists(config_path): with open(config_path, "r") as f: return json.load(f) @@ -209,7 +209,7 @@ def get_ldap_settings(): @router.post("/ldap-config") def update_ldap_settings(config: dict): - config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ldap_config.json") + config_path = os.path.join(database.DATA_DIR, "ldap_config.json") with open(config_path, "w") as f: json.dump(config, f) return {"message": "Config saved"} diff --git a/dev_docs/ARCHIVE_LOGS.md b/dev_docs/ARCHIVE_LOGS.md index f99a2a5f..03fb0520 100644 --- a/dev_docs/ARCHIVE_LOGS.md +++ b/dev_docs/ARCHIVE_LOGS.md @@ -1,3 +1,24 @@ +### [2026-04-11 12:45] v1.3.0: Dockerization & Export Script +**Purpose:** Upgraded the system architecture to support seamless dual-mode execution (Dockerized or Bare-Metal/Local). Extracted data and logic persistence layers to external volumes (`/data`, `/logs`). Added a dedicated production compiler. +**Actions:** +- `backend/database.py` and `users.py` now support `DATA_DIR` environment overrides. +- Implemented `backend/logger.py` for standard Python rotating logs mapped to `/app/logs`. +- Next.js configured for `standalone` output mode to strictly optimize containerized PWA size. +- Orchestrated full environment with `docker-compose.yml`, using Caddy for local self-signed HTTPS termination. +- Created `export_prod.sh` to extract a clean release bundle without AI/Dev files (using optimized `rsync`). +**Modified Files:** +- `backend/database.py` +- `backend/routers/users.py` +- `backend/logger.py` (New) +- `backend/main.py` +- `frontend/next.config.mjs` +- `frontend/Dockerfile` (New) +- `backend/Dockerfile` (New) +- `docker-compose.yml` (New) +- `Caddyfile` (New) +- `export_prod.sh` (New) +- `VERSION.json` + ### [2026-04-11 12:35] v1.2.9: Automatic IDE Entry Points **Purpose:** Restored specific ID-based entry points (`GEMINI.md` and `CLAUDE.md`) as "Proxy Pointers" to ensure modern AI extensions (Cursor, Windsurf, Gemini IDE) naturally pick them up as system prompt injections. **Modified Files:** diff --git a/dev_docs/SESSION_STATE.md b/dev_docs/SESSION_STATE.md index 97e29a91..15ff769b 100644 --- a/dev_docs/SESSION_STATE.md +++ b/dev_docs/SESSION_STATE.md @@ -1,15 +1,18 @@ -# AI Session State - HANDOVER +# CURRENT AI WORKING SESSION -**Status**: v1.2.6 Hotfixed & UX Polished. Persistent Gemini Context Created. -**Current AI Agent**: Gemini (Antigravity) -**Context**: -- **Offline Auth**: LDAP users now have local password hash caching (v1.2.4). -- **UI Affordance**: Added `ChevronDown` to selects and softened password dots. -- **Icon Sync**: Unified iconography (Layers = Categories, Package = Item Types). -- **Memory**: Created [GEMINI.md](../GEMINI.md) as the persistent entry point for this agent. -- **Version**: Currently at **v1.2.6**. Commit log in `ARCHIVE_LOGS.md`. +**Active AI:** Antigravity (Gemini) +**Last Updated:** 2026-04-11 +**Current Version:** v1.3.0 +**Branch:** dev -**Next Steps**: -1. **Phase 6: Advanced Audit Dashboard**. Current logs exist but could use a dedicated page with filtering. -2. Final testing of offline LDAP login (Basement simulation). -3. Verification of icon consistency in any new screens. +### Current Status +Finalized the system refactoring by implementing a complete, portable **Dockerization Architecture**. Introduced the `export_prod.sh` bundle compiler, centralized technical documentation into a Single Source Of Truth (`PROJECT_ARCHITECTURE.md`), and ensured exact reverse compatibility so that bare-metal development using `./start_server.sh` operates identically to previous versions. + +### Technical Context +- **Persistence Mapping:** Local database (`inventory.db`) and `ldap_config.json` now persist through the environmental variable `DATA_DIR` (mapped to local `/data` volume). +- **Log System:** Created `backend/logger.py` with Python's RotatingFileHandler. Streamed locally into `/logs`. +- **Standalone:** Next.js uses `output: standalone` configuration specifically to support the Node alpine dockerfile. Caddy replaces `local-ssl-proxy` only inside the Docker environment. + +### Next Steps / Blockers +1. Testing actual AI flow in production mode (using the exported Production Bundle zip). +2. Implementing the Advanced Audit log Dashboard inside the UI. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..636c9105 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,53 @@ +version: '3.8' + +services: + backend: + build: + context: . + dockerfile: backend/Dockerfile + networks: + - inventory_net + ports: + - "8000:8000" + volumes: + - ./data:/app/data + - ./logs:/app/logs + environment: + - DATA_DIR=/app/data + - LOGS_DIR=/app/logs + restart: unless-stopped + + frontend: + build: + context: . + dockerfile: frontend/Dockerfile + networks: + - inventory_net + ports: + - "3000:3000" + volumes: + - ./logs:/app/logs + # Write Next.js logs to both stdout (docker logs) and file (mapped volume) + command: sh -c "mkdir -p /app/logs && node server.js 2>&1 | tee -a /app/logs/frontend.log" + restart: unless-stopped + + proxy: + image: caddy:alpine + networks: + - inventory_net + ports: + - "3002:3002" + - "3003:3003" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile + # Persist the internal Caddy certificates so users don't get new certificate warnings constantly + - ./data/caddy_data:/data + - ./data/caddy_config:/config + depends_on: + - frontend + - backend + restart: unless-stopped + +networks: + inventory_net: + driver: bridge diff --git a/export_prod.sh b/export_prod.sh new file mode 100755 index 00000000..f62fe1d7 --- /dev/null +++ b/export_prod.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# export_prod.sh - Generates a clean production bundle for distribution + +echo "📦 Preparing TFM aInventory Production Bundle..." + +# Extract version from VERSION.json using grep to avoid macOS python/xcode stubs +VERSION=$(grep '"version"' VERSION.json | head -n 1 | awk -F '"' '{print $4}') +PROD_DIR="aInventory-PROD-v${VERSION}" + +# Clean previous run if it exists +rm -rf "$PROD_DIR" +rm -f "${PROD_DIR}.zip" + +mkdir -p "$PROD_DIR" + +echo "📂 Copying application components (excluding dev artifacts)..." +# Core application +rsync -a --exclude 'node_modules' --exclude '.next' frontend/ "$PROD_DIR/frontend/" +rsync -a --exclude '__pycache__' --exclude '.pytest_cache' --exclude '.venv' backend/ "$PROD_DIR/backend/" + +# Orchestration & Scripts +cp docker-compose.yml "$PROD_DIR/" +cp Caddyfile "$PROD_DIR/" +cp start_server.sh "$PROD_DIR/" +cp .git_path "$PROD_DIR/" 2>/dev/null || true +cp VERSION.json "$PROD_DIR/" + +# Setup persistent volume skeleton +mkdir -p "$PROD_DIR/data" +mkdir -p "$PROD_DIR/logs" +# Place a README in the root of the release +cat < "$PROD_DIR/README.txt" +TFM aInventory - v${VERSION} +============================= + +This is a clean production build, free of development or AI-agent constraints. + +TO RUN VIA DOCKER (Recommended): +1. Install Docker Desktop or Docker Engine. +2. Run: docker-compose build +3. Run: docker-compose up -d +4. Access via https://:3003 (Accept the internal security warning). + +TO RUN BARE-METAL (No Docker): +1. Install Python 3.12+ and Node.js 20+. +2. Ensure you have network access for npm installs. +3. Run: ./start_server.sh +4. Access via https://:3003 + +Note: Database and Logs will persist in the /data and /logs directories. +EOF + +echo "🗜️ Zipping the final bundle..." +zip -r -q "${PROD_DIR}.zip" "$PROD_DIR" + +# Optional: cleanup the directory to leave just the zip +# rm -rf "$PROD_DIR" + +echo "✅ SUCCESS: The clean production archive is ready: ${PROD_DIR}.zip" diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 00000000..944116a2 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,48 @@ +FROM node:20-alpine AS base + +# Step 1: Install dependencies +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app +# We run this from the frontend folder context +COPY package.json package-lock.json* ./ +RUN npm ci + +# Step 2: Build the source code +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +# Disable telemetry during build +ENV NEXT_TELEMETRY_DISABLED 1 +RUN npm run build + +# Step 3: Production image +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +# Add nextjs user +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy standalone output +COPY --from=builder /app/public ./public +# Set proper permissions for the Next.js cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +# Note: The server.js is created by next build from the standalone output +CMD ["node", "server.js"] diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index a1e01f5b..edf79256 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -9,7 +9,7 @@ const withPWA = withPWAInit({ /** @type {import('next').NextConfig} */ const nextConfig = { - // Config options here + output: "standalone", }; export default withPWA(nextConfig);