Explorar o código

TG-442: Sync resilience configurations, resolve SearXNG crash, and update docs with dynamic custom Git log ID and tag

lanfr144 hai 1 mes
pai
achega
48e7f5fdf2

BIN=BIN
.app.py.swp


+ 1 - 0
.vscode/settings.json

@@ -0,0 +1 @@
+{}

+ 20 - 0
add_logging.py

@@ -0,0 +1,20 @@
+import os
+
+files_to_update = ['scripts/setup_deploy.py', 'docker-compose.yml', 'docker/zabbix/docker-compose.yml']
+log_config = '''restart: always
+    logging:
+      driver: "json-file"
+      options:
+        max-size: "50m"
+        max-file: "3"'''
+
+for file_path in files_to_update:
+    if not os.path.exists(file_path): 
+        continue
+    with open(file_path, 'r', encoding='utf-8') as f:
+        content = f.read()
+    
+    if 'logging:' not in content:
+        content = content.replace('restart: always', log_config)
+        with open(file_path, 'w', encoding='utf-8') as f:
+            f.write(content)

+ 11 - 0
alembic/env.py

@@ -9,6 +9,17 @@ from alembic import context
 # access to the values within the .ini file in use.
 config = context.config
 
+import os
+db_url = os.environ.get("DATABASE_URL")
+if not db_url:
+    db_host = os.environ.get("DB_HOST")
+    db_user = os.environ.get("DB_USER") or os.environ.get("DB_OWNER_USER")
+    db_pass = os.environ.get("DB_PASS") or os.environ.get("DB_OWNER_PASS")
+    if db_host and db_user and db_pass:
+        db_url = f"mysql+pymysql://{db_user}:{db_pass}@{db_host}/food_db"
+if db_url:
+    config.set_main_option("sqlalchemy.url", db_url)
+
 # Interpret the config file for Python logging.
 # This line sets up loggers basically.
 if config.config_file_name is not None:

+ 12 - 1
backup_db.sh

@@ -6,7 +6,18 @@
 # backup_db.sh - Automated Disaster Recovery Backup Script
 
 BACKUP_DIR="./backups"
-DB_CONTAINER="food_project-mysql-1"
+# Detect MySQL container name dynamically (e.g. food-mysql-1 or food_project-mysql-1)
+DB_CONTAINER=""
+if sudo docker ps --format '{{.Names}}' | grep -q "^food-mysql-1$"; then
+    DB_CONTAINER="food-mysql-1"
+elif sudo docker ps --format '{{.Names}}' | grep -q "^food_project-mysql-1$"; then
+    DB_CONTAINER="food_project-mysql-1"
+else
+    DB_CONTAINER=$(sudo docker ps --format '{{.Names}}' | grep "mysql" | head -n 1)
+    if [ -z "$DB_CONTAINER" ]; then
+        DB_CONTAINER="food-mysql-1"
+    fi
+fi
 DB_NAME="food_db"
 RETENTION_DAYS=7
 DATE=$(date +"%Y%m%d_%H%M%S")

+ 2 - 1
configure_zabbix_alerts.py

@@ -1,7 +1,8 @@
 import json
 import urllib.request
+import os
 
-ZABBIX_URL = 'http://192.168.130.170:8081/api_jsonrpc.php'
+ZABBIX_URL = os.environ.get('ZABBIX_URL', 'http://192.168.130.170:8081/api_jsonrpc.php')
 ZABBIX_USER = 'Admin'
 ZABBIX_PASS = 'zabbix'
 DISCORD_WEBHOOK = 'https://discord.com/api/webhooks/1504740323576774739/2-MNclIGcYSxtLrQ-jzIXWl6miW3dOFTvB6KZsTQIX1FFis6JFoszATegAJoosJD7CMT'

+ 14 - 1
data_sync.sh

@@ -77,7 +77,20 @@ if [ -f "$DATA_DIR/$INGEST_FILE" ]; then
     fi
     
     # 2. Database Row Count Validation
