# ObsiGate **Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive. [![Version](https://img.shields.io/badge/Version-1.4.0-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) ``` ┌─────────────────────────────────────────────────────────┐ │ [🔍 Recherche...] [☀/🌙 Thème] ObsiGate │ ├──────────────┬──────────────────────────────────────────┤ │ SIDEBAR │ CONTENT AREA │ │ ▼ Recettes │ 📄 Titre du fichier │ │ 📁 Soupes │ Tags: #recette #rapide │ │ 📄 Pizza │ [Contenu Markdown rendu] │ │ ▼ IT │ │ │ 📁 Docker │ │ │ Tags Cloud │ │ └──────────────┴──────────────────────────────────────────┘ ``` --- ## 📋 Table des matières - [Fonctionnalités](#-fonctionnalités) - [Architecture](#-architecture) - [Prérequis](#-prérequis) - [Installation rapide](#-installation-rapide) - [Configuration détaillée](#-configuration-détaillée) - [Variables d'environnement](#-variables-denvironnement) - [🔒 Authentification](#-authentification) - [Ajouter une nouvelle vault](#-ajouter-une-nouvelle-vault) - [Build multi-platform](#-build-multi-platform) - [Utilisation](#-utilisation) - [API](#-api) - [Performance](#-performance) - [Dépannage](#-dépannage) - [Stack technique](#-stack-technique) - [Changelog](#-changelog) --- ## ✨ Fonctionnalités - **🗂️ Multi-vault** : Visualisez plusieurs vaults Obsidian simultanément - **🌳 Navigation arborescente** : Parcourez vos dossiers et fichiers dans la sidebar - **🔍 Recherche avancée** : Moteur TF-IDF avec normalisation des accents, snippets surlignés, facettes, pagination et tri - **💡 Autocomplétion intelligente** : Suggestions de fichiers, tags et historique avec navigation clavier - **🧩 Syntaxe de requête** : Opérateurs `tag:`, `#`, `vault:`, `title:`, `path:` avec chips visuels - **📜 Historique de recherche** : Persisté en localStorage (max 50 entrées, LIFO, dédupliqué) - **🏷️ Tag cloud** : Filtrage par tags extraits des frontmatters YAML - **🔗 Wikilinks** : Les `[[liens internes]]` Obsidian sont cliquables - **🖼️ Images Obsidian** : Support complet des syntaxes d'images Obsidian avec résolution intelligente - **🎨 Syntax highlight** : Coloration syntaxique des blocs de code - **🌓 Thème clair/sombre** : Toggle persisté en localStorage - **📡 Synchronisation temps réel** : Surveillance automatique des fichiers via watchdog avec mise à jour incrémentale de l'index - **📡 Server-Sent Events** : Notifications SSE pour les changements d'index avec reconnexion automatique - **➕ Gestion dynamique des vaults** : Ajout/suppression de vaults via API sans redémarrage - **🐳 Docker multi-platform** : linux/amd64, linux/arm64, linux/arm/v7, linux/386 - **🔒 Authentification** : JWT + Argon2id, sessions persistantes, contrôle d'accès par vault - **🛡️ Sécurité** : Headers de sécurité (CSP, X-Frame-Options…), protection path traversal, utilisateur non-root - **❤️ Healthcheck** : Endpoint `/api/health` intégré pour Docker et monitoring --- ## 🚀 Prérequis ### Système requis - **Docker** >= 20.10 - **docker-compose** >= 2.0 - **Espace disque** : ~200MB pour l'image Docker ### Systèmes supportés - Linux (Ubuntu, Debian, CentOS, etc.) - macOS (Intel et Apple Silicon) - Windows (avec Docker Desktop) - NAS compatibles Docker (Synology, QNAP, etc.) --- ## ⚡ Installation rapide ### 1. Cloner le dépôt ```bash git clone https://git.dracodev.net/Projets/ObsiGate.git cd ObsiGate ``` ### 2. Configurer vos vaults Éditez le fichier `docker-compose.yml` pour ajouter vos vaults Obsidian : ```yaml volumes: - /chemin/absolu/vers/votre/vault:/vaults/NomDeVotreVault:ro ``` > **Important** : Le chemin doit être absolu et le volume en lecture seule (`:ro`) ### 3. Lancer l'application ```bash # Build local de l'image + démarrage docker-compose up -d --build # Vérifier les logs docker-compose logs -f obsigate ``` > **Note** : ObsiGate est construit localement depuis le `Dockerfile` du projet. Sans build local, Docker essaiera de télécharger une image distante `obsigate:latest` qui n'existe pas forcément. ### 4. Accéder à l'interface Ouvrez votre navigateur sur : **http://localhost:2020** --- ## ⚙️ Configuration détaillée ### Étape 1 : Préparation des vaults 1. **Localisez vos vaults Obsidian** sur votre système 2. **Notez les chemins absolus** vers chaque dossier `.obsidian` 3. **Vérifiez les permissions** : Docker doit pouvoir lire ces dossiers ### Étape 2 : Configuration docker-compose.yml Voici un exemple complet : ```yaml services: obsigate: build: context: . image: obsigate:latest container_name: obsigate restart: unless-stopped ports: - "2020:8080" # Port local 2020 → Port conteneur 8080 volumes: # Exemples de vaults (adaptez à vos chemins) - /home/user/Documents/Obsidian-Recettes:/vaults/Recettes:ro - /home/user/Documents/Obsidian-IT:/vaults/IT:ro - /home/user/Documents/Obsidian-Perso:/vaults/Perso:ro environment: # Configuration des vaults - VAULT_1_NAME=Recettes - VAULT_1_PATH=/vaults/Recettes - VAULT_2_NAME=IT - VAULT_2_PATH=/vaults/IT - VAULT_3_NAME=Perso - VAULT_3_PATH=/vaults/Perso ``` ### Étape 3 : Construction de l'image ```bash # Build l'image Docker docker build -t obsigate:latest . # Puis démarrer le service docker-compose up -d --build # Ou utilisez le script de build multi-platform chmod +x build.sh ./build.sh ``` > **Compatibilité Docker** : l'image utilise la variante minimale de `uvicorn` et une version de `fastapi` compatible (`0.110.3`) afin d'éviter certaines dépendances optionnelles natives (`watchfiles`, `uvloop`, `httptools`, `fastapi-cli`, etc.) qui peuvent échouer au build sur certaines plateformes comme Alpine, ARM ou i386. --- ## 🌍 Variables d'environnement Les vaults sont configurées par paires de variables `VAULT_N_NAME` / `VAULT_N_PATH` (N = 1, 2, 3…) : | Variable | Description | Exemple | |----------|-------------|---------| | `VAULT_1_NAME` | Nom affiché de la vault | `Recettes` | | `VAULT_1_PATH` | Chemin dans le conteneur | `/vaults/Obsidian-RECETTES` | | `VAULT_1_ATTACHMENTS_PATH` | Chemin relatif vers le dossier d'attachements (optionnel) | `06_Boite_a_Outils/6.2_Attachments` | | `VAULT_1_SCAN_ATTACHMENTS` | Activer le scan d'images au démarrage (optionnel, défaut: true) | `true` | | `VAULT_2_NAME` | Nom affiché de la vault | `IT` | | `VAULT_2_PATH` | Chemin dans le conteneur | `/vaults/Obsidian_IT` | **Règles de nommage :** - Utilisez uniquement des lettres, chiffres et tirets - Pas d'espaces ou caractères spéciaux - Le nom doit correspondre au chemin dans le conteneur --- ## 🔒 Authentification > **Désactivée par défaut** — Compatible avec toutes les installations existantes. ObsiGate supporte un système d'authentification optionnel basé sur **JWT + Argon2id** avec contrôle d'accès par vault. ### Activer l'authentification Dans `docker-compose.yml`, décommentez les variables d'auth : ```yaml environment: # ... vaults ... - OBSIGATE_AUTH_ENABLED=true # - OBSIGATE_ADMIN_USER=admin # Défaut: admin # - OBSIGATE_ADMIN_PASSWORD= # Vide = mot de passe auto-généré (voir logs) # - OBSIGATE_SECURE_COOKIES=false # true si derrière un reverse-proxy HTTPS ``` ### Premier démarrage Si aucun utilisateur n'existe, ObsiGate crée automatiquement un compte admin et **affiche le mot de passe dans les logs** : ```bash docker-compose logs obsigate | grep -A4 "PREMIER" ``` ``` ============================================================ PREMIER DÉMARRAGE — Compte admin créé automatiquement Utilisateur : admin Mot de passe : xK9mQ3pLr7wN2jT5 CHANGEZ CE MOT DE PASSE dès la première connexion ! ============================================================ ``` ### Gestion des utilisateurs via CLI ```bash # Créer un utilisateur docker exec obsigate python backend/create_admin.py create alice MonMotDePasse --role user --vaults Recettes IT # Créer un admin avec accès total docker exec obsigate python backend/create_admin.py create bob SecretPass --role admin --vaults "*" # Lister les utilisateurs docker exec obsigate python backend/create_admin.py list # Supprimer un utilisateur docker exec obsigate python backend/create_admin.py delete alice ``` ### Interface d'administration Lorsqu'un compte **admin** est connecté, une icône 🛡️ apparaît dans le header. Cliquer dessus ouvre le panneau d'administration permettant de : - Lister tous les utilisateurs - Créer / modifier / supprimer des utilisateurs - Assigner les vaults accessibles par utilisateur - Activer/désactiver des comptes ### Contrôle d'accès par vault | Valeur vaults | Accès | |---------------|-------| | `["*"]` | Toutes les vaults (y compris futures) — défaut admin | | `["Recettes", "IT"]` | Uniquement ces vaults | | `[]` | Aucun accès | ### Variables d'environnement d'auth | Variable | Description | Défaut | |----------|-------------|--------| | `OBSIGATE_AUTH_ENABLED` | Activer l'authentification | `false` | | `OBSIGATE_ADMIN_USER` | Nom de l'admin auto-créé | `admin` | | `OBSIGATE_ADMIN_PASSWORD` | Mot de passe admin (vide = auto-généré) | *(auto)* | | `OBSIGATE_SECURE_COOKIES` | Cookie `Secure` (HTTPS uniquement) | `false` | ### Volume pour la persistance Les données d'auth (`users.json`, `secret.key`) sont stockées dans `/app/data`. Ajoutez un volume pour les persister : ```yaml volumes: # vaults... - ./data:/app/data # Persistance des utilisateurs et clé JWT ``` --- ## ➕ Ajouter une nouvelle vault ### Méthode 1 : Édition directe 1. **Arrêtez le conteneur** : ```bash docker-compose down ``` 2. **Ajoutez un volume** dans `docker-compose.yml` : ```yaml volumes: - /nouveau/chemin/vault:/vaults/NouvelleVault:ro ``` 3. **Ajoutez les variables d'environnement** : ```yaml environment: - VAULT_4_NAME=NouvelleVault - VAULT_4_PATH=/vaults/NouvelleVault ``` 4. **Redémarrez** : ```bash docker-compose up -d --build ``` ### Méthode 2 : Hot-reload (recommandé) 1. **Ajoutez le volume et les variables** comme ci-dessus 2. **Appliquez les changements** : ```bash docker-compose up -d --build ``` 3. **Rechargez l'index** via l'interface ou l'API : ```bash curl http://localhost:2020/api/index/reload ``` ### Méthode 3 : API dynamique (sans redémarrage) Ajoutez une vault à chaud via l'API (le volume doit déjà être monté) : ```bash curl -X POST http://localhost:2020/api/vaults/add \ -H "Content-Type: application/json" \ -d '{"name": "NouvelleVault", "path": "/vaults/NouvelleVault"}' ``` Supprimez une vault : ```bash curl -X DELETE http://localhost:2020/api/vaults/NouvelleVault ``` --- ## 🔨 Build multi-platform Pour les architectures multiples (Raspberry Pi, NAS, etc.) : ```bash # Rendre le script exécutable chmod +x build.sh # Lancer le build multi-platform ./build.sh ``` **Architectures supportées :** - `linux/amd64` (PC, serveurs standards) - `linux/arm64` (Raspberry Pi 4, Apple Silicon, NAS modernes) - `linux/arm/v7` (Raspberry Pi 3, anciens NAS) - `linux/386` (Systèmes 32-bit legacy) **Pour un build local uniquement :** ```bash docker buildx build --platform linux/amd64 --load -t obsigate:latest . ``` --- ## �️ Rendu d'images Obsidian ObsiGate supporte **toutes les syntaxes d'images Obsidian** avec un système de résolution intelligent multi-stratégies. ### Syntaxes supportées 1. **Standard Markdown avec attributs HTML** (compatible Obsidian) : ```markdown [](https://example.com) ``` 2. **Wiki-link embed avec chemin complet** : ```markdown ![[06_Boite_a_Outils/6.2_Attachments/image.svg]] ``` 3. **Wiki-link embed avec nom de fichier uniquement** : ```markdown ![[image.svg]] ``` 4. **Markdown standard** : ```markdown ![alt text](path/to/image.png) ``` ### Résolution intelligente des chemins ObsiGate utilise 7 stratégies de résolution par ordre de priorité : 1. **Chemin absolu** : Si le chemin est absolu et existe 2. **Dossier d'attachements configuré** : Via `VAULT_N_ATTACHMENTS_PATH` 3. **Index de démarrage (match unique)** : Recherche par nom de fichier dans l'index 4. **Même répertoire** : Relatif au fichier markdown courant 5. **Racine du vault** : Relatif à la racine du vault 6. **Index de démarrage (match le plus proche)** : Si plusieurs fichiers portent le même nom 7. **Fallback** : Affiche un placeholder stylisé `[image not found: filename.ext]` ### Configuration Pour optimiser la résolution, configurez le dossier d'attachements de chaque vault : ```yaml environment: - VAULT_1_NAME=MonVault - VAULT_1_PATH=/vaults/MonVault - VAULT_1_ATTACHMENTS_PATH=Assets/Images # Chemin relatif - VAULT_1_SCAN_ATTACHMENTS=true # Activer le scan (défaut) ``` ### Rescan manuel Pour rescanner les images d'un vault après ajout/suppression : ```bash curl -X POST http://localhost:2020/api/attachments/rescan/MonVault ``` --- ## �📖 Utilisation ### Interface web 1. **Navigation** : Cliquez sur les vaults dans la sidebar pour les développer 2. **Recherche** : Utilisez la barre de recherche pour chercher dans toutes les vaults 3. **Tags** : Cliquez sur les tags pour filtrer les contenus 4. **Wikilinks** : Les liens `[[page]]` sont cliquables et navigables 5. **Images** : Toutes les syntaxes d'images Obsidian sont rendues automatiquement 6. **Thème** : Basculez entre thème clair/sombre avec l'icône 🌙/☀️ ### Raccourcis clavier | Action | Raccourci | |--------|-----------| | Recherche | `Ctrl + K` ou `/` | | Toggle thème | `Ctrl + T` | | Focus recherche | `Esc` | --- ## 🔌 API ObsiGate expose une API REST complète : | Endpoint | Description | Méthode | Auth | |----------|-------------|---------|------| | `/api/health` | Health check (status, version, stats) | GET | Non | | `/api/auth/status` | Statut auth (activé, utilisateurs présents) | GET | Non | | `/api/auth/login` | Connexion (retourne access token + cookie refresh) | POST | Non | | `/api/auth/refresh` | Renouveler l'access token via cookie refresh | POST | Cookie | | `/api/auth/logout` | Déconnexion + révocation refresh token | POST | Oui | | `/api/auth/me` | Infos utilisateur courant | GET | Oui | | `/api/auth/change-password` | Changer son mot de passe | POST | Oui | | `/api/auth/admin/users` | Lister / créer des utilisateurs | GET/POST | Admin | | `/api/auth/admin/users/{u}` | Modifier / supprimer un utilisateur | PATCH/DELETE | Admin | | `/api/vaults` | Liste des vaults (filtrée par permissions) | GET | Oui | | `/api/browse/{vault}?path=` | Navigation dans les dossiers | GET | Oui | | `/api/file/{vault}?path=` | Contenu rendu d'un fichier | GET | Oui | | `/api/file/{vault}/raw?path=` | Contenu brut d'un fichier | GET | Oui | | `/api/file/{vault}/download?path=` | Téléchargement d'un fichier | GET | Oui | | `/api/file/{vault}/save?path=` | Sauvegarder un fichier | PUT | Oui | | `/api/file/{vault}?path=` | Supprimer un fichier | DELETE | Oui | | `/api/search/advanced` | Recherche avancée TF-IDF | GET | Oui | | `/api/suggest` / `/api/tags/suggest` | Autocomplétion | GET | Oui | | `/api/tags?vault=` | Tags uniques avec compteurs | GET | Oui | | `/api/index/reload` | Force un re-scan des vaults | GET | Admin | | `/api/events` | Flux SSE temps réel | GET | Oui | | `/api/vaults/add` / `/api/vaults/{name}` | Gestion dynamique des vaults | POST/DELETE | Admin | | `/api/image/{vault}?path=` | Servir une image | GET | Oui | | `/api/config` | Lire / écrire la configuration | GET/POST | Oui/Admin | | `/api/diagnostics` | Statistiques index et mémoire | GET | Admin | > Quand `OBSIGATE_AUTH_ENABLED=false`, tous les endpoints sont accessibles sans token. > Tous les endpoints exposent des schémas Pydantic documentés. La doc interactive est disponible sur `/docs` (Swagger UI). **Exemple d'utilisation :** ```bash # Health check curl http://localhost:2020/api/health # Lister les vaults curl http://localhost:2020/api/vaults # Recherche simple (legacy) curl "http://localhost:2020/api/search?q=recette&vault=all" # Recherche avancée avec TF-IDF, facettes et pagination curl "http://localhost:2020/api/search/advanced?q=recette%20tag:cuisine&vault=all&limit=20&offset=0&sort=relevance" # Autocomplétion de titres curl "http://localhost:2020/api/suggest?q=piz&vault=all" # Autocomplétion de tags curl "http://localhost:2020/api/tags/suggest?q=rec&vault=all" # Obtenir un fichier curl "http://localhost:2020/api/file/Recettes?path=pizza.md" ``` --- ## 🔍 Recherche avancée ### Syntaxe de requête | Opérateur | Description | Exemple | |-----------|-------------|---------| | `tag:` | Filtrer par tag | `tag:recette docker` | | `#` | Raccourci tag | `#linux serveur` | | `vault:` | Filtrer par vault | `vault:IT kubernetes` | | `title:` | Filtrer par titre | `title:pizza` | | `path:` | Filtrer par chemin | `path:recettes/soupes` | | `"phrase exacte"` | Recherche de phrase | `tag:"multi mots"` | Les opérateurs sont combinables : `tag:linux vault:IT serveur web` recherche "serveur web" dans le vault IT avec le tag linux. ### Raccourcis clavier | Raccourci | Action | |-----------|--------| | `Ctrl+K` / `Cmd+K` | Focaliser la barre de recherche | | `/` | Focaliser la recherche (hors champ texte) | | `↑` / `↓` | Naviguer dans les suggestions | | `Enter` | Sélectionner la suggestion active ou lancer la recherche | | `Escape` | Fermer les suggestions / quitter la recherche | ### Fonctionnalités - **TF-IDF** : Scoring basé sur la fréquence des termes pondérée par l'inverse de la fréquence documentaire - **Boost titre** : Les correspondances dans le titre reçoivent un score 3× supérieur - **Normalisation des accents** : `resume` trouve `résumé`, `elephant` trouve `éléphant` - **Snippets surlignés** : Les termes trouvés sont encadrés par `` dans les extraits - **Facettes** : Compteurs par vault et par tag dans les résultats - **Pagination** : Navigation par pages de 50 résultats - **Tri** : Par pertinence (TF-IDF) ou par date de modification - **Chips visuels** : Les filtres actifs sont affichés comme des chips colorés supprimables - **Historique** : Les 50 dernières recherches sont mémorisées en localStorage --- ## 🔧 Dépannage ### Problèmes courants **Port déjà utilisé :** ```bash # Vérifier qui utilise le port sudo netstat -tulpn | grep 2020 # Changer le port dans docker-compose.yml ports: - "2021:8080" ``` **Vault non trouvée :** - Vérifiez que les chemins sont absolus - Assurez-vous que les permissions permettent la lecture - Redémarrez le conteneur après modification **Build échoue :** ```bash # Nettoyer et rebuild docker system prune -f docker-compose down docker build --no-cache -t obsigate:latest . docker-compose up -d ``` **Logs pour debugging :** ```bash # Logs en temps réel docker-compose logs -f obsigate # Logs détaillés docker-compose logs --tail=100 obsigate ``` --- ## ⚡ Performance | Métrique | Estimation | |----------|------------| | **Indexation** | ~1–2s pour 1 000 fichiers markdown | | **Recherche avancée** | < 10ms pour la plupart des requêtes (index inversé + TF-IDF) | | **Résolution wikilinks** | O(1) via table de lookup | | **Mémoire** | ~80–150MB par 1 000 fichiers (contenu capé à 100 KB/fichier) | | **Image Docker** | ~180MB (multi-stage, sans outils de build) | | **CPU** | Non-bloquant ; recherche offloadée sur thread pool dédié | ### Paramètres recommandés par taille de vault | Taille | Fichiers | `search_workers` | `prefix_max_expansions` | `max_content_size` | |--------|----------|-------------------|--------------------------|---------------------| | Petit | < 500 | 1 | 50 | 100 000 | | Moyen | 500–5 000 | 2 | 50 | 100 000 | | Grand | 5 000+ | 4 | 30 | 50 000 | Ces paramètres sont configurables via l'interface (Settings) ou l'API `/api/config`. ### Optimisations clés (v1.2.0) - **Index inversé avec set-intersection** : la recherche utilise les posting lists pour un retrieval O(k × postings) au lieu de O(N) scan complet - **Prefix matching par recherche binaire** : O(log V + k) au lieu de O(V) scan linéaire du vocabulaire - **ThreadPoolExecutor** : les fonctions de recherche CPU-bound sont offloadées du event loop asyncio - **Race condition guard** : `currentSearchId` + `AbortController` empêchent le rendu de résultats obsolètes - **Progress bar** : barre de progression animée pendant la recherche - **Search timeout** : abandon automatique après 30s (configurable) - **Query time display** : temps serveur affiché dans les résultats (`query_time_ms`) - **Staleness detection fix** : utilisation d'un compteur de génération au lieu de `id(index)` pour détecter les changements d'index ### Optimisations v1.1.0 - **Recherche sans I/O** : le contenu des fichiers est mis en cache dans l'index mémoire - **Scoring multi-facteurs** : titre exact (+20), titre partiel (+10), chemin (+5), tag (+3), fréquence contenu (x1 par occurrence, capé à 10) - **Rendu Markdown singleton** : le renderer mistune est instancié une seule fois - **AbortController** : les requêtes de recherche obsolètes sont annulées côté client - **Debounced icon rendering** : `lucide.createIcons()` est batché via `requestAnimationFrame` --- ## 🛡️ Sécurité - **Path traversal** : tous les endpoints fichier valident que le chemin résolu reste dans la vault - **Utilisateur non-root** : le conteneur Docker tourne sous l'utilisateur `obsigate` - **Volumes read-only** : les vaults sont montées en `:ro` par défaut dans docker-compose --- ## 🏗️ Stack technique - **Backend** : Python 3.11 + FastAPI 0.110 + Uvicorn - **Auth** : python-jose (JWT HS256) + argon2-cffi (Argon2id) - **File Watcher** : watchdog 4.x (inotify natif + fallback polling) - **Frontend** : Vanilla JS + HTML + CSS (zéro framework, zéro build) - **Rendu Markdown** : mistune 3.x - **Image Docker** : python:3.11-slim (multi-stage) - **Stockage utilisateurs** : JSON local (`data/users.json`) — aucune base de données - **Architecture** : SPA + API REST + SSE --- ## 🏠 Architecture ``` ┌─────────────────┐ ┌─────────────────────────────────────────┐ │ Navigateur │◄───►│ FastAPI (backend/main.py) │ │ (SPA) │ REST │ │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ app.js │ │ │ indexer.py │ │ search.py │ │ │ style.css │ │ │ (scan+cache)│ │ (in-memory) │ │ │ index.html │ │ └───────┬──────┘ └──────┬───────┘ │ └─────────────────┘ │ │ │ │ │ └──────┬───────┘ │ │ │ │ │ ┌────────┴─────────┐ │ │ │ Index en mémoire │ │ │ │ (fichiers, tags, │ │ │ │ contenu, lookup)│ │ │ └──────────────────┘ │ └─────────────────────────────────────────┘ ┌───────────────────────────────┐ │ Filesystem (vaults montées) │ │ /vaults/Recettes (ro) │ │ /vaults/IT (ro) │ └───────────────────────────────┘ ``` **Flux de données :** 1. Au démarrage, `indexer.py` scanne tous les vaults en parallèle (thread pool) 2. Le contenu, les tags (YAML + inline) et les métadonnées sont mis en cache en mémoire 3. Une table de lookup O(1) est construite pour la résolution des wikilinks 4. `watcher.py` démarre la surveillance des fichiers (watchdog natif ou polling) 5. Les modifications détectées déclenchent une mise à jour incrémentale de l'index 6. Les changements sont notifiés au frontend via Server-Sent Events (SSE) 7. Les requêtes de recherche utilisent l'index en mémoire (zéro I/O disque) 8. Le frontend SPA communique via REST + SSE et gère l'état côté client --- ## 📝 Développement ### Structure du projet ``` ObsiGate/ ├── backend/ # API FastAPI │ ├── main.py # Endpoints, Pydantic models, rendu markdown │ ├── indexer.py # Scan des vaults, index en mémoire, lookup table │ ├── search.py # Moteur de recherche fulltext avec scoring │ ├── watcher.py # Surveillance fichiers (watchdog + debounce) │ ├── auth/ # Module d'authentification │ │ ├── password.py # Argon2id hashing │ │ ├── jwt_handler.py # Tokens JWT + révocation │ │ ├── user_store.py # CRUD users.json (atomique) │ │ ├── middleware.py # Dépendances FastAPI auth │ │ └── router.py # Endpoints /api/auth/* │ ├── create_admin.py # CLI gestion utilisateurs │ └── requirements.txt ├── frontend/ # Interface web (Vanilla JS, zéro framework) │ ├── index.html # Page SPA + écran de login │ ├── app.js # Logique SPA, AuthManager, AdminPanel │ └── style.css # Styles (CSS variables, thèmes, responsive) ├── data/ # Données persistantes (créé au démarrage) │ ├── users.json # Utilisateurs (hashés Argon2id) │ └── secret.key # Clé secrète JWT (512 bits) ├── Dockerfile # Multi-stage, healthcheck, non-root ├── docker-compose.yml # Déploiement avec healthcheck et auth env vars ├── build.sh # Build multi-platform (amd64/arm64/arm/v7/i386) └── CONTRIBUTING.md # Guide de contribution ``` ### Contribuer Voir [CONTRIBUTING.md](CONTRIBUTING.md) pour les détails. --- ## 📄 Licence Ce projet est sous licence **MIT** - voir le fichier [LICENSE](LICENSE) pour les détails. --- ## 🤝 Support - **Issues** : [git.dracodev.net/Projets/ObsiGate/issues](https://git.dracodev.net/Projets/ObsiGate/issues) - **Documentation** : [git.dracodev.net/Projets/ObsiGate/wiki](https://git.dracodev.net/Projets/ObsiGate/wiki) - **Auteur** : Bruno Beloeil --- ## 📝 Changelog ### v1.4.0 (2026) **Authentification & Contrôle d'accès** - Système d'authentification JWT (HS256) optionnel — désactivé par défaut, activation via `OBSIGATE_AUTH_ENABLED=true` - Hachage des mots de passe **Argon2id** (paramètres OWASP 2024 : time_cost=2, mem=64MB) - Sessions avec **access token (1h)** + **refresh token (7j, HttpOnly cookie)** et révocation - Contrôle d'accès **par vault** : chaque utilisateur accède uniquement à ses vaults assignées - Bootstrap automatique du compte admin au premier démarrage (mot de passe généré et affiché dans les logs) - CLI de gestion des utilisateurs : `python backend/create_admin.py create|list|delete` - Interface d'administration dans la SPA : création/modification/suppression d'utilisateurs avec sélection de vaults - Protection brute-force : verrouillage 15 min après 5 tentatives échouées - Réponses en temps constant sur le login pour éviter l'énumération d'utilisateurs - Token de rafraîchissement révocable (persisté dans `data/revoked_tokens.json`) - Clé secrète JWT auto-générée (512 bits) et persistée dans `data/secret.key` **Sécurité (headers)** - `Content-Security-Policy` sur toutes les réponses - `X-Frame-Options: SAMEORIGIN` - `X-Content-Type-Options: nosniff` - `X-XSS-Protection: 1; mode=block` - `Referrer-Policy: strict-origin-when-cross-origin` **Frontend** - Écran de connexion avec glassmorphism (avant chargement de l'app si auth activée) - Menu utilisateur dans le header (nom affiché, bouton admin, déconnexion) - Rafraîchissement silencieux de l'access token (via cookie refresh) - Compatibilité totale : `OBSIGATE_AUTH_ENABLED=false` → comportement identique à v1.3.x ### v1.3.0 (2025) **Synchronisation temps réel** - Surveillance automatique des fichiers via `watchdog` avec fallback polling automatique - Mise à jour incrémentale de l'index : ajout, modification, suppression et déplacement de fichiers sans rebuild complet - Server-Sent Events (SSE) : endpoint `/api/events` pour notifications temps réel vers le frontend - Badge de synchronisation dans le header avec indicateur visuel (connecté/déconnecté/syncing) - Panel d'événements récents accessible en cliquant sur le badge - Reconnexion SSE automatique avec backoff exponentiel - Toast informatif à chaque mise à jour détectée - Rafraîchissement automatique de la sidebar et du fichier courant si affecté **Gestion dynamique des vaults** - `POST /api/vaults/add` : ajouter une vault à chaud sans redémarrage - `DELETE /api/vaults/{name}` : supprimer une vault de l'index - `GET /api/vaults/status` : statut détaillé (fichiers, tags, état de surveillance) - Les vaults ajoutées sont automatiquement surveillées par le watcher **Configuration watcher** - Nouveaux paramètres dans `config.json` : `watcher_enabled`, `watcher_use_polling`, `watcher_polling_interval`, `watcher_debounce` - Section « Synchronisation automatique » dans la modal de configuration frontend - Toggles pour activer/désactiver la surveillance et le mode polling ### v1.2.0 (2025) **Performance (critique)** - Réécriture du moteur `advanced_search()` : retrieval par set-intersection sur l'index inversé (O(k × postings) au lieu de O(N)) - Prefix matching par recherche binaire sur liste triée de tokens (O(log V + k) au lieu de O(V)) - Offload des fonctions de recherche CPU-bound via `ThreadPoolExecutor` (2 workers par défaut) - Pré-calcul des expansions de préfixe pour éviter les recherches binaires répétées - Fix du bug de staleness : `is_stale()` utilise un compteur de génération au lieu de `id(index)` **Frontend** - Guard contre les race conditions : `currentSearchId` vérifié après chaque `fetch` avant rendu - Barre de progression animée pendant la recherche - Timeout de recherche configurable (30s par défaut) - Longueur minimale de requête configurable (2 caractères par défaut) - Affichage du temps de requête serveur (`query_time_ms`) dans les résultats - Pagination ajoutée sur l'endpoint legacy `/api/search` (params `limit`/`offset`) **Configuration & Diagnostics** - Nouveaux endpoints `GET/POST /api/config` pour la configuration persistante (`config.json`) - Nouveau endpoint `GET /api/diagnostics` (stats index, mémoire, moteur de recherche) - Page de configuration étendue : paramètres frontend (debounce, résultats/page, timeout) et backend (workers, boosts, expansions) - Panel de diagnostics intégré dans la modal de configuration - Boutons « Forcer réindexation » et « Réinitialiser » dans les paramètres ### v1.1.0 (2025) **Sécurité** - Protection path traversal sur tous les endpoints fichier - Utilisateur non-root dans le conteneur Docker - Dockerfile multi-stage (élimination des outils de build) **Performance** - Recherche fulltext en mémoire (zéro I/O disque par requête) - Table de lookup O(1) pour la résolution des wikilinks - Renderer mistune mis en cache (singleton) - Scoring multi-facteurs (titre, chemin, tags, fréquence) - `lucide.createIcons()` batché via `requestAnimationFrame` - `AbortController` sur les requêtes de recherche **Robustesse** - Swap atomique de l'index (thread-safe) pendant le reload - Extraction des tags inline (#tag) depuis le contenu markdown - Modèles Pydantic sur tous les endpoints API - Gestion d'erreurs avec toasts utilisateur (frontend) - États de chargement pour la sidebar et le contenu - Remplacement de `on_event` déprécié par `lifespan` **Infrastructure** - Endpoint `/api/health` pour monitoring - Healthcheck Docker (Dockerfile + docker-compose) - `build.sh` amélioré (variable version, checks, couleurs) **Documentation** - Docstrings complètes sur toutes les fonctions Python - Schémas Pydantic documentés (Swagger UI auto-générée) - README : sections Architecture, Performance, Sécurité, Changelog - CONTRIBUTING.md ajouté ### v1.0.0 (2025) - Version initiale --- *Projet : ObsiGate | Version : 1.4.0 | Dernière mise à jour : 2026*