跳到正文
GitHub·· 8 天前AI 评分22

VaultBridge v1.3.0 发布:为 Obsidian 提供自托管语义搜索 API

mrtrollex/VaultBridge

AI 导读

VaultBridge v1.3.0 发布,这是一个面向 Obsidian Markdown 库的自托管 REST + 语义搜索 API,新增跨 REST、MCP 和仪表盘的 Obsidian wikilink 关系验证,以及默认关闭的 MCP 笔记创建/追加功能。

正文

VaultBridge

VaultBridge is a self-hosted REST + semantic search API for Obsidian Markdown vaults.

Keep Markdown as the source of truth while giving AI clients, scripts, and automations a small authenticated interface for reading, searching, and safely writing notes. Semantic retrieval runs locally, and the derived index remains disposable.

Stable release v1.3.0 MIT License Python 3.12 VaultBridge container on GHCR

Quick start · API · TrueNAS · Story behind v1.0

VaultBridge architecture showing an AI client connected through VaultBridge to an Obsidian vault and a local semantic index

Release status: VaultBridge v1.3.0 is the latest published stable application and GHCR release. Its immutable source, workflow, OCI digests, aliases, anonymous pull, MCP smoke, and full functional verification are recorded in docs/RELEASE_CHECKLIST.md. The current TrueNAS Community package pinned by P2 is 1.0.3 and selects application image 1.3.0; refresh upstream before operational tests. Its pinned form does not yet include the first-class MCP configuration fields.

Candidate source: 1.4.0-rc.1 is being prepared, with read-only Dashboard Hygiene, remembered sessions and Hygiene/Query/Capture/Promotion CLI workflows. These source capabilities require a future candidate image; no RC is published by P3. See the RC runbook.

v1.3.0 scope: This release adds verified Obsidian wikilink relationships across REST, MCP, and the dashboard, plus default-off MCP note creation/appending and first-class TrueNAS MCP package-source settings. Existing REST compatibility, production semantic ranking, and storage/index formats remain unchanged. Upstream delivery of those form fields and live TrueNAS lifecycle verification remain separate from the application release.

VaultBridge sits between an Obsidian vault and API-capable clients such as ChatGPT, scripts, automations, or future applications. Its boundaries are intentionally narrow:

  • Markdown files remain authoritative and usable without VaultBridge.
  • Semantic data is local, derived, disposable, and rebuildable from the vault.
  • The API exposes a small set of authenticated note and search operations, not general filesystem access.
  • Vault-relative path validation and containment keep clients inside the configured vault.
  • The service is client agnostic and needs only a normal self-hosted Docker setup plus SQLite.

The architecture remains understandable without the graphic:

Client
  |
  | HTTPS + Bearer key
  v
VaultBridge (FastAPI)
  |                 \
  v                  v
Markdown vault    Semantic retrieval
                     |
                 FastEmbed/ONNX
                     |
                 SQLite index

The vault is never replaced by the index, and there is no general filesystem endpoint.

Features

📁 Vault API

  • create, read, append to, and list Markdown notes
  • literal title and content search
  • safe vault-relative access with traversal and symlink-escape protection
  • advisory duplicate-candidate discovery against live notes

🧠 Local semantic retrieval

  • multilingual related-note search
  • hybrid semantic + lexical ranking with inspectable scores
  • heading-aware chunks and title/heading embedding context
  • incremental SQLite index that remains derived from Markdown
  • startup, targeted, and background semantic refresh
  • optional filesystem watcher for external Markdown changes

🔒 Self-hosted by design

  • Markdown remains the source of truth
  • local FastEmbed/ONNX embeddings on CPU
  • Bearer authentication with operator-controlled API-key rotation
  • process-local rate limiting for protected routes
  • no cloud embedding API or external vector database required

🖥️ Web Dashboard

  • bundled same-origin /ui/ entry point with no second frontend service
  • accessible Overview, Search, Hygiene, API / Integration, and About navigation
  • read-only Overview using the existing public /health facts, with manual refresh and explicit loading/unavailable states
  • API-key unlock through the existing dashboard session boundary and signed HttpOnly cookie
  • strict browser security headers, local assets, and text-only dynamic rendering
  • protected literal and semantic Search using existing backend ranking and read-only result fields, with no query/history persistence
  • bounded outgoing-link and backlink facts for a successfully read note, using the existing authenticated versioned endpoints without client-side relationship parsing or persistence
  • read-only Knowledge Hygiene scans on explicit Run scan / Refresh scan, with returned finding counts, canonical note paths, safe evidence, and separate scan/candidate/index coverage; partial scans and truncated findings are explicitly labelled
  • retrieval-only operator visibility: no note editor, index-maintenance controls, account system, or TrueNAS-specific behavior