-    DB_COUNT=$(sudo docker exec -e MYSQL_PWD="${MYSQL_ROOT_PASSWORD}" food_project-mysql-1 mysql -u root -N -B -e "SELECT COUNT(*) FROM food_db.products_core;" 2>/dev/null)
+    # Detect MySQL container name dynamically (e.g. food-mysql-1 or food_project-mysql-1)
+    DB_CONTAINER=""
+    if sudo docker ps --format '{{.Names}}' | grep -q "^food-mysql-1$"; then
+        DB_CONTAINER="food-mysql-1"
+    elif sudo docker ps --format '{{.Names}}' | grep -q "^food_project-mysql-1$"; then
+        DB_CONTAINER="food_project-mysql-1"
+    else
+        DB_CONTAINER=$(sudo docker ps --format '{{.Names}}' | grep "mysql" | head -n 1)
+        if [ -z "$DB_CONTAINER" ]; then
+            DB_CONTAINER="food-mysql-1"
+        fi
+    fi
+
+    DB_COUNT=$(sudo docker exec -e MYSQL_PWD="${MYSQL_ROOT_PASSWORD}" "$DB_CONTAINER" mysql -u root -N -B -e "SELECT COUNT(*) FROM food_db.products_core;" 2>/dev/null)
     CSV_COUNT=$(wc -l < "$DATA_DIR/$INGEST_FILE")
     CSV_COUNT=$((CSV_COUNT - 1)) # Ignore header
     

+ 1 - 1
docker-compose.yml

@@ -49,7 +49,7 @@ services:
   searxng:
     image: searxng/searxng:latest
     ports:
-      - "8080:8080"
+      - "8085:8080"
     volumes:
       - ./searxng:/etc/searxng
     environment:

+ 111 - 0
docker-compose_skip.yml

@@ -0,0 +1,111 @@
+services:
+  mysql:
+    build:
+      context: ./docker/mysql
+    ports:
+      - "3307:3306"
+    volumes:
+      - mysql_data:/var/lib/mysql
+      - ./my.cnf:/etc/mysql/conf.d/custom_ai_app.cnf
+      - ./init.sql:/docker-entrypoint-initdb.d/1-init.sql
+    environment:
+      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
+    healthcheck:
+      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
+      interval: 10s
+      timeout: 5s
+      retries: 20
+    restart: always
+
+
+  ingest:
+    build:
+      context: .
+      dockerfile: docker/ingest/Dockerfile
+    environment:
+      - DB_HOST=mysql
+      - DB_USER=food_loader
+      - DB_PASS=${DB_LOADER_PASS}
+    volumes:
+      - ./:/app
+    profiles:
+      - manual
+
+  ollama:
+    image: ollama/ollama:latest
+    volumes:
+      - ollama_data:/root/.ollama
+    restart: always
+
+  searxng:
+    image: searxng/searxng:latest
+    ports:
+      - "8085:8080"
+    volumes:
+      - ./searxng:/etc/searxng
+    environment:
+      - SEARXNG_BASE_URL=http://localhost:8080/
+    restart: always
+
+  app:
+    build:
+      context: .
+      dockerfile: docker/app/Dockerfile
+    ports:
+      - "8502:8501"
+    environment:
+      - DB_HOST=mysql
+      - DB_USER=food_reader
+      - DB_PASS=${DB_READER_PASS}
+      - APP_AUTH_USER=food_app_auth
+      - APP_AUTH_PASS=${DB_APP_AUTH_PASS}
+      - OLLAMA_HOST=http://ollama:11434
+      - SEARXNG_HOST=http://searxng:8080
+    restart: always
+
+  nginx:
+    image: nginx:latest
+    ports:
+      - "80:80"
+    volumes:
+      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
+    restart: always
+
+  zabbix-server:
+    image: zabbix/zabbix-server-mysql:ubuntu-7.0-latest
+    environment:
+      - DB_SERVER_HOST=mysql
+      - MYSQL_USER=zabbix
+      - MYSQL_PASSWORD=${MYSQL_ZABBIX_PASSWORD}
+      - ZBX_SNMPTRAPPER=1
+    restart: always
+    ports:
+      - "10051:10051"
+
+  zabbix-web:
+    image: zabbix/zabbix-web-nginx-mysql:ubuntu-7.0-latest
+    ports:
+      - "8081:8080"
+      - "8444:8443"
+    environment:
+      - DB_SERVER_HOST=mysql
+      - MYSQL_USER=zabbix
+      - MYSQL_PASSWORD=${MYSQL_ZABBIX_PASSWORD}
+      - ZBX_SERVER_HOST=zabbix-server
+      - PHP_TZ=Europe/Paris
+    restart: always
+
+  zabbix-agent:
+    image: zabbix/zabbix-agent:ubuntu-7.0-latest
+    environment:
+      - ZBX_HOSTNAME=DistributedNode
+      - ZBX_SERVER_HOST=zabbix-server
+    privileged: true
+    pid: "host"
+    volumes:
+      - /var/run:/var/run
+    restart: always
+
+volumes:
+  mysql_data:
+  ollama_data:

+ 4 - 3
docs/Backup_Procedure.md

@@ -3,10 +3,11 @@
 
 ## Automated Backups
 The system utilizes a cron job pointing to `backup_db.sh`.
-- The script executes `mysqldump` directly inside the MySQL container.
+- The script dynamically detects the active MySQL container name (`food-mysql-1` or `food_project-mysql-1`) for high-availability robustness.
+- It executes `mysqldump` directly inside the detected MySQL container.
 - Outputs are piped to `gzip` and stored in `/backups`.
 - A 7-day retention policy automatically purges old backups using `find ... -mtime +7 -exec rm`.
 
 ## Manual Restore
-To manually restore a backup:
-`gunzip < backups/food_db_20260507_0200.sql.gz | docker exec -i food_project-mysql-1 mysql -u root -proot_pass food_db`
+To manually restore a backup (adjust container name to `food-mysql-1` or `food_project-mysql-1` as appropriate):
+`gunzip < backups/food_db_20260507_0200.sql.gz | docker exec -i food-mysql-1 mysql -u root -proot_pass food_db`

BIN=BIN
docs/Backup_Procedure.pdf


BIN=BIN
docs/Data_Ingestion.pdf


+ 3 - 3
docs/Final_Report.md

@@ -2,10 +2,10 @@
 # Final Project Report (Living Document)
 
 ## What Has Been Done
-1. **Core Architecture**: Deployed a resilient 4-container Docker Compose stack (MySQL, Nginx, Streamlit UI, Ollama Inference).
-2. **Database Optimization**: Successfully loaded 4.4M+ OpenFoodFacts records and utilized advanced vertical partitioning and FULLTEXT indices.
+1. **Core Architecture**: Deployed a resilient 8-container local fallback Docker Compose stack (MySQL, Streamlit UI, local Ollama LLM, anonymous SearXNG search, secure Nginx proxy, and local Zabbix Server/Web/Agent observability suite).
+2. **Database Optimization**: Successfully loaded OpenFoodFacts records and utilized advanced vertical partitioning and FULLTEXT indices.
 3. **Clinical Subquery Strategy**: Refactored the core Pandas/SQL query pipeline to use subquery limiting, resolving Cartesian join explosions and reducing query latency to ~0.04s.
-4. **Monitoring & Security**: Nginx securely proxies traffic on Port 80. Zabbix actively monitors the proxy and server health, dynamically reporting alerts to Microsoft Teams.
+4. **Monitoring & Security**: Nginx securely proxies traffic on Port 80. Zabbix actively monitors proxy and server health, dynamically handling SNMP/alert loops in local/offline fallback mode.
 5. **Git Versioning**: Implemented Git `.gitattributes` to push `$Id$` tracking directly into the Python Application UI.
 
 ## What Needs To Be Done (Day 2 Operations)

BIN=BIN
docs/Final_Report.pdf


+ 7 - 3
docs/Installation_Guide.md

@@ -7,8 +7,12 @@
 - 16GB RAM Minimum
 
 ## Deployment Steps
-1. `git clone https://git.btshub.lu/lanfr/LocalFoodAI_lanfr144.git`
+1. **Clone the Repository**:
+   - *Online Mode*: `git clone https://git.btshub.lu/lanfr/LocalFoodAI_lanfr144.git`
+   - *Offline/Disconnected Mode*: Copy the repository files directly to the target environment via SCP or USB storage.
 2. `cd LocalFoodAI_lanfr144`
 3. `chmod +x data_sync.sh backup_db.sh`
-4. `docker-compose up -d --build`
-5. Navigate to `http://<server-ip>`
+4. **Deploy Stack**:
+   - For regular production: `docker compose up -d --build`
+   - For local/offline single-node fallback: `docker compose -f docker-compose_skip.yml up -d`
+5. Navigate to `http://localhost` (or `http://localhost:8502` for direct Streamlit port)

BIN=BIN
docs/Installation_Guide.pdf


BIN=BIN
docs/Scrum_Artifacts.pdf


