Skip to content

Architecture

The same Rust daemon runs on every device as a client and on the home server as the server. Same code, same database schema.

    flowchart LR
    src["Files · Mail · Notes"] --> client
    subgraph client["Device — client"]
        db[("SQLite<br/>full text · vectors")]
        ui["CLI · Emacs · Web"]
    end
    subgraph server["Home server"]
        api["API + job queue"]
        blobs["Blob store"]
    end
    worker["ML worker<br/>OCR · embeddings"]
    client <-->|"sync: operation log"| api
    api --- blobs
    api --> worker
  

Key ideas

  • Files stay files. Indexed files remain in the file system. Moved or renamed files are recognised by their hash.
  • The server holds everything. Clients subscribe per folder: metadata only, plus extracted text and thumbnails, or fully offline.
  • Derived data is computed once — text, OCR, thumbnails, embeddings — on the server, never on weak devices.
  • Sync is an operation log with hybrid logical clocks; the last writer wins per field.
  • Private by default. All traffic stays inside a private network (Tailscale/Headscale).

Stack

LayerChoice
CoreRust (tokio, axum)
StorageSQLite with FTS5 and sqlite-vec, statically linked
BlobsFile system, keyed by BLAKE3 hash
MLPython worker for OCR and embeddings
DeploymentOne Debian package; Docker Compose on the server