Open the dashboard from the same VaultBridge origin at /ui/. The browser session uses an operator-supplied existing API key once through /ui/session; the key is not stored by browser JavaScript. The signed HttpOnly cookie restores access across reloads and browser reopen for up to seven days; successful restoration refreshes that lifetime. It uses SameSite=Strict, path /, and Secure when the application sees HTTPS. Unlock sends the key in a JSON POST body, never a URL. Protected requests use the cookie plus X-VaultBridge-UI-Request: 1. Changing the current API key invalidates existing sessions, even while the previous key remains accepted for Bearer clients and new unlocks. Logout deletes the browser cookie and clears protected form/results; 401 also clears that page state. There is no server-side revocation list: a copied signed token remains valid until expiry or current-key rotation. The public Overview remains available.

The dashboard targets current evergreen browsers with support for ES modules, fetch, AbortController, URL, and Intl.NumberFormat. This is a modest standards-based compatibility statement, not a claim that every browser/version has been tested. Actual browser and production-image evidence is tracked separately in docs/DASHBOARD_RELEASE_CHECKLIST.md.

VaultBridge dashboard Overview showing a ready local semantic index

VaultBridge dashboard About view describing its local-first, API-first design

These screenshots were captured from the running TrueNAS canary dashboard without browser chrome, API keys, note content, paths, or private host details. The Overview exposes only the same public health facts returned by /health. Their repository URLs are non-authoritative source/preview material; they are not evidence that the accepted TrueNAS catalog already displays them.

MCP

VaultBridge can be launched as a local MCP server for MCP-capable clients:

python -m app.mcp_server

The stdio transport exposes list_notes, read_note, search_notes, related_notes, duplicate_candidates, note_links, note_backlinks, and knowledge_hygiene_scan (eight read-only tools), plus contained Markdown Resources such as vaultbridge://note/Projects%2FLaunch%20plan.md. The process reuses VAULT_PATH, SEMANTIC_DATA_PATH, model, size, and rate-limit settings. It does not require API_KEY; the local spawning process and filesystem permissions are the trust boundary.

With the default MCP_WRITE_ENABLED=false, the adapter never starts FastAPI, a network listener, a background indexer, or the filesystem watcher. Literal list/read/search operations work independently of semantic storage. Semantic tools read only an existing compatible ready index and never synchronize, rebuild, or modify it; if a safe immutable index view is unavailable, those tools return a retryable error.

A generic client entry uses the current Python environment and repository as its working directory:

command: python
args:
  - -m
  - app.mcp_server

The same eight read-only tools and contained Markdown Resources can be exposed over Streamable HTTP at /mcp on the existing application port. It is disabled by default. Enable it with MCP_HTTP_ENABLED=true, keep using Authorization: Bearer <token>, and configure MCP_HTTP_ALLOWED_HOSTS plus MCP_HTTP_ALLOWED_ORIGINS for the actual deployment. The defaults allow loopback Host/Origin values only; a request without Origin remains valid for non-browser clients when its Host is allowed. The HTTP transport accepts the current API_KEY and optional API_KEY_PREVIOUS, and shares the normal process-local peer rate limit and live application services/index lifecycle.

Set MCP_WRITE_ENABLED=true to add exactly create_note and append_note to either transport (ten tools total). They reuse the protected REST write behavior: create never overwrites, append supports an optional dedupe key, and only committed changes queue targeted semantic refresh. Stdio owns and shuts down a targeted index worker only in this mode; it does not start an unnecessary full sync. For remote read/write MCP, use all four explicit settings and terminate TLS at a trusted reverse proxy or use a private VPN:

MCP_HTTP_ENABLED=true
MCP_WRITE_ENABLED=true
MCP_HTTP_ALLOWED_HOSTS=vaultbridge.example.test:*
MCP_HTTP_ALLOWED_ORIGINS=https://vaultbridge.example.test

Do not use wildcard Host or Origin values. Streamable HTTP remains schema-hidden and adds no port, service, container, OAuth flow, Prompt, subscription feature, or standalone HTTP+SSE endpoint.

🐳 Deployment & operations

  • Docker source builds and published GHCR release images
  • TrueNAS SCALE deployment runbook
  • liveness, readiness, and detailed operator health visibility
  • local CLI for search, status, related notes, and stopped-service index maintenance
  • Custom GPT Action example with legacy-route compatibility

