feat: complete dockerization architecture with data/logs persistence and prod export script v1.3.0
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,3 +4,5 @@ __pycache__/
|
|||||||
*.pyc
|
*.pyc
|
||||||
.env
|
.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
aInventory-PROD*
|
||||||
|
aInventory-PROD*.zip
|
||||||
|
|||||||
12
Caddyfile
Normal file
12
Caddyfile
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
{
|
{
|
||||||
"version": "1.2.9",
|
"version": "1.3.0",
|
||||||
"last_build": "2026-04-11-1235",
|
"last_build": "2026-04-11-1235",
|
||||||
"commit": "3b2f1",
|
"commit": "PENDING",
|
||||||
"changelog": [
|
"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.9: Automatic IDE Entry Points (GEMINI.md, CLAUDE.md proxies)",
|
||||||
"v1.2.8: Absolute Documentation Consolidation (Unified ARCHITECTURE and AI_RULES)",
|
"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.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.6: Hotfix - Added missing icon imports (Layers, ChevronDown)",
|
||||||
"v1.2.5: Synchronized icons across UI (Layers for Categories, Package for Item Types)",
|
"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.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.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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
35
backend/Dockerfile
Normal file
35
backend/Dockerfile
Normal file
@@ -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"]
|
||||||
@@ -4,9 +4,11 @@ import os
|
|||||||
|
|
||||||
# Get absolute path for the backend directory
|
# Get absolute path for the backend directory
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
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)
|
os.makedirs(DATA_DIR, exist_ok=True)
|
||||||
|
|
||||||
# Handle absolute path for SQLite (needs 4 slashes on Unix)
|
# Handle absolute path for SQLite (needs 4 slashes on Unix)
|
||||||
|
|||||||
51
backend/logger.py
Normal file
51
backend/logger.py
Normal file
@@ -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()
|
||||||
174
backend/logs/backend.log
Normal file
174
backend/logs/backend.log
Normal file
@@ -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
|
||||||
@@ -3,11 +3,14 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
from . import models
|
from . import models
|
||||||
from .database import engine
|
from .database import engine
|
||||||
from .routers import items, operations, users, categories
|
from .routers import items, operations, users, categories
|
||||||
|
from .logger import log
|
||||||
|
|
||||||
# Create the database tables
|
# Create the database tables
|
||||||
models.Base.metadata.create_all(bind=engine)
|
models.Base.metadata.create_all(bind=engine)
|
||||||
|
log.info("Database tables verified.")
|
||||||
|
|
||||||
app = FastAPI(title="TFM aInventory API", version="1.1.0")
|
app = FastAPI(title="TFM aInventory API", version="1.1.0")
|
||||||
|
log.info("TFM aInventory API process started.")
|
||||||
|
|
||||||
# Setup Cross-Origin for Client interaction (PWA)
|
# Setup Cross-Origin for Client interaction (PWA)
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ router = APIRouter(prefix="/users", tags=["users"])
|
|||||||
pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")
|
pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")
|
||||||
|
|
||||||
def get_ldap_config():
|
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):
|
if os.path.exists(config_path):
|
||||||
with open(config_path, "r") as f:
|
with open(config_path, "r") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
@@ -209,7 +209,7 @@ def get_ldap_settings():
|
|||||||
|
|
||||||
@router.post("/ldap-config")
|
@router.post("/ldap-config")
|
||||||
def update_ldap_settings(config: dict):
|
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:
|
with open(config_path, "w") as f:
|
||||||
json.dump(config, f)
|
json.dump(config, f)
|
||||||
return {"message": "Config saved"}
|
return {"message": "Config saved"}
|
||||||
|
|||||||
@@ -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
|
### [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.
|
**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:**
|
**Modified Files:**
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
# AI Session State - HANDOVER
|
# CURRENT AI WORKING SESSION
|
||||||
|
|
||||||
**Status**: v1.2.6 Hotfixed & UX Polished. Persistent Gemini Context Created.
|
**Active AI:** Antigravity (Gemini)
|
||||||
**Current AI Agent**: Gemini (Antigravity)
|
**Last Updated:** 2026-04-11
|
||||||
**Context**:
|
**Current Version:** v1.3.0
|
||||||
- **Offline Auth**: LDAP users now have local password hash caching (v1.2.4).
|
**Branch:** dev
|
||||||
- **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`.
|
|
||||||
|
|
||||||
**Next Steps**:
|
### Current Status
|
||||||
1. **Phase 6: Advanced Audit Dashboard**. Current logs exist but could use a dedicated page with filtering.
|
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.
|
||||||
2. Final testing of offline LDAP login (Basement simulation).
|
|
||||||
3. Verification of icon consistency in any new screens.
|
### 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.
|
||||||
|
|||||||
53
docker-compose.yml
Normal file
53
docker-compose.yml
Normal file
@@ -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
|
||||||
59
export_prod.sh
Executable file
59
export_prod.sh
Executable file
@@ -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 <<EOF > "$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://<YOUR-IP>: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://<YOUR-IP>: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"
|
||||||
48
frontend/Dockerfile
Normal file
48
frontend/Dockerfile
Normal file
@@ -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"]
|
||||||
@@ -9,7 +9,7 @@ const withPWA = withPWAInit({
|
|||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
// Config options here
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withPWA(nextConfig);
|
export default withPWA(nextConfig);
|
||||||
|
|||||||
Reference in New Issue
Block a user