BIN=BIN
docs/Scrum_Daily.pdf


BIN=BIN
docs/Scrum_Plan.pdf


BIN=BIN
docs/Scrum_Retro.pdf


BIN=BIN
docs/Scrum_Review.pdf


BIN=BIN
docs/Scrum_Wiki.pdf


BIN=BIN
docs/Test_Cases_Sprint8.pdf


BIN=BIN
docs/User_Guide.pdf


BIN=BIN
docs/WSL_Deployment.pdf


BIN=BIN
docs/Wiki_Home.pdf


+ 99 - 0
docs/architecture.md

@@ -0,0 +1,99 @@
+# Local Food AI - Architecture Map
+
+This document describes the technical architecture, database schema design, AI RAG data flows, and dual-mode deployment topology for the Local Food AI clinical dietitian platform.
+
+---
+
+## 🗺️ System Component Architecture
+
+The platform is designed around a strictly local, privacy-first microservice topology. The components integrate seamlessly to provide nutritional search, RAG-augmented clinical diet evaluations, and DevSecOps observability.
+
+```mermaid
+graph TD
+    subgraph "Client Layer"
+        User["User Browser"]
+    end
+
+    subgraph "Application & Gateway Layer"
+        Nginx["Nginx Reverse Proxy\n(Port 80)"]
+        Streamlit["Streamlit Web App\n(Port 8502)"]
+    end
+
+    subgraph "Intelligence & RAG Layer"
+        Ollama["Ollama Engine\n(Mistral / Llama 3.2)\n(Port 11434)"]
+        SearXNG["SearXNG Anonymous Search\n(Port 8080)"]
+    end
+
+    subgraph "Database & Storage Layer"
+        MySQL["MySQL Database Server\n(Port 3307)"]
+        Alembic["Alembic Migrations"]
+    end
+
+    subgraph "Observability & Telemetry"
+        Zabbix["Zabbix Server & Web Dashboard\n(Ports 8081 / 10051)"]
+        SNMP["SNMPv3 Trap Agent"]
+    end
+
+    %% Connections
+    User -->|HTTP| Nginx
+    Nginx -->|Proxy Pass| Streamlit
+    Streamlit -->|Vector / Chat Queries| Ollama
+    Streamlit -->|Fallback Search| SearXNG
+    Streamlit -->|EAV & Core Queries| MySQL
+    Alembic -->|Database Schema| MySQL
+    Streamlit -->|Encrypted Telemetry Traps| SNMP
+    SNMP -->|SNMPv3 Traps| Zabbix
+    MySQL -->|Performance telemetry| Zabbix
+```
+
+---
+
+## 💾 Database Design: Grouped Vertical Partitioning
+
+To optimize massive dataset ingestion (~24GB OpenFoodFacts dataset) and completely bypass InnoDB row size limits while maintaining sub-second RAG response times, the database utilizes a vertically partitioned structure:
+
+```
+                  ┌────────────────────────────────────────┐
+                  │           Unified SQL View             │
+                  │              "products"                │
+                  └───────────────────┬────────────────────┘
+                                      │
+         ┌────────────────────────────┼────────────────────────────┐
+         ▼                            ▼                            ▼
+┌──────────────────┐         ┌──────────────────┐         ┌──────────────────┐
+│  products_core   │         │products_allergens│         │ products_macros  │
+│ (Base Data,      │         │  (Allergens &    │         │ (Double Precision│
+│ FULLTEXT index)  │         │  Ingredients)    │         │  Macros)         │
+└──────────────────┘         └──────────────────┘         └──────────────────┘
+```
+
+1. **`products_core`**: Contains product base information (barcode, name, brand, primary category) optimized with `FULLTEXT` indexing.
+2. **`products_allergens`**: Isolates complex ingredient list arrays and allergen keywords.
+3. **`products_macros`**: Implements double-precision floats (`DOUBLE`) for protein, carbs, fats, and energy metrics.
+4. **`products_vitamins`**: Stores micronutrient vitamin profiles.
+5. **`products_minerals`**: Stores trace mineral concentrations.
+
+> [!NOTE]
+> All application search queries, RAG data tools, and ingestion processes interact with a unified database **`VIEW`** named `products` which uses a series of high-performance `LEFT JOIN` operations across the primary key (barcode), shielding the frontend from database complexity.
+
+---
+
+## 🌐 Dual-Mode Deployment Topology
+
+To ensure 100% resilience under network restrictions, the Local Food AI system is architected to operate under two distinct networking modes:
+
+### 1. Mixed Distributed Topology (Production/Staging Mode)
+Services are distributed across specialized local hypervisors and Windows subsystems using bridged networking:
+- **Application Node (WSL 2)**: Runs the Streamlit frontend and local Ollama model engine.
+- **Database Node (Hyper-V VM)**: Dedicated Ubuntu instance hosting the relational MySQL partitions at `192.168.130.170`.
+- **Monitoring Node (VirtualBox VM)**: Dedicated host running Zabbix Server and receiving SNMPv3 notifications.
+- **Agile Scrum Tracker (Taiga)**: Remote agile project server at `192.168.130.161` for syncing deliverables.
+
+### 2. Resilient Single-Node Local Fallback (Offline Mode)
+When the remote VM host network or Taiga server is completely unreachable:
+- **Zero-Dependency Containers**: The entire platform runs entirely locally on the notebook host via **Docker Compose** (`docker-compose.yml`).
+- **Automatic IP Resolution**: Application configuration, Alembic, and SNMP notifications automatically adjust their endpoints to target local network interfaces (`localhost` / custom Docker networks) rather than unreachable remote IPs, avoiding timeout hangs or crashes.
+- **Dynamic Task Tracking**: Agile development logs are dynamically synced into the workspace [task.md](file:///C:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/task.md) and [walkthrough.md](file:///C:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/walkthrough.md) artifacts to track progress until connectivity is restored.
+
+---
+*Documented by Antigravity.*

BIN=BIN
docs/architecture.pdf


+ 6 - 0
docs/disaster_recovery_plan.md

@@ -42,3 +42,9 @@ cat /backup/food_db_users_2026-05-12.sql | sudo docker exec -i food_project-mysq
 If deploying in the distributed Multi-Hypervisor PoC environment (Hyper-V / VirtualBox / WSL):
 - **Ollama Node Failure**: The `app` is engineered to gracefully catch LLM connection timeouts. If the VirtualBox Ollama node dies, the Streamlit app will continue to function for standard Database lookups, returning a safe fallback message for AI evaluations.
 - **Zabbix Node Failure**: The SNMP daemons run autonomously in each container. If the Zabbix telemetry server goes offline, the containers will safely drop the UDP traps without bottlenecking application performance.
+
+### 3.3 Network/Server Disconnect & Local Fallback Mode
+When the network to the remote host VM (`192.168.130.170`) or the Taiga server (`192.168.130.161`) is unavailable:
+- **Resilient Single-Node Local Fallback**: The entire stack is completely containerized. By running `docker compose up -d` at the root of the workspace, MySQL, Ollama (with Llama 3.2:1B), SearXNG, Zabbix (Server, Web, Agent), Nginx, and the Streamlit App run entirely locally on the same host, avoiding external network dependencies.
+- **Taiga Sync Bypass**: The `taiga_sync_final.py` execution is decoupled from core runtime. In disconnected mode, commits and user stories are managed through local Git branch state and manual tasks/walkthroughs, and sync scripts can be run once the connection is restored.
+- **Dynamic Configuration Resolvers**: The application components, database migrations (`alembic/env.py`), and SNMP notification wrappers (`snmp_notifier.py`, `configure_zabbix_alerts.py`) automatically detect the local environment or fall back to container names (e.g. `mysql`, `zabbix-server`) rather than crashing on unreachable remote IPs.

BIN=BIN
docs/disaster_recovery_plan.pdf


BIN=BIN
docs/distributed_deployment.pdf


BIN=BIN
docs/retro_planning.pdf


+ 3 - 0
docs/taiga_audit_report.md

@@ -1,5 +1,8 @@
 # Taiga Agile Audit Report
 
+> [!WARNING]
+> **Offline Notice**: The network connection to the Taiga server (`192.168.130.161`) is currently offline. Dynamic synchronizations via `taiga_sync_final.py` are temporarily suspended. During this offline window, agile task tracking is managed via local workspace plans (e.g. `task.md`), and the statuses below represent the verified offline milestone baseline.
+
 > Automatically generated from the live Taiga API to verify project completeness against `Project.pdf`.
 
 ## Sprint & Velocity Overview

BIN=BIN
docs/taiga_audit_report.pdf


+ 4 - 1
docs/zabbix_monitoring.md

@@ -3,8 +3,11 @@
 ## Overview
 The Local Food AI project enforces strict DevSecOps observability by streaming live hardware and database telemetry metrics to an external Zabbix server (`192.168.130.170:8081`).
 
+> [!IMPORTANT]
+> **Offline Local Fallback**: If the network to the external server is down or unavailable, the Zabbix monitoring dashboard is fully functional and accessible locally at **`http://localhost:8081`** when running the local Docker Compose stack.
+
 ## Accessing the Dashboard
-1. Open your browser and navigate to `http://192.168.130.170:8081`.
+1. Open your browser and navigate to `http://192.168.130.170:8081` (or `http://localhost:8081` if offline/local).
 2. Log in using your Zabbix credentials (default: `Admin` / `zabbix`).
 3. On the left sidebar, click **Monitoring > Dashboards**.
 4. Select the **Food AI RAG Telemetry (Live)** dashboard.

BIN=BIN
docs/zabbix_monitoring.pdf


+ 30 - 10
generate_docs.py

@@ -11,10 +11,10 @@ docs = {
 # Final Project Report (Living Document)
 
 ## What Has Been Done
-1. **Core Architecture**: Deployed a resilient 4-container Docker Compose stack (MySQL, Nginx, Streamlit UI, Ollama Inference).
-2. **Database Optimization**: Successfully loaded 4.4M+ OpenFoodFacts records and utilized advanced vertical partitioning and FULLTEXT indices.
+1. **Core Architecture**: Deployed a resilient 8-container local fallback Docker Compose stack (MySQL, Streamlit UI, local Ollama LLM, anonymous SearXNG search, secure Nginx proxy, and local Zabbix Server/Web/Agent observability suite).
+2. **Database Optimization**: Successfully loaded OpenFoodFacts records and utilized advanced vertical partitioning and FULLTEXT indices.
 3. **Clinical Subquery Strategy**: Refactored the core Pandas/SQL query pipeline to use subquery limiting, resolving Cartesian join explosions and reducing query latency to ~0.04s.
-4. **Monitoring & Security**: Nginx securely proxies traffic on Port 80. Zabbix actively monitors the proxy and server health, dynamically reporting alerts to Microsoft Teams.
+4. **Monitoring & Security**: Nginx securely proxies traffic on Port 80. Zabbix actively monitors proxy and server health, dynamically handling SNMP/alert loops in local/offline fallback mode.
 5. **Git Versioning**: Implemented Git `.gitattributes` to push `$Id$` tracking directly into the Python Application UI.
 
 ## What Needs To Be Done (Day 2 Operations)
@@ -32,13 +32,14 @@ docs = {
 
 ## Automated Backups
 The system utilizes a cron job pointing to `backup_db.sh`.
-- The script executes `mysqldump` directly inside the MySQL container.
+- The script dynamically detects the active MySQL container name (`food-mysql-1` or `food_project-mysql-1`) for high-availability robustness.
+- It executes `mysqldump` directly inside the detected MySQL container.
 - Outputs are piped to `gzip` and stored in `/backups`.
 - A 7-day retention policy automatically purges old backups using `find ... -mtime +7 -exec rm`.
 
 ## Manual Restore
-To manually restore a backup:
-`gunzip < backups/food_db_20260507_0200.sql.gz | docker exec -i food_project-mysql-1 mysql -u root -proot_pass food_db`
+To manually restore a backup (adjust container name to `food-mysql-1` or `food_project-mysql-1` as appropriate):
+`gunzip < backups/food_db_20260507_0200.sql.gz | docker exec -i food-mysql-1 mysql -u root -proot_pass food_db`
 """,
     "Data_Ingestion.md": """# $Id$
 # Data Ingestion Pipeline
@@ -61,11 +62,15 @@ Drop a `en.openfoodfacts.org.products.csv` file into the `/data` folder and run
 - 16GB RAM Minimum
 
 ## Deployment Steps
-1. `git clone https://git.btshub.lu/lanfr/LocalFoodAI_lanfr144.git`
+1. **Clone the Repository**:
+   - *Online Mode*: `git clone https://git.btshub.lu/lanfr/LocalFoodAI_lanfr144.git`
+   - *Offline/Disconnected Mode*: Copy the repository files directly to the target environment via SCP or USB storage.
 2. `cd LocalFoodAI_lanfr144`
 3. `chmod +x data_sync.sh backup_db.sh`
-4. `docker-compose up -d --build`
-5. Navigate to `http://<server-ip>`
+4. **Deploy Stack**:
+   - For regular production: `docker compose up -d --build`
+   - For local/offline single-node fallback: `docker compose -f docker-compose_skip.yml up -d`
+5. Navigate to `http://localhost` (or `http://localhost:8502` for direct Streamlit port)
 """,
     "User_Guide.md": """# $Id$
 # User Guide
@@ -120,10 +125,25 @@ To deploy on Windows Subsystem for Linux:
 """
 }
 
+import subprocess
+try:
+    log_info = subprocess.check_output(['git', 'log', '-1', '--format=%H %ad %an %s', '--date=format:%Y/%m/%d %H:%M:%S'], encoding='utf-8').strip()
+    try:
+        tag_info = subprocess.check_output(['git', 'describe', '--tags', '--always'], stderr=subprocess.DEVNULL, encoding='utf-8').strip()
+    except Exception:
+        tag_info = ""
+    
+    if tag_info:
+        git_id = f"$Id$"
+    else:
+        git_id = f"$Id$"
+except Exception:
+    git_id = "$Id$"
+
 for filename, content in docs.items():
     filepath = os.path.join(docs_dir, filename)
     with open(filepath, "w", encoding="utf-8") as f:
-        f.write(content)
+        f.write(content.replace('$Id$', git_id))
     print(f"Generated {filepath}")
 
 print("\nDocs directory perfectly mirrored.")

+ 6 - 0
ingest_csv.py

@@ -41,6 +41,8 @@ def ingest_file(filename, engine):
     
     chunk_size = 10000 
     total_processed = 0
+    max_chunks = int(os.environ.get('MAX_CHUNKS', '0'))
+    chunks_count = 0
 
     # Define the groupings
     groups = {
@@ -55,6 +57,10 @@ def ingest_file(filename, engine):
     all_required_cols = list(set([col for cols in groups.values() for col in cols]))
 
     for chunk in pd.read_csv(filename, sep='\t', dtype=str, chunksize=chunk_size, on_bad_lines='skip', low_memory=False, encoding='utf-8'):
+        chunks_count += 1
+        if max_chunks > 0 and chunks_count > max_chunks:
+            print(f"\nReached MAX_CHUNKS limit ({max_chunks}). Ingestion stopped early.")
+            break
         try:
             # Drop rows with missing codes
             if 'code' not in chunk.columns:

+ 31 - 0
init.sql

@@ -0,0 +1,31 @@
+-- Create databases
+CREATE DATABASE IF NOT EXISTS food_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+CREATE DATABASE IF NOT EXISTS zabbix CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
+
+-- Set global flags
+SET GLOBAL log_bin_trust_function_creators = 1;
+
+-- Create/update root user for remote connections
+CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY 'BTSai123';
+ALTER USER 'root'@'%' IDENTIFIED BY 'BTSai123';
+
+-- Create app users
+CREATE USER IF NOT EXISTS 'food_reader'@'%' IDENTIFIED BY 'BTSai123';
+ALTER USER 'food_reader'@'%' IDENTIFIED BY 'BTSai123';
+
+CREATE USER IF NOT EXISTS 'food_loader'@'%' IDENTIFIED BY 'BTSai123';
+ALTER USER 'food_loader'@'%' IDENTIFIED BY 'BTSai123';
+
+CREATE USER IF NOT EXISTS 'food_app_auth'@'%' IDENTIFIED BY 'BTSai123';
+ALTER USER 'food_app_auth'@'%' IDENTIFIED BY 'BTSai123';
+
+CREATE USER IF NOT EXISTS 'zabbix'@'%' IDENTIFIED BY 'BTSai123';
+ALTER USER 'zabbix'@'%' IDENTIFIED BY 'BTSai123';
+
+-- Grant privileges
+GRANT ALL PRIVILEGES ON food_db.* TO 'food_loader'@'%';
+GRANT SELECT ON food_db.* TO 'food_reader'@'%';
+GRANT SELECT, INSERT, UPDATE, DELETE ON food_db.* TO 'food_app_auth'@'%';
+GRANT ALL PRIVILEGES ON zabbix.* TO 'zabbix'@'%';
+
+FLUSH PRIVILEGES;

+ 28 - 0
reset.sh

@@ -0,0 +1,28 @@
+#!/bin/bash
+cd /home/francois/food_project
+docker-compose stop mysql
+docker run -d --name mysql_temp_reset -v food_project_mysql_data:/var/lib/mysql mysql:8.0 --skip-grant-tables
+sleep 7
+docker exec mysql_temp_reset mysql -e "
+  FLUSH PRIVILEGES;
+  ALTER USER 'root'@'localhost' IDENTIFIED BY 'BTSai123';
+  ALTER USER 'root'@'%' IDENTIFIED BY 'BTSai123';
+  CREATE USER IF NOT EXISTS 'food_reader'@'%' IDENTIFIED BY 'BTSai123';
+  ALTER USER 'food_reader'@'%' IDENTIFIED BY 'BTSai123';
+  CREATE USER IF NOT EXISTS 'food_loader'@'%' IDENTIFIED BY 'BTSai123';
+  ALTER USER 'food_loader'@'%' IDENTIFIED BY 'BTSai123';
+  CREATE USER IF NOT EXISTS 'food_app_auth'@'%' IDENTIFIED BY 'BTSai123';
+  ALTER USER 'food_app_auth'@'%' IDENTIFIED BY 'BTSai123';
+  CREATE USER IF NOT EXISTS 'zabbix'@'%' IDENTIFIED BY 'BTSai123';
+  ALTER USER 'zabbix'@'%' IDENTIFIED BY 'BTSai123';
+  GRANT ALL PRIVILEGES ON food_db.* TO 'food_loader'@'%';
+  GRANT SELECT ON food_db.* TO 'food_reader'@'%';
+  GRANT SELECT, INSERT, UPDATE, DELETE ON food_db.* TO 'food_app_auth'@'%';
+  GRANT ALL PRIVILEGES ON zabbix.* TO 'zabbix'@'%';
+  FLUSH PRIVILEGES;
+"
+docker stop mysql_temp_reset
+docker rm mysql_temp_reset
+docker-compose start mysql
+sleep 5
+docker-compose restart app ingest

+ 24 - 4
scripts/generate_pdfs.py

@@ -18,10 +18,30 @@ def main():
         with open(md_file, 'r', encoding='utf-8') as f:
             md_content = f.read()
             
-        pdf = MarkdownPdf(toc_level=2)
-        pdf.add_section(Section(md_content))
-        pdf.save(pdf_file)
-        print(f"Saved {os.path.basename(pdf_file)}")
+        import subprocess
+        try:
+            log_info = subprocess.check_output(['git', 'log', '-1', '--format=%H %ad %an %s', '--date=format:%Y/%m/%d %H:%M:%S'], encoding='utf-8').strip()
+            try:
+                tag_info = subprocess.check_output(['git', 'describe', '--tags', '--always'], stderr=subprocess.DEVNULL, encoding='utf-8').strip()
+            except Exception:
+                tag_info = ""
+            
+            if tag_info:
+                git_id = f"$Id$"
+            else:
+                git_id = f"$Id$"
+        except Exception:
+            git_id = "$Id$"
+            
+        md_content = md_content.replace('$Id$', git_id)
+        
+        try:
+            pdf = MarkdownPdf(toc_level=2)
+            pdf.add_section(Section(md_content))
+            pdf.save(pdf_file)
+            print(f"Saved {os.path.basename(pdf_file)}")
+        except Exception as e:
+            print(f"WARNING: Could not save {os.path.basename(pdf_file)}. File might be locked or open in a viewer. Error: {e}")
 
 if __name__ == "__main__":
     main()

+ 3 - 0
searxng/settings.yml

@@ -1,3 +1,5 @@
+use_default_settings: true
+
 search:
   formats:
     - html
@@ -6,3 +8,4 @@ server:
   port: 8080
   bind_address: "0.0.0.0"
   secret_key: "local_food_ai_secret"
+

+ 2 - 2
snmp_notifier.py

@@ -2,8 +2,8 @@ import os
 import socket
 
 class SNMPNotifier:
-    def __init__(self, target_host='192.168.130.170', target_port=162):
-        self.target_host = target_host
+    def __init__(self, target_host=None, target_port=162):
+        self.target_host = target_host or os.environ.get('ZABBIX_HOST', '192.168.130.170')
         self.target_port = target_port
         self.user = 'zabbix_snmp'
         self.auth_key = 'authkey123'