Current API

New integrations should use the versioned application API:

Method Path Operation
POST /api/v1/notes createNoteV1
POST /api/v1/notes/append appendNoteV1
GET /api/v1/notes/read readNoteV1
POST /api/v1/notes/search searchNotesV1
POST /api/v1/notes/related findRelatedNotesV1
POST /api/v1/notes/duplicates findDuplicateCandidatesV1
GET /api/v1/notes/list listNotesV1
GET /api/v1/notes/links listNoteLinksV1
GET /api/v1/notes/backlinks listNoteBacklinksV1
POST /api/v1/knowledge/hygiene/scan scanKnowledgeHygieneV1

Existing clients may continue using the unversioned compatibility layer:

Legacy path Legacy operation Preferred path
/notes createNote /api/v1/notes
/notes/append appendNote /api/v1/notes/append
/notes/read readNote /api/v1/notes/read
/notes/search searchNotes /api/v1/notes/search
/notes/related findRelatedNotes /api/v1/notes/related
/notes/duplicates findDuplicateCandidates /api/v1/notes/duplicates
/notes/list listNotes /api/v1/notes/list

Both paths in each compatibility pair use the same authentication, validation, response, error, and domain-service implementation. The checked-in ChatGPT Action schema remains on the legacy paths so existing Actions continue working without an immediate configuration change. Migrating that external configuration and removing the compatibility layer are separate future decisions; no removal date is set.

The relationship endpoints are versioned-only and have no unversioned compatibility aliases. They return live derived link metadata and perform no Markdown or semantic-index mutation.

Operational routes remain unversioned: GET /health (healthCheck), GET /health/live (livenessCheck), and GET /health/ready (readinessCheck). The public GET /privacy text endpoint is intentionally excluded from OpenAPI and also remains unversioned.

Health and operator status

GET /health is unauthenticated as before and returns a cheap factual snapshot without loading the embedding model, synchronizing the vault, generating embeddings, or performing a search.

{
  "ok": true,
  "vault_exists": true,
  "semantic_index_ready": false,
  "semantic_index_state": "indexing",
  "semantic_search_available": true,
  "semantic_indexer_running": true,
  "full_sync_required": false,
  "indexed_notes": 734,
  "semantic_chunks": 4610,
  "vault_notes": 842,
  "last_successful_sync": "2026-08-23T10:00:00+00:00"
}

ok means the application answered the health request; vault availability remains a separate fact. semantic_index_ready retains its original meaning (semantic_index_state == "ready"). Search availability is separate because an existing compatible index can remain searchable while a refresh is indexing or after that refresh fails with error. full_sync_required is process-local and is also true while unresolved full-sync recovery debt exists; it does not claim system-wide recovery.

Indexed-note/chunk counts come from one short-lived, coherent read-only SQLite snapshot. vault_notes counts semantic-index-eligible Markdown files using the same containment, internal directory exclusion and maximum-size policy as full synchronization, without reading note contents. Together these counts provide inferred completeness; they are not explicit current-note, percentage, batch or ETA progress counters. last_successful_sync is null until the first successful full synchronization and is not advanced by targeted note refreshes.

Semantic search

Default embedding model:

sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2

FastEmbed runs the ONNX model locally on CPU. Markdown is split on ATX heading boundaries where practical, and each chunk retains its heading hierarchy. Oversized sections remain bounded and are split through exact source slices while fitted fenced-code blocks stay intact. Adjacent tiny sections coalesce into useful bounded chunks whose metadata identifies the contained heading range. Markdown chunks and normalized embeddings are stored in SQLite. Persisted chunk content remains unchanged; embedding input adds the canonical heading hierarchy between note title and content when that context is not already the chunk's first heading. The semantic database is derived data and can always be rebuilt from the vault.

Upgrading from the earlier fixed-size chunker or the VB-020 heading-aware embedding representation changes the semantic index signature. VaultBridge automatically discards incompatible derived chunks and rebuilds them from the Markdown vault; no manual SQLite migration is required. If a targeted refresh encounters an older signature, it safely performs the required full rebuild. Upgrading from v1.1.0 to v1.2.0 also performs one automatic rebuild because v1.2.0 fingerprints the effective embedding backend and model artifacts before reusing persisted vectors. Markdown is not changed.

Example:

curl -X POST http://127.0.0.1:8765/api/v1/notes/related \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"professional courses and certifications","limit":5}'

Before creating a note, clients can request advisory duplicate candidates:

