# ObsiGate **Ultra-light web gateway for your Obsidian vaults** β€” Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. [![Version](https://img.shields.io/badge/Version-2.54.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/) [![CI/CD](https://img.shields.io/badge/CI%2FCD-Gitea_Actions-green.svg)](https://git.dracodev.net/Projets/ObsiGate/actions) ![ObsiGate interface β€” statistics dashboard with vaults, tags and keyboard shortcuts](docs/images/obsigate-home.png) > ObsiGate web interface: multi-vault sidebar, global search, dashboard stats and shortcuts. --- ## πŸ“š Guides Step-by-step **user guides** live in [`docs/GUIDES/`](docs/GUIDES/): | Guide | What it covers | |---|---| | πŸš€ [Getting Started](docs/GUIDES/PRISE_EN_MAIN.md) | First run, interface, navigation, vaults, shortcuts | | πŸ” [Search, PDF, Excel & Excalidraw](docs/GUIDES/RECHERCHE_PDF_EXCALIDRAW.md) | Query syntax, semantic search, PDF/Excel viewers, diagrams | | πŸ€– [AI Assistant & Forge](docs/GUIDES/ASSISTANT_IA_FORGE.md) | Providers, AI editor, BooksLM, Forge, `@` / `/` commands | | πŸ“ [Editing & Collaboration](docs/GUIDES/COLLABORATION.md) | Simultaneous editing, remote cursors, persistence | | πŸ“± [PWA & Offline](docs/GUIDES/PWA_HORS_LIGNE.md) | Install as an app, offline cache, sync queue, push | | πŸ”Œ [REST API](docs/GUIDES/API_REST.md) | Authentication, API keys, endpoints, `curl` examples, SSE | | 🧩 [MCP Server](docs/GUIDES/MCP.md) | Connect Claude Desktop, Cursor, Cline… to your vaults | | πŸ”’ [Auth & Security](docs/GUIDES/AUTHENTIFICATION_SECURITE.md) | Users, MFA, per-vault permissions, hardening | | 🐳 [Docker Deployment](docs/GUIDES/DEPLOIEMENT_DOCKER.md) | `docker-compose`, volumes, reverse proxy, updates | | πŸ–₯️ [Desktop (Tauri)](docs/GUIDES/DESKTOP.md) | Install, first run, build from source, troubleshooting | > All guides are currently written in **French**. See the full index: > [`docs/GUIDES/README.md`](docs/GUIDES/README.md). --- ## πŸ“‹ Table of Contents - ✨ [Features](#features) - πŸ“š [Guides](#guides) - πŸš€ [Prerequisites](#prerequisites) - ⚑ [Quick Installation](#quick-installation) - βš™οΈ [Detailed Configuration](#detailed-configuration) - 🌍 [Environment Variables](#environment-variables) - πŸ”’ [Authentication](#authentication) - βž• [Adding a New Vault](#adding-a-new-vault) - πŸ”¨ [Build & Deployment with build.sh](#build--deployment-with-buildsh) - πŸ–ΌοΈ [Obsidian Image Rendering](#obsidian-image-rendering) - πŸ–₯️ [Desktop (Tauri) β€” Native Application](#desktop-tauri--native-application) - πŸ“– [Usage](#usage) - πŸ‘₯ [Real-time Collaboration](#real-time-collaboration) - πŸ”Œ [API](#api) - πŸ” [Advanced Search](#advanced-search) - πŸ›‘οΈ [Security](#security) - ⚑ [Performance](#performance) - πŸ”§ [Troubleshooting](#troubleshooting) - πŸ—οΈ [Tech Stack](#tech-stack) - 🏠 [Architecture](#architecture) - πŸ“ [Development](#development) - πŸ“„ [License](#license) - 🀝 [Support](#support) - πŸ“ [Changelog](#changelog) --- ## ✨ Features - **πŸ€– Integrated AI Editor** β€” CodeMirror 6 editor with AI toolbar: improve, correct, translate, generate, custom rewrite, toolbox (list, table, frontmatter, canvas) β€” multi-provider DeepSeek/OpenRouter/Gemini - **🧩 MCP Server & AI Agent** β€” Built-in Model Context Protocol server (`/mcp`) and tool-calling assistant: read, search and edit your vaults from Claude Desktop, Cursor… with two-step confirmations, per-vault permissions, rate limiting and secret redaction ([guide](docs/GUIDES/MCP.md)) - **πŸ‘₯ Real-time Collaboration** β€” Simultaneous editing of the same document (Yjs/CRDT): colored remote cursors, presence indicator, conflict-free merge, automatic reconnection and server-side persistence ([details](docs/features/collaboration.md)) - **πŸ“– Built-in User Guide** β€” Complete FR/EN help from the Options menu: interface, navigation, search, files, AI, security, API & integrations (OpenAPI, MCP), offline, collaboration, desktop, plus an **Architecture** section with a Mermaid diagram; downloadable as **Markdown** and **PDF** in the current language ([details](docs/features/guide-coverage-105.md)) - **πŸ“± Native Mobile Editor** β€” Touch-optimised editing: floating Markdown toolbar (bold/italic/code/list/link), persistent Paste button (iOS workaround), pinch-zoom font & adjustable height, swipe shortcuts (backlinks / table of contents) and a full-screen reading mode with page navigation ([details](docs/features/mobile-editor.md)) - **πŸ—ΊοΈ Interactive Graph View** β€” Canvas force-directed with Barnes-Hut O(n log n), filters (tag, type), depth, focus mode, navigation history ←→↑, export PNG, preview on hover (Ctrl+click) - **πŸ—‚οΈ Multi-vault** : View multiple Obsidian vaults simultaneously - **🌳 Tree Navigation** : Browse your folders and files in the sidebar; clicking a folder in the tree opens a dedicated **navigation tab** β€” path, recents, clickable subfolders, Vaults Β· Tags Β· Extensions facets, Pertinence/Date sorting and save-as-search β€” coexisting with your open files - **πŸ” Advanced Search** : TF-IDF search engine with French stemming, accent normalization, highlighted snippets, facets, pagination, and sorting β€” plus an optional **semantic search** (embeddings via `all-MiniLM-L6-v2`, hybrid TF-IDF + RRF fusion) toggled with `~` ([details](docs/features/semantic-search.md)) - **πŸ’‘ Smart Autocomplete** : Suggestions for files, tags, and history with keyboard navigation - **🧩 Query Syntax** : Operators `tag:`, `#`, `vault:`, `title:`, `path:`, `ext:` with visual chips - **πŸ“œ Search History** : Persisted in localStorage (max 50 entries, LIFO, deduplicated) - **🏷️ Tag Cloud** : Filtering by tags extracted from YAML frontmatters - **πŸ”— Wikilinks** : `[[internal links]]` from Obsidian are clickable - **πŸ–ΌοΈ Obsidian Images** : Full support for all Obsidian image syntaxes with intelligent resolution - **🎬 Audio & video** : Built-in HTML5 players (`.mp3 .wav .flac .mp4 .webm`…) with HTTP Range streaming (play, seek, fullscreen) and **persistent playback** (floating mini-player / mini video window, return to media or stop anytime, lock-screen controls via Media Session), falling back to download when the format is not playable in the browser - **🎨 Excalidraw Diagrams** : Native viewer/editor for `.excalidraw` and `.excalidraw.md` files (sandboxed iframe, autosave, dark/light theme, diagram text indexed for search) - **πŸ“Š Excel Spreadsheets** : `.xlsx` and `.xlsm` files open in a dedicated viewer β€” one table per sheet with tabs, A1 headers and inline cell editing (`PUT /api/file/{vault}/xlsx/save`, automatic backup, atomic write), plus download of the original file. The viewer renders fonts, colors, merged cells and frozen panes, offers keyboard navigation and shortcuts (`Ctrl+S`, `Delete`, `F2`, `Ctrl+Home/End`, `PgUp/PgDn`, `Ctrl+arrows`), a formula bar with function suggestions, an editable Name Box ("go to" `A1:B3`), a range clipboard (copy/cut/paste a block, from or to Excel), a **Format** menu (bold/italic/underline, alignments, font & fill colours, number formats, merges, frozen panes, column width/row height β€” `PUT /api/file/{vault}/xlsx/style`), sort/filter/find across every sheet, CSV/Markdown/HTML export and printing (selection or sheet), sheet & row/column structure editing and a workbook dashboard (named ranges, charts/pivot detection, per-sheet stats); `.csv` is edited in the same grid (RFC 4180) while `.xls` and `.ods` open read-only. Workbooks holding elements ObsiGate cannot preserve (cached values, slicers, form controls, signature…) show a **warning** and ask for confirmation before saving; a value starting with `=` or `@` is stored as text unless the `f(x)` toggle is enabled, and concurrent writes from another process are detected (`If-Match` β†’ "Retry"). The AI assistant can list sheets, dump a bounded table to its context, search the workbook, analyze a range, update cells and append rows β€” on `.xlsx`, `.xlsm` and `.csv` - **🎨 Syntax Highlight** : Syntax highlighting for code blocks - **πŸŒ“ Light/Dark Theme** : Toggle persisted in localStorage - **πŸ“‘ Real-time Sync** : Automatic file monitoring via watchdog with incremental index updates - **πŸ“‘ Server-Sent Events** : SSE notifications for index changes with automatic reconnection - **βž• Dynamic Vault Management** : Add/remove vaults via API without restart - **🐳 Multi-platform Docker** : linux/amd64, linux/arm64, linux/arm/v7, linux/386 - **πŸ”’ Authentication** : JWT + Argon2id, persistent sessions, per-vault access control - **πŸ›‘οΈ Security** : Rate limiting, audit log, automatic backup, secret redaction, CSP headers, path traversal protection, non-root user - **⚑ Performance** : GZip compression, Cache-Control immutable, incremental inverted index, search with no disk I/O - **❀️ Healthcheck** : `/api/health` endpoint integrated for Docker and monitoring --- ## πŸš€ Prerequisites ### System Requirements - **Docker** >= 20.10 - **docker-compose** >= 2.0 - **Disk space** : ~200MB for the Docker image ### Supported Systems - Linux (Ubuntu, Debian, CentOS, etc.) - macOS (Intel and Apple Silicon) - Windows (with Docker Desktop) - Docker-compatible NAS (Synology, QNAP, etc.) --- ## ⚑ Quick Installation ### 1. Clone the Repository ```bash git clone https://git.dracodev.net/Projets/ObsiGate.git cd ObsiGate ``` ### 2. Configure Your Vaults and Secrets Edit the `docker-compose.yml` file to add your Obsidian vaults: ```yaml volumes: - /absolute/path/to/your/vault:/vaults/YourVaultName:ro ``` > **Important** : The path must be absolute and the volume must be read-only (`:ro`) Create your `.env` file for authentication and secrets: ```bash cp .env.example .env # Edit .env to configure your passwords and options ``` > **Never commit `.env`!** It is in `.gitignore`. Use `.env.example` as a reference. ### 3. Launch the Application ```bash # Make the script executable (once) chmod +x build.sh # Build + deploy in one command ./build.sh ``` > **That's it!** `build.sh` checks Docker, validates your volumes, builds the image, and starts the container automatically. > > Useful options: `./build.sh --help` for help, `./build.sh --cache` for a faster rebuild, `./build.sh --build-only` to build without starting. ### 4. Access the Interface Open your browser at: **http://localhost:2020** --- ## βš™οΈ Detailed Configuration ### Step 1: Prepare Your Vaults 1. **Locate your Obsidian vaults** on your system 2. **Note the absolute paths** to each `.obsidian` folder 3. **Check permissions** : Docker must be able to read these folders ### Step 2: Configure docker-compose.yml Here's a complete example: ```yaml services: obsigate: build: context: . image: obsigate:latest container_name: obsigate restart: unless-stopped ports: - "2020:8080" # Local port 2020 β†’ Container port 8080 volumes: # Vault examples (adapt to your paths) - /home/user/Documents/Obsidian-Recipes:/vaults/Recipes:ro - /home/user/Documents/Obsidian-IT:/vaults/IT:ro - /home/user/Documents/Obsidian-Personal:/vaults/Personal:ro # Auth data persistence - ./data:/app/data environment: # Vault configuration - VAULT_1_NAME=Recipes - VAULT_1_PATH=/vaults/Recipes - VAULT_2_NAME=IT - VAULT_2_PATH=/vaults/IT - VAULT_3_NAME=Personal - VAULT_3_PATH=/vaults/Personal # Auth (secrets are in .env) - OBSIGATE_AUTH_ENABLED=true - OBSIGATE_ADMIN_USER=admin env_file: - .env # Contains OBSIGATE_ADMIN_PASSWORD and other secrets ``` ### Step 3: Build & Deploy ```bash # Use the automated build script (recommended) chmod +x build.sh ./build.sh ``` The `build.sh` script handles everything: prerequisite checks, volume validation, image building, and startup. **Manual alternative:** ```bash docker compose build --no-cache docker compose up -d ``` > **Docker Compatibility** : The image uses the minimal `uvicorn` variant and a compatible `fastapi` version (`0.110.3`) to avoid certain optional native dependencies (`watchfiles`, `uvloop`, `httptools`, `fastapi-cli`, etc.) that may fail to build on some platforms like Alpine, ARM, or i386. --- ## 🌍 Environment Variables Vaults are configured using pairs of `VAULT_N_NAME` / `VAULT_N_PATH` variables (N = 1, 2, 3…): | Variable | Description | Example | |----------|-------------|---------| | `VAULT_1_NAME` | Display name of the vault | `Recipes` | | `VAULT_1_PATH` | Path inside the container | `/vaults/Obsidian-RECIPES` | | `VAULT_1_ATTACHMENTS_PATH` | Relative path to the attachments folder (optional) | `06_Toolbox/6.2_Attachments` | | `VAULT_1_SCAN_ATTACHMENTS` | Enable image scanning on startup (optional, default: true) | `true` | | `VAULT_2_NAME` | Display name of the vault | `IT` | | `VAULT_2_PATH` | Path inside the container | `/vaults/Obsidian_IT` | **Naming rules:** - Use only letters, numbers, and hyphens - No spaces or special characters - The name must match the path inside the container --- ## πŸ”’ Authentication > **Disabled by default** β€” Compatible with all existing installations. ObsiGate supports an optional authentication system based on **JWT + Argon2id** with per-vault access control. ### Enable Authentication 1. **Copy the `.env` template** : ```bash cp .env.example .env ``` 2. **Edit `.env`** and uncomment/configure the variables : ```bash OBSIGATE_AUTH_ENABLED=true OBSIGATE_ADMIN_USER=admin OBSIGATE_ADMIN_PASSWORD=your_password # Leave empty = auto-generated (see logs) # OBSIGATE_SECURE_COOKIES=false # true if behind HTTPS ``` 3. **In `docker-compose.yml`**, make sure you have : ```yaml env_file: - .env ``` > **Never put passwords in `docker-compose.yml`!** Always use `.env`. ### First Startup If no user exists, ObsiGate automatically creates an admin account and **displays the password in the logs** : ```bash docker-compose logs obsigate | grep -A4 "FIRST" ``` ``` ============================================================ FIRST STARTUP β€” Admin account created automatically Username : admin Password : xK9mQ3pLr7wN2jT5 CHANGE THIS PASSWORD on first login! ============================================================ ``` ### User Management via CLI ```bash # Create a user docker exec obsigate python backend/create_admin.py create alice MyPassword --role user --vaults Recipes IT # Create an admin with full access docker exec obsigate python backend/create_admin.py create bob SecretPass --role admin --vaults "*" # List users docker exec obsigate python backend/create_admin.py list # Delete a user docker exec obsigate python backend/create_admin.py delete alice ``` ### Admin Interface When an **admin** account is logged in, a πŸ›‘οΈ icon appears in the header. Clicking it opens the admin panel allowing you to: - List all users - Create / edit / delete users - Assign accessible vaults per user - Enable/disable accounts ### Per-Vault Access Control | vaults value | Access | |--------------|--------| | `["*"]` | All vaults (including future ones) β€” admin default | | `["Recipes", "IT"]` | Only these vaults | | `[]` | No access | ### Auth Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `OBSIGATE_AUTH_ENABLED` | Enable authentication | `false` | | `OBSIGATE_ADMIN_USER` | Auto-created admin username | `admin` | | `OBSIGATE_ADMIN_PASSWORD` | Admin password (empty = auto-generated) | *(auto)* | | `OBSIGATE_SECURE_COOKIES` | `Secure` cookie (HTTPS only) | `false` | | `OBSIGATE_ACCESS_TOKEN_TTL` | JWT token lifetime (seconds) | `3600` | | `OBSIGATE_REFRESH_TOKEN_TTL` | Refresh token lifetime (seconds) | `2592000` | | `OBSIGATE_LOGIN_MAX_ATTEMPTS` | Max login attempts per IP | `10` | | `OBSIGATE_ACCOUNT_MAX_ATTEMPTS` | Max login attempts per account | `10` | | `OBSIGATE_LOGIN_WINDOW_SECONDS` | Rate limiting window (seconds) | `900` | | `OBSIGATE_TRUST_PROXY` | Trust `X-Forwarded-For` for the client IP (reverse proxy) | `false` | | `OBSIGATE_WEBHOOK_ALLOW_HTTP` | Allow non-HTTPS webhook targets | `false` | | `OBSIGATE_WEBHOOK_ALLOW_PRIVATE` | Allow webhooks to private/loopback addresses | `false` | | `OBSIGATE_PDF_MAX_SIZE_MB` | Max PDF size for text extraction | `50` | | `OBSIGATE_MEDIA_MAX_INLINE_MB` | Max size for inline audio/video playback (above: download) | `500` | | `OBSIGATE_PDF_EXTRACT_TIMEOUT` | PDF extraction timeout (seconds) | `30` | | `OBSIGATE_TAVILY_API_KEY` / `OBSIGATE_BRAVE_API_KEY` / `OBSIGATE_SERPAPI_API_KEY` / `OBSIGATE_EXA_API_KEY` | Keyed web-search providers (tried before SearXNG) | β€” | | `OBSIGATE_WEB_PROVIDERS` | Search provider order (e.g. `brave,searxng`) | β€” | | `OBSIGATE_WEB_RETRY` | Web tools network retries (house-made backoff) | `1` | | `OBSIGATE_WEB_CACHE_TTL` | SQLite cache TTL for web results (seconds, `0` = off) | `900` | | `OBSIGATE_GITEA_URL` / `OBSIGATE_GITEA_TOKEN` | Gitea connected source (`git_list_repos`…) | β€” | | `OBSIGATE_GITHUB_TOKEN` | GitHub token (`git_list_repos`…) | β€” | > These keys can also be entered **from the UI** (menu β†’ Configurations β†’ > "Connected sources & search"): the stored value goes to `data/api_keys.json` > and takes precedence over the environment variable. >All these variables are documented in `.env.example`. ### Volume for Persistence Auth data (`users.json`, `secret.key`) is stored in `/app/data`. Add a volume to persist it: ```yaml volumes: # vaults... - ./data:/app/data # Persist users and JWT key ``` --- ## βž• Adding a New Vault ### Method 1: Direct Editing 1. **Stop the container** : ```bash docker-compose down ``` 2. **Add a volume** in `docker-compose.yml` : ```yaml volumes: - /new/vault/path:/vaults/NewVault:ro ``` 3. **Add the environment variables** : ```yaml environment: - VAULT_4_NAME=NewVault - VAULT_4_PATH=/vaults/NewVault ``` 4. **Restart** : ```bash ./build.sh ``` ### Method 2: Hot-reload (recommended) 1. **Add the volume and variables** as above 2. **Apply the changes** : ```bash ./build.sh ``` 3. **Reload the index** via the interface or the API : ```bash curl http://localhost:2020/api/index/reload ``` ### Method 3: Dynamic API (without restart) Add a vault on the fly via the API (the volume must already be mounted): ```bash curl -X POST http://localhost:2020/api/vaults/add \ -H "Content-Type: application/json" \ -d '{"name": "NewVault", "path": "/vaults/NewVault"}' ``` Remove a vault: ```bash curl -X DELETE http://localhost:2020/api/vaults/NewVault ``` --- ## πŸ”¨ Build & Deployment with build.sh The `build.sh` script is the recommended way to build and deploy ObsiGate. ### Basic Usage ```bash chmod +x build.sh # once ./build.sh # build from scratch + start ``` ### Available Options | Option | Description | |--------|-------------| | `--help`, `-h` | Display full help | | `--build-only` | Build the image without starting the container | | `--no-cache` | Full rebuild without Docker cache **(default)** | | `--cache` | Use Docker cache (faster if few changes) | | `--progress=plain` | Verbose output (recommended for debugging) | | `--progress=tty` | Interactive output with progress bars | ### What the Script Does 1. **Checks** that Docker and Docker Compose are installed (with version numbers) 2. **Validates** the presence and syntax of the `docker-compose.yml` file 3. **Checks each mounted volume** : warns if a source directory does not exist 4. **Builds** the Docker image via `docker compose build` (multi-stage, ~180MB) 5. **Starts** the container via `docker compose up -d` 6. **Displays** the container status then real-time logs ### Examples ```bash # Clean build + start (recommended after code changes) ./build.sh # Fast rebuild with cache (minor frontend changes only) ./build.sh --cache # Test the build without starting (useful for pre-prod testing) ./build.sh --build-only # Verbose output to debug a build failure ./build.sh --progress=plain ``` ### Stopping / Restarting ```bash docker compose down # Stop the container docker compose up -d # Restart without rebuild docker compose logs -f # View logs ``` --- ## πŸ–ΌοΈ Obsidian Image Rendering ObsiGate supports **all Obsidian image syntaxes** with an intelligent multi-strategy resolution system. ### Supported Syntaxes 1. **Standard Markdown with HTML attributes** (Obsidian-compatible) : ```markdown [](https://example.com) ``` 2. **Wiki-link embed with full path** : ```markdown ![[06_Toolbox/6.2_Attachments/image.svg]] ``` 3. **Wiki-link embed with filename only** : ```markdown ![[image.svg]] ``` 4. **Standard Markdown** : ```markdown ![alt text](path/to/image.png) ``` ### Intelligent Path Resolution ObsiGate uses 7 resolution strategies in order of priority: 1. **Absolute path** : If the path is absolute and exists 2. **Configured attachments folder** : Via `VAULT_N_ATTACHMENTS_PATH` 3. **Startup index (unique match)** : Search by filename in the index 4. **Same directory** : Relative to the current markdown file 5. **Vault root** : Relative to the vault root 6. **Startup index (closest match)** : If multiple files have the same name 7. **Fallback** : Display a styled placeholder `[image not found: filename.ext]` ### Viewer & file tree Images are first-class vault files: they appear in the tree, are indexed (name + metadata, **never the bytes**) and open in a **dedicated viewer** β€” wheel zoom 0.1×–8Γ—, drag pan, double-click to reset, ←/β†’ navigation between images in the same folder (WebP thumbnail filmstrip), metadata panel, full-screen lightbox, open original and download. The `ext:png`/`ext:jpg` search filter is available. Decodable formats: PNG, JPEG, GIF, WebP, BMP, ICO, SVG (SVG served with a `sandbox` CSP). **HEIC/HEIF** (iPhone) is not decodable by browsers and is not supported. ### Configuration To optimize resolution, configure the attachments folder for each vault: ```yaml environment: - VAULT_1_NAME=MyVault - VAULT_1_PATH=/vaults/MyVault - VAULT_1_ATTACHMENTS_PATH=Assets/Images # Relative path - VAULT_1_SCAN_ATTACHMENTS=true # Enable scanning (default) ``` ### Manual Rescan To rescan images in a vault after adding/removing: ```bash curl -X POST http://localhost:2020/api/attachments/rescan/MyVault ``` --- ## πŸ–₯️ Desktop (Tauri) β€” Native Application > πŸ“– Full guide: [Desktop (Tauri)](docs/GUIDES/DESKTOP.md) ObsiGate Desktop is a native application built with [Tauri](https://tauri.app/) (Rust + system webview). It embeds the Python backend and frontend in a standalone executable β€” zero Docker, zero command line. > 🚧 **Version 2.0.0 β€” binaries are being stabilized.** For now, building from source is recommended. ### Native Desktop Features | Feature | Web | Desktop | |---|---|---| | Local file access | Via upload | Native (folder picker) | | System theme | Manual | Auto (follows OS dark/light) | | Notifications | Service Worker | Native OS | | `.md` associations | ❌ | βœ… "Open with ObsiGate" | | Tray icon | ❌ | βœ… Taskbar | | Auto-update | ❌ | βœ… Checks Gitea releases | | Offline mode | Limited | Full (local backend) | ### Download (Pre-built Binaries) Releases are published on [Gitea](https://git.dracodev.net/Projets/ObsiGate/releases) : | Platform | Format | |---|---| | **Linux** | `.deb` + `.AppImage` | | **Windows** | `.msi` + `.exe` (NSIS) | #### Linux ```bash # .deb (Debian/Ubuntu/Deepin) sudo dpkg -i obsigate_2.0.0_amd64.deb # Launch : ObsiGate from the applications menu or `obsigate-desktop` # .AppImage (any distro) chmod +x ObsiGate_2.0.0_amd64.AppImage ./ObsiGate_2.0.0_amd64.AppImage ``` #### Windows ```cmd :: Double-click ObsiGate_2.0.0_x64.msi :: Or launch ObsiGate from the Start menu ``` ### Getting Started 1. **Launch the application** from the menu or command line 2. The Python backend starts automatically on `127.0.0.1:17890` 3. The window opens and loads the ObsiGate interface 4. **First launch** : select your Obsidian vaults folder via the native picker 5. To close : tray icon β†’ Quit (clean backend shutdown) ### Build from Source Detailed guide: [desktop/README.md](./desktop/README.md). #### Common Prerequisites | Tool | Version | Installation | |---|---|---| | Rust (cargo) | β‰₯ 1.75 | `rustup` | | Tauri CLI | β‰₯ 2.0 | `cargo install tauri-cli` | | Git | β€” | β€” | | Linux system dependencies | β€” | `sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev` | > **Important β€” staging :** `tauri.conf.json` embeds `backend/**` and `frontend/**` **from the `desktop/` folder**. > The build scripts automatically copy `../backend` and `../frontend` into `desktop/` before `cargo tauri build`. > Without this staging, the build fails with "glob pattern backend/**/* path not found". #### πŸͺŸ Windows β€” `build-windows.bat` ```cmd REM Prerequisites (via Scoop) : rustup, curl, git scoop install rustup curl git rustup default stable cargo install tauri-cli cd desktop build-windows.bat ``` Script steps: 1. Kills residual Python processes (`taskkill /F /IM python.exe`) 2. Downloads **Python 3.11 embed** (python.org) β†’ `desktop\python-embed\` + enables pip (`python311._pth`) 3. `pip install -r ..\backend\requirements.txt` in the embed 4. **Staging** : copies `..\backend` and `..\frontend` to `desktop\` 5. `cargo tauri build --target x86_64-pc-windows-msvc --bundles nsis` 6. Copies `python-embed` next to the executable (`target\x86_64-pc-windows-msvc\release\`) for local dev mode 7. Cleans the staged folders β†’ **Artifact :** `desktop\target\x86_64-pc-windows-msvc\release\bundle\nsis\ObsiGate_2.0.0_x64-setup.exe` #### 🐧 Linux β€” `build-linux.sh` ```bash cd desktop chmod +x build-linux.sh ./build-linux.sh ``` Script steps: 1. Checks Rust + Tauri CLI, installs system dependencies (apt) 2. Creates a Python venv `desktop/python-embed/venv` + `pip install -r ../backend/requirements.txt` 3. **Staging** : copies `../backend` and `../frontend` to `desktop/` 4. `cargo tauri build --target x86_64-unknown-linux-gnu --bundles deb,appimage` 5. Copies the runtime (`python-embed/`, `backend/`, `frontend/`) next to the executable β†’ **Artifacts :** - `desktop/target/x86_64-unknown-linux-gnu/release/bundle/deb/obsigate_2.0.0_amd64.deb` - `desktop/target/x86_64-unknown-linux-gnu/release/bundle/appimage/ObsiGate_2.0.0_amd64.AppImage` ### πŸ€– CI/CD Builds β€” Automatic Artifacts **Yes** β€” the workflow [`.gitea/workflows/desktop-build.yml`](./.gitea/workflows/desktop-build.yml) builds desktop binaries on every push to `main` touching `desktop/**`, `frontend/**`, or `backend/**` (and manually via `workflow_dispatch`), on **self-hosted runners** : | Job | Runner | Artifacts (retained 30 days) | |---|---|---| | `build-windows` | `[self-hosted, windows, desktop]` | `desktop/target/release/bundle/msi/*.msi` | | `build-linux` | `[self-hosted, linux, desktop]` | `*.AppImage` + `*.deb` | - Artifacts are downloadable from the **Actions** page of the Gitea run. - Publishing to **Gitea Release** is planned for tags `v*` (`Publish to Gitea Release` step). - The web workflow [`.gitea/workflows/ci.yml`](./.gitea/workflows/ci.yml) separately handles lint β†’ tests β†’ security β†’ Docker build β†’ e2e Playwright. ### Desktop Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Tauri (Rust) β”‚ β”‚ β”œβ”€ Webview (system webview) β”‚ β”‚ β”‚ └─ Frontend (HTML/JS/CSS) β”‚ β”‚ └─ Sidecar Python β”‚ β”‚ └─ uvicorn backend.main:app β”‚ β”‚ └─ port 127.0.0.1:17890 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` Lifecycle: Tauri spawns the Python backend β†’ health check β†’ opens the webview. On close: SIGTERM β†’ clean shutdown β†’ cleanup. --- ## πŸ“– Usage ### Web Interface 1. **Navigation** : Click on vaults in the sidebar to expand them 2. **Search** : Use the search bar to search across all vaults 3. **Tags** : Click on tags to filter content 4. **Wikilinks** : `[[page]]` links are clickable and navigable 5. **Images** : All Obsidian image syntaxes are rendered automatically 6. **Theme** : Toggle between light/dark theme with the πŸŒ™/β˜€οΈ icon ### Keyboard Shortcuts | Action | Shortcut | |--------|----------| | Search | `Ctrl + K` or `/` | | Toggle theme | `Ctrl + T` | | Focus search | `Esc` | --- ## πŸ‘₯ Real-time Collaboration > πŸ“– Full guide: [Editing & Collaboration](docs/GUIDES/COLLABORATION.md) Multiple users can edit the same markdown document simultaneously (Google Docs style): - **Conflict-free merge** via Yjs (CRDT): two people can type in the same place, no change is lost. - **Colored remote cursors** and visible selections in CodeMirror, labelled with each user's name. - **Presence indicator** in the editor header (avatars + connection status). - **Automatic reconnection** (exponential backoff): state is merged on return. - **Server-side persistence**: the document is written to disk 2 s after the last change. - **Transport**: WebSocket `ws(s):///ws/collab/{vault}/{path}`, authenticated via the `access_token` cookie (or `?token=`) and subject to per-vault access control. No configuration is required: open the same file in two browsers (or two windows) to see it live. --- ## πŸ”Œ API > πŸ“– Full guide: [REST API](docs/GUIDES/API_REST.md) Β· [MCP Server](docs/GUIDES/MCP.md) ObsiGate exposes a complete REST API : | Endpoint | Description | Method | Auth | |----------|-------------|--------|------| | `/api/health` | Health check (status, version, stats) | GET | No | | `/api/auth/status` | Auth status (enabled, users present) | GET | No | | `/api/auth/login` | Login (returns access token + refresh cookie) | POST | No | | `/api/auth/refresh` | Refresh access token via refresh cookie | POST | Cookie | | `/api/auth/logout` | Logout + revoke refresh token | POST | Yes | | `/api/auth/me` | Current user info | GET | Yes | | `/api/auth/change-password` | Change password | POST | Yes | | `/api/auth/admin/users` | List / create users | GET/POST | Admin | | `/api/auth/admin/users/{u}` | Modify / delete a user | PATCH/DELETE | Admin | | `/api/vaults` | List vaults (filtered by permissions) | GET | Yes | | `/api/browse/{vault}?path=` | Browse folders | GET | Yes | | `/api/file/{vault}?path=` | Rendered content of a file | GET | Yes | | `/api/file/{vault}/raw?path=` | Raw content of a file | GET | Yes | | `/api/file/{vault}/download?path=` | Download a file | GET | Yes | | `/api/file/{vault}/save?path=` | Save a file | PUT | Yes | | `/api/file/{vault}?path=` | Delete a file | DELETE | Yes | | `/api/search/advanced` | Advanced TF-IDF search (+ `semantic=true` for hybrid) | GET | Yes | | `/api/suggest` / `/api/tags/suggest` | Autocomplete | GET | Yes | | `/api/tags?vault=` | Unique tags with counters | GET | Yes | | `/api/index/reload` | Force a rescan of vaults | GET | Admin | | `/api/events` | Real-time SSE stream | GET | Yes | | `/api/vaults/add` / `/api/vaults/{name}` | Dynamic vault management | POST/DELETE | Admin | | `/api/image/{vault}?path=` | Serve an image | GET | Yes | | `/api/media/{vault}/thumb?path=&size=` | WebP thumbnail (disk cache) | GET | Yes | | `/api/config` | Read / write configuration | GET/POST | Yes/Admin | | `/api/diagnostics` | Index and memory statistics | GET | Admin | > When `OBSIGATE_AUTH_ENABLED=false`, all endpoints are accessible without a token. > All endpoints expose documented Pydantic schemas. Interactive docs are available at `/docs` (Swagger UI). **Usage Example :** ```bash # Health check curl http://localhost:2020/api/health # List vaults curl http://localhost:2020/api/vaults # Simple search (legacy) curl "http://localhost:2020/api/search?q=recipe&vault=all" # Advanced search with TF-IDF, facets, and pagination curl "http://localhost:2020/api/search/advanced?q=recipe%20tag:cuisine&vault=all&limit=20&offset=0&sort=relevance" # Title autocomplete curl "http://localhost:2020/api/suggest?q=piz&vault=all" # Tag autocomplete curl "http://localhost:2020/api/tags/suggest?q=rec&vault=all" # Get a file curl "http://localhost:2020/api/file/Recipes?path=pizza.md" ``` ## πŸ” Advanced Search > πŸ“– Full guide: [Search, PDF, Excel & Excalidraw](docs/GUIDES/RECHERCHE_PDF_EXCALIDRAW.md) ### Query Syntax | Operator | Description | Example | |----------|-------------|---------| | `tag:` | Filter by tag | `tag:recipe docker` | | `#` | Tag shortcut | `#linux server` | | `vault:` | Filter by vault | `vault:IT kubernetes` | | `title:` | Filter by title | `title:pizza` | | `path:` | Filter by path | `path:recipes/soups` | | `ext:` | Filter by file type | `ext:md kubernetes` | | `"exact phrase"` | Phrase search | `tag:"multiple words"` | Extension filter examples: `ext:sh` for bash scripts, `ext:py` for Python scripts, `ext:md` for Markdown files, `ext:pdf` for PDF documents, `ext:excalidraw` for Excalidraw diagrams (text-extracted content is indexed). ### PDF support PDF files in your vaults are rendered inline in the browser via the native PDF viewer (iframe + ``). The viewer streams the file over HTTP Range requests (206 Partial Content), so large PDFs load progressively. Text is extracted on indexing (pypdf / pymupdf) so PDF content is searchable via the full-text search. Filter with `ext:pdf` to restrict results to PDFs. PDF metadata (pages, title, author) is available via `GET /api/file/{vault}/pdf/info` without transferring the document. **Limitations:** no OCR (scanned PDFs aren't searchable), no annotation, no editing of the PDF itself. ### Excalidraw diagrams `.excalidraw` and `.excalidraw.md` files open in a full visual Excalidraw editor embedded in a sandboxed iframe β€” draw, edit and save without leaving ObsiGate. Changes are saved automatically (2s debounce) or with `Ctrl+S`; the editor follows the light/dark theme. Text labels inside the diagram elements are extracted on indexing, so diagram content is searchable via the full-text search (`ext:excalidraw`). Files created with the **Obsidian Excalidraw plugin** (including the compressed `.excalidraw.md` format) are compatible. Operators are combinable: `tag:linux vault:IT ext:md server web` searches for "server web" in Markdown files of the IT vault with the linux tag. ### Keyboard Shortcuts | Shortcut | Action | |----------|--------| | `Ctrl+K` / `Cmd+K` | Focus search bar | | `/` | Focus search (outside text fields) | | `↑` / `↓` | Navigate suggestions | | `Enter` | Select active suggestion or launch search | | `Escape` | Close suggestions / exit search | ### Features - **TF-IDF** : Scoring based on term frequency weighted by inverse document frequency - **Title boost** : Title matches receive 3Γ— higher score - **Accent normalization** : `resume` finds `rΓ©sumΓ©`, `elephant` finds `Γ©lΓ©phant` - **Highlighted snippets** : Found terms are wrapped in `` in excerpts - **Facets** : Counters by vault and by tag in results - **Pagination** : Page navigation with 50 results per page - **Sorting** : By relevance (TF-IDF) or modification date - **Visual chips** : Active filters are shown as removable colored chips - **History** : Last 50 searches are stored in localStorage - **Semantic search** (optional) : Toggle `~` (or `Alt+S`) fuses the TF-IDF ranking with an embedding-based ranking (RRF). Works out of the box with a dependency-free hashing embedder; install `backend/requirements-semantic.txt` and/or set `OBSIGATE_EMBEDDING_*` for real `all-MiniLM-L6-v2` embeddings. See [docs/features/semantic-search.md](docs/features/semantic-search.md). --- ## πŸ”§ Troubleshooting ### Common Issues **Port already in use:** ```bash # Check who is using the port sudo netstat -tulpn | grep 2020 # Change the port in docker-compose.yml ports: - "2021:8080" ``` **Vault not found:** - Verify paths are absolute - Ensure read permissions - Restart the container after modifications **Build fails:** ```bash # Clean Docker cache and rebuild docker system prune -f docker compose down ./build.sh --progress=plain # If failure persists, check build logs ./build.sh --progress=plain 2>&1 | tee build.log ``` **Logs for debugging:** ```bash # Real-time logs docker compose logs -f obsigate # Detailed logs docker compose logs --tail=100 obsigate ``` --- ## ⚑ Performance | Metric | Estimate | |--------|----------| | **Indexing** | ~1–2s for 1,000 markdown files | | **Advanced Search** | < 10ms for most queries (inverted index + TF-IDF) | | **Wikilink Resolution** | O(1) via lookup table | | **Memory** | ~80–150MB per 1,000 files (content capped at 100 KB/file) | | **Docker Image** | ~180MB (multi-stage, no build tools) | | **CPU** | Non-blocking; search offloaded to dedicated thread pool | ### Recommended Settings by Vault Size | Size | Files | `search_workers` | `prefix_max_expansions` | `max_content_size` | |------|-------|------------------|--------------------------|---------------------| | Small | < 500 | 1 | 50 | 100,000 | | Medium | 500–5,000 | 2 | 50 | 100,000 | | Large | 5,000+ | 4 | 30 | 50,000 | These parameters are configurable via the interface (Settings) or the `/api/config` API. ### Key Optimizations (v1.2.0) - **Inverted index with set-intersection** : Search uses posting lists for O(k Γ— postings) retrieval instead of O(N) full scan - **Prefix matching by binary search** : O(log V + k) instead of O(V) linear vocabulary scan - **ThreadPoolExecutor** : CPU-bound search functions are offloaded from the asyncio event loop - **Race condition guard** : `currentSearchId` + `AbortController` prevent stale result rendering - **Progress bar** : Animated progress bar during search - **Search timeout** : Automatic abort after 30s (configurable) - **Query time display** : Server time shown in results (`query_time_ms`) - **Staleness detection fix** : Generation counter instead of `id(index)` to detect index changes ### Optimizations v1.5.0 (Quick Wins 2026-05-27) - **GZip Compression** : FastAPI middleware compresses all responses >1KB (~70% bandwidth saved) - **Cache-Control immutable** : Static assets (`/static/*`) are cached by the browser for 1 year - **`.dockerignore`** : Minimal Docker build context (no `.git`, `docs/`, `*.md`, `__pycache__`) - **Incremental InvertedIndex** : `add_document`/`remove_document` hooks, no more O(N) rebuild on every mutation ### Optimizations v1.1.0 - **Search without I/O** : File content is cached in memory index - **Multi-factor scoring** : exact title (+20), partial title (+10), path (+5), tag (+3), content frequency (x1 per occurrence, capped at 10) - **Markdown renderer singleton** : The mistune renderer is instantiated once - **AbortController** : Stale search requests are aborted client-side - **Debounced icon rendering** : `lucide.createIcons()` is batched via `requestAnimationFrame` --- ## πŸ›‘οΈ Security > πŸ“– Full guide: [Auth & Security](docs/GUIDES/AUTHENTIFICATION_SECURITE.md) - **Path traversal** : All file endpoints validate that the resolved path stays within the vault - **Rate limiting** : 10 login attempts max per IP over 15 minutes + per-account lockout (5 attempts) - **Audit log** : All writes, deletions, and config changes are logged in `data/audit.log` (JSON lines, 10 MB rotation) - **Automatic backup** : Every file modification or deletion is saved in `.obsigate-backup/` with timestamp - **Secret redaction** : Automatic masking of JWTs, API keys, tokens, and connection strings in previews - **Non-root user** : The Docker container runs under user `obsigate` (UID 1000) - **Read-only volumes** : Vaults are mounted as `:ro` by default in docker-compose - **Secrets in `.env`** : Passwords and tokens are never in `docker-compose.yml` - **Atomic writes** : Data files (users.json, shares.json, webhooks.json) use tmp+replace --- ## πŸ—οΈ Tech Stack - **Backend** : Python 3.11 + FastAPI 0.110 + Uvicorn - **Auth** : python-jose (JWT HS256) + argon2-cffi (Argon2id) - **Security** : Rate limiting (IP + account lockout), audit logging, secret redaction, atomic writes - **File Watcher** : watchdog 4.x (native inotify + fallback polling) - **Frontend** : Vanilla JS + HTML + CSS (zero framework, zero build) - **Markdown Rendering** : mistune 3.x - **PDF Export** : WeasyPrint 60+ - **Compression** : GZip middleware (FastAPI), Cache-Control immutable for assets - **Docker Image** : python:3.11-slim (multi-stage), `.dockerignore` for minimal context - **User Storage** : Local JSON (`data/users.json`) β€” no database - **Architecture** : SPA + REST API + SSE --- ## 🏠 Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Browser │◄───►│ FastAPI (backend/main.py) β”‚ β”‚ (SPA) β”‚ REST β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ app.js β”‚ β”‚ β”‚ indexer.py β”‚ β”‚ search.py β”‚ β”‚ β”‚ style.css β”‚ β”‚ β”‚ (scan+cache)β”‚ β”‚ (in-memory) β”‚ β”‚ β”‚ index.html β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ In-Memory Index β”‚ β”‚ β”‚ β”‚ (files, tags, β”‚ β”‚ β”‚ β”‚ content, lookup)β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Filesystem (mounted vaults) β”‚ β”‚ /vaults/Recipes (ro) β”‚ β”‚ /vaults/IT (ro) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` **Data Flow:** 1. On startup, `indexer.py` scans all vaults in parallel (thread pool) 2. Content, tags (YAML + inline), and metadata are cached in memory 3. An O(1) lookup table is built for wikilink resolution 4. `watcher.py` starts file monitoring (native watchdog or polling) 5. Detected changes trigger incremental index updates 6. Changes are notified to the frontend via Server-Sent Events (SSE) 7. Search queries use the in-memory index (zero disk I/O) 8. The SPA frontend communicates via REST + SSE and manages state client-side --- ## πŸ“ Development ### πŸ§ͺ Tests and CI β€” Mandatory Rules (Before Each Push) > ⚠️ **Absolute rule β€” human contributors and AI agents**: never `git push` > without having **locally** executed the entire sequence below with **100 % > success**. Push is not meant to discover failures: CI is a safety net, not a > diagnostic tool. #### Local Checks Before Commit | Step | Command | Equivalent CI Job | |---|---|---| | Backend lint | `ruff check backend/` | `lint` | | Frontend import validation | `node tests/frontend/validate-imports.mjs` | `lint` | | Frontend unit tests | `node tests/frontend/unit.test.mjs` | `lint` | | Backend tests | `pytest tests/ -q` | `test` | | **E2E Playwright** | `npm run test:e2e` (~10 min) | `e2e` | #### Local E2E Tests (`npm run test:e2e`) The `scripts/run-e2e-local.sh` script faithfully reproduces the `e2e` CI job conditions (`.gitea/workflows/ci.yml`): - backend started natively (venv `.venv-e2e`, Python 3.11 via `uv`) ; - authentication disabled (`OBSIGATE_AUTH_ENABLED=false`) ; - fixtures `test_vault` (TestVault) and `test_dir` (TestDir) ; - port `2029` (baseURL from `playwright.config.ts`) ; - Playwright project `chromium-desktop` only (same as CI). Prerequisites: `uv`, Node.js β‰₯ 20, Playwright browsers (`npx playwright install chromium`). The venv is created automatically on first run. Docker is not required. ```bash npm run test:e2e # full suite bash scripts/run-e2e-local.sh --headed # visible browser bash scripts/run-e2e-local.sh -g "reset panes" # filter on a test ``` On Windows, when `bash` is unusable (WSL unavailable, git-bash blocked by an Application Control policy), use the equivalent PowerShell launcher: ```powershell npm run test:e2e:ps pwsh -File scripts/run-e2e-local.ps1 -PlaywrightArgs @('-g','reset panes') ``` The suite must end with **all tests passing** (60 currently), with no failure or reliance on retries. In case of failure: fix and re-run locally until 100 %, then only commit. #### Test Writing Rules (Lesson Learned) - **Check every selector in the actual DOM** before using it in a test. Real bug encountered: a test was targeting `#cp-input` while the command palette input only has the `.cp-input` class (no id) β€” the test could not pass in any environment. - Any new or modified test must be **run locally** (at least with `-g "test name"`) before push. - No workarounds that mask flakiness (arbitrary `waitForTimeout`, silent fallbacks): fix the root cause. #### Commits and Push - Commit message: imperative subject ≀ 50 chars; body (72 col.) only if it adds useful information. - One commit = one logical change (do not mix fix and refactor). - Push only when all 5 local steps are green. CI pipeline: `lint β†’ test β†’ security β†’ build β†’ e2e`. ### Project Structure ``` ObsiGate/ β”œβ”€β”€ backend/ # FastAPI API β”‚ β”œβ”€β”€ main.py # Endpoints, Pydantic models, markdown rendering β”‚ β”œβ”€β”€ indexer.py # Vault scanning, in-memory index, lookup table β”‚ β”œβ”€β”€ search.py # Full-text search engine with scoring β”‚ β”œβ”€β”€ watcher.py # File monitoring (watchdog + debounce) β”‚ β”œβ”€β”€ auth/ # Authentication module β”‚ β”‚ β”œβ”€β”€ password.py # Argon2id hashing β”‚ β”‚ β”œβ”€β”€ jwt_handler.py # JWT tokens + revocation β”‚ β”‚ β”œβ”€β”€ user_store.py # users.json CRUD (atomic) β”‚ β”‚ β”œβ”€β”€ middleware.py # FastAPI auth dependencies β”‚ β”‚ └── router.py # /api/auth/* endpoints β”‚ β”œβ”€β”€ create_admin.py # User management CLI β”‚ └── requirements.txt β”œβ”€β”€ frontend/ # Web interface (Vanilla JS, zero framework) β”‚ β”œβ”€β”€ index.html # SPA page + login screen β”‚ β”œβ”€β”€ app.js # SPA logic, AuthManager, AdminPanel β”‚ └── style.css # Styles (CSS variables, themes, responsive) β”œβ”€β”€ data/ # Persistent data (created on startup) β”‚ β”œβ”€β”€ users.json # Users (Argon2id hashed) β”‚ └── secret.key # JWT secret key (512 bits) β”œβ”€β”€ Dockerfile # Multi-stage, healthcheck, non-root β”œβ”€β”€ docker-compose.yml # Deployment with healthcheck and auth env vars β”œβ”€β”€ build.sh # Automated build & deployment (docker compose build + up) └── docs/ β”œβ”€β”€ GUIDES/ # User guides (getting started, API, MCP, desktop…) └── CONTRIBUTING.md # Contribution guide ``` ### Contributing See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for code standards and [docs/DELIVERY_WORKFLOW.md](docs/DELIVERY_WORKFLOW.md) for the mandatory delivery process. --- ## πŸ“„ License This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details. --- ## 🀝 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) - **Author** : Bruno Beloeil --- ## πŸ“ Changelog See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 β†’ v2.54.0). --- *Project: ObsiGate | Version: 2.54.0 | Last updated: September 2026*