QaidVoid/lapidary

Track anything

★ 0Forks 0RustGitHub ↗Compare

README

Lapidary

A self-hostable framework for building trackers. Trackers (movies/TV, GitHub PRs, and eventually anything) are plugins over a shared core that provides storage, indexing, full-text search, sync scheduling, and per-user tracking state, all backed by a single Postgres database.

Development

Requirements: Rust, Docker, Bun.

# one-time: env file (compose, the daemon, and sqlx tests all read it)
cp .env.example .env

# start Postgres only
docker compose up -d postgres

# build the web UI once (the daemon serves ui/dist)
cd ui && bun install && bun run build && cd ..

# run the daemon locally against it
cargo run --bin lapidary

The daemon listens on :8480 (LAPIDARY_LISTEN overrides) and serves the web UI at /. Health check at GET /healthz. Steel tracker plugins are loaded from plugins/ (LAPIDARY_PLUGINS_DIR overrides).

Tests need a running Postgres (docker compose up -d postgres):

cargo test --workspace

Deployment

The full stack (daemon plus Postgres) runs from the compose file on any small VPS (4 GB RAM is plenty):

POSTGRES_PASSWORD=change-me docker compose --profile full up -d

Tracking state is the only backup-critical data; catalog data can always be re-imported by plugins. A pg_dump of the entity_state, entity_tags, and users tables is a complete backup.

Getting started

Open http://localhost:8480, go to settings, add a tracker instance (the config form is generated from the plugin's manifest; secret fields can be left blank later to keep their stored values), and hit "Sync now". The same works over the API:

# movies (Rust plugin, needs a TMDB API key)
curl -X POST localhost:8480/api/instances -H 'content-type: application/json' \
  -d '{"plugin_id":"movies","name":"my-movies","config":{"api_key":"<tmdb-key>","pages":2}}'

# github PRs (Steel plugin from plugins/github.scm)
curl -X POST localhost:8480/api/instances -H 'content-type: application/json' \
  -d '{"plugin_id":"github","name":"my-prs","config":{"token":"<gh-token>","repos":["you/repo"]}}'

# trigger a sync now (also runs on a schedule), then browse http://localhost:8480
curl -X POST localhost:8480/api/instances/<id>/sync
curl localhost:8480/api/instances/<id>/runs

The movies plugin accepts a lists config (popular, top_rated, trending, now_playing; default popular). Anything not on the lists can be imported on demand: search inside the tracker in the UI and use "Search the source", or via the API:

curl "localhost:8480/api/instances/<id>/lookup?q=the+matrix"
curl -X POST localhost:8480/api/instances/<id>/import \
  -H 'content-type: application/json' -d '{"reference":"movie:603"}'

On-demand imports are pinned: catalog sweeps never remove them.

Workspace layout

  • crates/core: storage, indexing, search, sync engine, tracking state
  • crates/plugin-api: the plugin contract (manifest types, ctx API)
  • crates/daemon: axum HTTP API, scheduler, web UI host
  • crates/plugins/movies: movies/TV tracker (Rust, TMDB)
  • plugins/: Steel tracker definitions (GitHub tracker)

Contributors

QaidVoid

Issues