curl -X POST http://127.0.0.1:8765/api/v1/notes/duplicates \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Professional certifications","text":"Courses, exams, and renewal notes","limit":5}'

The response contains only real live vault notes. Conservative normalized filename equivalence is reported as exact_title and is stronger evidence than semantic relatedness; a semantic match does not prove that two notes are duplicates. Semantic paths are verified against the live vault before return, and the operation never merges, creates, appends, renames, deletes, or otherwise writes notes or the semantic index.

Quick start

Prerequisites

  • Docker Engine or Docker Desktop with Docker Compose support
  • Git, when cloning the repository
  • an existing Obsidian vault directory
  • enough free disk space for the local embedding model cache and semantic index
  • internet access when the container image and embedding model are downloaded for the first time

TrueNAS, Kubernetes, Redis, an external vector database, and a cloud embedding API are not required.

The following is the canonical Linux/macOS shell workflow:

git clone https://github.com/mrtrollex/VaultBridge.git
cd VaultBridge
cp .env.example .env
docker run --rm python:3.12-slim python -c 'import secrets; print(secrets.token_urlsafe(48))'

Copy the generated value into API_KEY in .env, then set the host path to your existing vault:

API_KEY=YOUR_GENERATED_API_KEY
API_KEY_PREVIOUS=
OBSIDIAN_VAULT_PATH=/home/alice/Documents/MyVault

OBSIDIAN_VAULT_PATH is a path on the Docker host. Compose bind-mounts it at /vault in the container; it is not a container path. Never commit .env. It is ignored by Git, but you should still treat it as a secret-bearing file.

On Linux, set the container identity to the user that owns the vault:

id -u
id -g

Put those values in PUID and PGID in .env. The defaults are 1000:1000. VaultBridge needs read access for all note operations and write access for create/append operations and its derived semantic-data directory. Fix ownership or access-control rules deliberately; do not use chmod 777. Docker Desktop for macOS and Windows mediates bind-mount permissions through its VM, so host UID/GID behavior is not identical to native Linux. Ensure the vault directory is shared with Docker Desktop; leave the defaults unless your Docker Desktop setup requires different values.

Build and start VaultBridge:

docker compose up -d --build
docker compose ps
docker compose logs --tail 100

The source build uses Python 3.12, installs the Linux libgomp1 runtime required by ONNX Runtime, and starts Uvicorn on container port 8000.

GHCR release images

Published GitHub Releases also produce the same Dockerfile-based application image at:

ghcr.io/<repository-owner>/vaultbridge:<version>

Use the lowercase repository owner shown on the package page. The current stable image is v1.3.0:

docker pull ghcr.io/<repository-owner>/vaultbridge:1.3.0

The publication workflow accepts v-prefixed semantic release tags. Every release receives the exact version tag without the leading v. A stable release also updates its major.minor alias and major alias plus latest; a GitHub prerelease receives only its exact prerelease tag, such as 1.0.0-rc.1. Release tags must not be reused. For a cryptographically immutable deployment reference, copy the published digest from GHCR and pull ghcr.io/<repository-owner>/vaultbridge@sha256:....

GHCR packages are private on first publication unless account/organization policy says otherwise. After the first successful release, the repository owner should open the package's Package settings, verify repository linkage, and deliberately change visibility to Public if anonymous pulls are intended. Public visibility is not assumed by this workflow.

The checked-in source-build Compose workflow remains supported and unchanged. The GHCR image is an additional distribution artifact, not an automatic migration of existing Docker or TrueNAS installations. VB-054 publishes only the normal Linux architecture produced by the GitHub-hosted runner; multi-architecture manifests remain VB-055.

Supported deployment platforms

  • Production is the repository Dockerfile running as a Linux container, built from source or pulled from GHCR after a release image is actually published and verified.
  • Docker-based TrueNAS SCALE users can install the accepted Community App from Discover Apps. The source-built Custom App workflow remains available in README_TRUENAS.md.
  • Docker Desktop on Windows or macOS may run the Linux container. Native Windows is a development and test environment, not a documented production deployment.
  • The current GHCR workflow is single-architecture. Do not assume ARM64 or multi-architecture image support before VB-055 and manifest verification.

Release gates, RC validation, and the stable release procedure are tracked in docs/RELEASE_CHECKLIST.md.

Docker configuration

The generic Compose file reads .env, validates application settings at startup, and uses this deployment contract:

host OBSIDIAN_VAULT_PATH  ->  container /vault
/vault/.obsidian-chatgpt-data  ->  configured SEMANTIC_DATA_PATH
host 127.0.0.1:API_PORT  ->  container port 8000
host PUID:PGID  ->  container process user and group
.env variable Default/example Purpose and constraint
API_KEY placeholder only Required by protected routes; replace with a long random secret
API_KEY_PREVIOUS empty Optional old key accepted only during an operator-controlled rotation window
RATE_LIMIT_ENABLED true Enable the process-local protected-route limiter
RATE_LIMIT_REQUESTS 120 Positive requests allowed per peer in one fixed window
RATE_LIMIT_WINDOW_SECONDS 60 Positive fixed-window duration in seconds
RATE_LIMIT_MAX_CLIENTS 1024 Positive hard cap on process-local peer state
MCP_HTTP_ENABLED false Opt in to Streamable HTTP at /mcp on the existing application port
MCP_WRITE_ENABLED false Add MCP create_note and append_note; the default eight-tool surface remains read-only
MCP_HTTP_ALLOWED_HOSTS loopback hosts with any port Comma-separated Host allowlist enforced by the MCP SDK; external hosts must be explicit
MCP_HTTP_ALLOWED_ORIGINS loopback HTTP origins with any port Comma-separated allowlist for a present Origin; external origins must be explicit
OBSIDIAN_VAULT_PATH /path/to/your/Obsidian/Vault Required absolute host path to the vault
API_PORT 8765 Host loopback port mapped to container port 8000
PUID / PGID 1000 / 1000 Numeric container user/group used for bind-mounted files
MAX_NOTE_BYTES 1000000 Positive maximum note size
SEMANTIC_MODEL sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 Non-empty local model name
SEMANTIC_CHUNK_CHARS 600 Integer of at least 250
SEMANTIC_CHUNK_OVERLAP 100 Non-negative and at most half the chunk size
SEMANTIC_EMBED_BATCH_SIZE 4 Experimental positive FastEmbed inference batch size; lower values can reduce peak memory during indexing
SEMANTIC_ONNX_CPU_MEM_ARENA true Experimental toggle for the ONNX Runtime CPU memory arena during semantic inference
SEMANTIC_INDEX_BATCH_SIZE 25 Positive maximum notes committed per indexing transaction
SEMANTIC_WATCH_ENABLED false Opt in to recursive external Markdown change watching
SEMANTIC_WATCH_DEBOUNCE_SECONDS 1.0 Positive coalescing window for editor/synchronizer event bursts

Compose fixes the application-only VAULT_PATH to /vault and SEMANTIC_DATA_PATH to /vault/.obsidian-chatgpt-data; do not put host paths in either setting. Invalid numeric values or empty paths/model names stop application startup. OBSIDIAN_VAULT_PATH, API_PORT, PUID, and PGID are Compose inputs and are not read by the Python application.

Rotate the API key safely

API_KEY is always the required current credential. API_KEY_PREVIOUS is optional; when it is non-empty, protected legacy and /api/v1 routes accept either key with the unchanged Authorization: Bearer <key> header. It does not replace a missing API_KEY.

Use this operator-controlled rotation procedure:

  1. Generate a new long random key without printing or storing it in repository files.

  2. Edit the untracked .env so the new key is current and the old key is previous:

    API_KEY=<new-current-key>
    API_KEY_PREVIOUS=<old-key-during-rotation>
  3. Run docker compose up -d to recreate the service with both keys accepted.

  4. Migrate every client to the new API_KEY and verify its protected requests.

  5. Remove API_KEY_PREVIOUS or set it to empty, then run docker compose up -d again.

The application does not expire, generate, persist, or hot-reload keys. The previous key remains valid until the operator removes it and recreates/restarts the service. Keep both values out of logs, support output, shell history, and version control.

Tune process-local rate limiting

VaultBridge limits protected legacy and /api/v1 note/search requests by the ASGI peer address. The default allows 120 requests per 60 seconds for each peer and retains at most 1024 peer windows. GET /health, GET /health/live, GET /health/ready, and the public schema-hidden /privacy route are exempt. A rejected request returns HTTP 429 with {"detail":"Rate limit exceeded"} and an integer Retry-After value for the remaining fixed window.

The limiter is deliberately in-process: state is not persisted, is reset by a restart, and is not shared across multiple workers or application instances. It does not provide a distributed quota. VaultBridge uses the direct ASGI peer address and does not trust X-Forwarded-For, X-Real-IP, or Forwarded. Behind a reverse proxy the peer is commonly the proxy itself, so multiple external clients may share one bucket. VB-043 does not add trusted-proxy parsing; size and tune the shared bucket for that deployment or disable it with RATE_LIMIT_ENABLED=false only when equivalent protection exists elsewhere.

Semantic-data persistence

The generic Compose deployment stores these derived files inside the mounted vault directory:

/vault/.obsidian-chatgpt-data/semantic-index.sqlite3
/vault/.obsidian-chatgpt-data/models/
/vault/.obsidian-chatgpt-data/huggingface/

On the host, they are under ${OBSIDIAN_VAULT_PATH}/.obsidian-chatgpt-data. This directory therefore persists across container replacement and requires write permission for PUID:PGID. The SQLite index and downloaded model cache are derived/disposable; Markdown files remain the source of truth and should be the focus of backups. Do not edit SQLite manually. Use the supported stopped-service maintenance commands below to inspect or rebuild the index.

Compose fixes FastEmbed/Hugging Face's internal HF_HOME to the huggingface/ directory above, so the configured non-root PUID:PGID uses the same writable derived-data mount instead of an unwritable root-home cache. This is an internal container path and requires no additional .env setting.

The separate /data mount described in the TrueNAS guide is not part of generic Compose.

External Obsidian and synchronization edits

Filesystem watching is disabled by default, preserving the previous startup/full-sync behavior. Set SEMANTIC_WATCH_ENABLED=true to observe the mounted vault recursively for external Markdown creates, changes, deletes, and renames from tools such as Obsidian or Syncthing. The default one-second debounce coalesces event bursts and atomic-replace patterns before paths enter the existing targeted semantic-index queue. Non-Markdown files, Obsidian internals, temporary artifacts, the semantic data directory, and paths that fail vault containment checks are ignored.

The watcher is incremental, process-local functionality for one VaultBridge process. It does not replace the authoritative startup full synchronization, which still reconciles changes made while VaultBridge was stopped. It also does not replace normal Markdown backups. Disable the watcher if the mounted filesystem does not reliably deliver native change notifications.

First startup and health verification

The HTTP application starts without waiting for the whole vault to be indexed. A background semantic synchronization then scans the vault and downloads the local embedding model if it is not cached. Consequently, liveness can succeed before semantic readiness:

curl -fsS http://127.0.0.1:8765/health/live
curl -i http://127.0.0.1:8765/health/ready
curl -fsS http://127.0.0.1:8765/health
  • /health/live means the HTTP process is alive and returns 200 with {"ok":true}.
  • /health/ready means VaultBridge can serve its intended vault and semantic workload. It returns 503 with {"ready":false} during an initial build and 200 with {"ready":true} when usable.
  • /health returns richer operator diagnostics, including lifecycle state, availability, indexer activity, counts, and the last successful full synchronization.

Poll readiness and inspect logs while the first index is built. Semantic functionality becomes available after a successful initial index. A later startup synchronizes changed Markdown; when the stored index signature is incompatible with the running model/chunk configuration, VaultBridge automatically rebuilds the derived index from Markdown.

After readiness succeeds, test the preferred versioned API with a non-destructive request:

curl -fsS 'http://127.0.0.1:8765/api/v1/notes/list?limit=5' \
  -H 'Authorization: Bearer YOUR_API_KEY'

Replace YOUR_API_KEY with the value in .env. The API key protects note and search routes; health probes are intentionally public. Unversioned note/search routes remain compatibility aliases, but new integrations should use /api/v1.

If you changed API_PORT, replace 8765 in these commands with that value.

Logs and basic troubleshooting

VaultBridge writes structured application events to the container log stream. Useful commands are:

docker compose ps
docker compose logs --tail 100
docker compose logs -f
docker compose restart
docker compose down

Common first-start checks:

  • Readiness remains 503 while initial indexing or model download is still running; inspect /health and follow the logs rather than assuming container startup is blocked.
  • Permission errors usually mean PUID:PGID cannot read/write the host vault or create .obsidian-chatgpt-data.
  • Protected requests return the existing server configuration error when API_KEY is missing; an API_KEY_PREVIOUS value cannot become the primary credential. Missing, malformed, or unknown Bearer credentials return the existing generic authentication error.
  • Invalid typed environment values stop startup; the container logs identify the configuration validation failure without exposing the API key.
  • HTTP 429 means the process-local peer bucket is exhausted; honor Retry-After and check for an accidental client loop before raising the configured allowance.
  • A first model download requires network access. Network/download failures appear in container logs and leave initial semantic readiness unavailable. Correct the problem, then run docker compose restart to schedule a new startup synchronization.

Avoid pasting a resolved docker compose config into support requests: depending on the Compose version, it can include the resolved API_KEY and API_KEY_PREVIOUS. Never dump the complete container environment.

Updating and stopping

This deployment builds from the checked-out source. Update it conservatively with:

git pull
docker compose up -d --build
docker compose ps

This recreates the application container as needed and is not a zero-downtime procedure. Markdown remains authoritative. Ordinary compatible updates reuse the persisted semantic data; a change to the model/chunk/index signature triggers the existing automatic rebuild logic.

docker compose stop stops the existing container so it can be started again. docker compose down stops and removes the Compose container and network. Neither command removes the bind-mounted host vault or .obsidian-chatgpt-data; deleting host files is a separate destructive action. Do not use docker compose down -v as a cleanup shortcut, and never point a removal command at the vault.

Local CLI and semantic index administration

The dependency-free local CLI reuses VaultBridge's existing vault and semantic services:

python -m app.cli status
python -m app.cli index
python -m app.cli reindex
python -m app.cli search "backup"
python -m app.cli related "how is my backup replicated?"
python -m app.cli query --semantic-text "backup" --folder Projects --tag active --limit 10

status is the concise persisted vault/index view. search is literal title/content search and does not load the embedding model. related uses the existing compatible semantic index without synchronizing it and live-verifies result paths before display. Both query commands support --folder; search supports --limit, while related supports --limit and --min-score. Those commands print bounded snippets and vault-relative paths. query calls the bounded KnowledgeQueryService directly and supports --semantic-text, --literal-text, --folder, repeatable exact --path, repeatable required --tag, and --limit. It prints only canonical vault-relative paths and existing semantic score evidence when applicable. Empty results are successful.

capture reads exactly one UTF-8 JSON request from standard input (at most 1,048,576 bytes):

python -m app.cli capture < capture-request.json

The request requires content, state (inbox or draft), capture_id (a lowercase UUID v4), and captured_at (UTC RFC 3339 at second precision, with Z or +00:00). Optional fields are title, source, ordered tags, capture_type, and metadata for additional safe portable frontmatter values. Duplicate JSON keys and unknown top-level fields are rejected. For example:

{"content":"Selected Markdown text\n","state":"inbox","capture_id":"9f563c82-f963-4a44-9f36-1f7c6f459a4c","captured_at":"2026-10-03T12:30:00Z","source":"unknown","tags":[]}

Capture creates only Inbox/Captures/<capture_id>.md; title is display metadata. It retains the exact authored body and ordered portable frontmatter. Keep the original request, ID, and timestamp for retries. Exact bytes at that path yield already_applied; differences yield conflict without overwriting or choosing another path. Output contains the stable category and committed boolean; committed outcomes also include the ID, canonical path, and separate index_pending or index_unavailable evidence. An accepted indexing job may subsequently fail without changing Markdown success. After commit_unknown, retain the original request and inspect the exact canonical path. Verified matching bytes can resolve the outcome as already_applied. If the path is absent after a possible commit or namespace relocation, do not automatically retry: an operator must resolve the possible relocated artifact and explicitly authorize another write. If another same-ID artifact cannot be ruled out, do not write again. Expected failures exit 1, confirmed Markdown success exits 0, and configuration/programming failures exit 2. Content and metadata are never echoed in diagnostics.

Capture enqueues existing local indexing after commit and drains its worker before CLI exit. Keep the serving process stopped while running this CLI command because semantic persistence has no cross-process index lock. Markdown creation itself coordinates cooperating VaultService writers across local processes and threads: a POSIX directory lock or Windows kernel mutex, plus a complete temporary file linked atomically without replacement. Active capture destination or staging namespace mutation by a non-cooperating filesystem writer is outside that strong guarantee; detected uncertainty does not confirm creation. Ordinary external Markdown editing remains supported. The filesystem must support hard links; unsupported filesystems fail safely. This adds no capture database. Explicit review and promotion are separate CLI operations described below.

hygiene scan reads live Markdown and reports relationship (wikilink and inline Markdown link), isolation, frontmatter, alias and authored-body diagnostics without repairs:

python -m app.cli hygiene scan --json
python -m app.cli hygiene scan --group relationships --finding-limit 100

The same bounded diagnostics are available through protected POST /api/v1/knowledge/hygiene/scan, MCP knowledge_hygiene_scan, and the Dashboard Hygiene panel. Coverage, unavailable notes, truncation and optional duplicate/index evidence are explicit; advice is not a repair, freshness guarantee or semantic-duplicate promise.

Explicit promotion retains the capture source and requires a human decision:

python -m app.cli promote review --source-path Inbox/Captures/<capture-id>.md --capture-id <capture-id>
python -m app.cli promote apply < promotion-decision.json

Replace <capture-id> with the original lowercase UUID v4. Review emits the source bytes/metadata and source_sha256; --destination Notes/Target.md additionally inspects an append destination. Treat review output as private vault content. Apply accepts one bounded JSON decision with source_path, capture_id, expected_source_sha256, approved_content, action (create or append), destination, a stable lowercase UUID v4 promotion_id, UTC-second approved_at, and ordered transfer_fields (selected existing title, tags, capture_type). Create also requires expected_destination: "absent"; append instead requires expected_destination_sha256 from review. Changed source/destination bytes fail closed. Preserve the exact decision for retries; do not change IDs after uncertain outcomes. commit_unknown requires operator reconciliation before another write. Markdown success and derived-index outcome are separate. Stop the serving process for Capture and Promotion apply because they own targeted index writes; use the same offline discipline for review with optional candidate evidence. Focused Capture/Promotion tests cover synthetic writes separately from the image gate, which only checks their parsers.

Query, Capture and Promotion have no REST/MCP/dashboard adapters. Named multi-space serving and Hygiene mutations remain unavailable.

index brings derived semantic data up to date through the production incremental/full sync path; reindex first discards and then rebuilds derived semantic data. Markdown remains the source of truth and neither command changes note files.

VaultBridge has no cross-process index lock. status, index, and reindex are stopped-service operations; do not run them through docker exec in the serving application container. Read-only search, related, and query do not mutate semantic persistence, but they are local readers and are not a new remote/concurrent-process coordination layer.

Inspect persisted index integrity without loading the embedding model or changing storage:

docker compose stop obsidian-api
docker compose run --rm --no-deps obsidian-api python -m app.cli status
docker compose run --rm --no-deps obsidian-api python -m app.cli index check
docker compose up -d obsidian-api

The check reports vault/database/schema/signature/lifecycle/searchability/count status. It uses an immutable SQLite view and refuses inspection while SQLite WAL/SHM sidecars are present. Exit 0 means healthy, 1 means an integrity/readiness problem, and 2 means a CLI, configuration, or programming failure. /health and /health/ready remain authoritative for the running service.

To update a compatible index normally, keep the service stopped and synchronize it without a reset:

docker compose run --rm --no-deps obsidian-api python -m app.cli index

If inspection shows a clean rebuild is appropriate, rebuild only the derived semantic data through the production synchronization path:

docker compose run --rm --no-deps obsidian-api python -m app.cli reindex
docker compose run --rm --no-deps obsidian-api python -m app.cli index rebuild
docker compose up -d obsidian-api

reindex and index rebuild are equivalent. Rebuild may download the model if its cache is empty. It clears/recreates semantic index data but does not modify Markdown. Review a failed check before deciding to rebuild; do not edit or delete the SQLite files while the service is running.

Networking and security

Generic Compose publishes 127.0.0.1:${API_PORT:-8765}:8000, so VaultBridge is reachable only from the Docker host by default. Keep this security-positive binding. For access from another device or the public internet, place an HTTPS reverse proxy or VPN in front rather than changing the binding to 0.0.0.0 merely for convenience. VaultBridge does not terminate TLS itself.

  • use a strong random API key and never commit .env;
  • never expose or publish the vault directory itself;
  • use HTTPS, a VPN, or both for remote access;
  • back up the authoritative Markdown vault;
  • treat the semantic database/model cache as rebuildable derived data;
  • remember that the built-in rate limiter is process-local and may aggregate clients behind a reverse proxy; it is not a distributed edge-protection service.

See SECURITY.md for the security invariants.

TrueNAS

The generic workflow above does not require TrueNAS. For ordinary TrueNAS installations, use the VaultBridge Community App from the Community train in Discover Apps. The source-built Custom App path remains available for manual and compatibility deployments; see README_TRUENAS.md. Its separate Compose file, /data mount, and compatibility identifiers are intentionally not reused in the generic examples.

About the project

VaultBridge started as a personal bridge between ChatGPT and my Obsidian vault and grew into a standalone open-source project.

📖 Read the story behind VaultBridge 1.0.

Development

See DEVELOPMENT.md.

Core checks:

PYTHONPATH=. pytest -q
python -m compileall -q app

Codex development

The repository is prepared for task-by-task Codex work:

Use one exact item from BACKLOG.md at a time and verify the current recommendation before starting.

License

VaultBridge is licensed under the MIT License.

来源:GitHub · github.com