A self-hosted 3D model library (STL/3MF). Point it at a directory containing one subfolder per project and it adopts what's already there — no import step, no re-organizing required. Every project's file history lives in a real git repo, kept in sync transparently whether a new version comes through the web UI or you just drop files onto the share yourself. Tags, descriptions, and thumbnails live in a small local SQLite database alongside it.
Warning
AI Slop / Vibe Coded Project — This project was "built" with heavy AI assistance to scratch a personal itch. The code works for my use case, but it has not been hardened, audited, or battle-tested. Deploy at your own risk, preferably not exposed to the open internet. No warranties, no support guarantees, no promises.
| Library | Project detail | Upload a new version |
![]() |
![]() |
![]() |
| Search and filter by tag | 3D viewer, tags, and full git history per project | Every upload is a commit |
- Non-destructive adoption — point
LIBRARY_ROOTat a directory you already have; each subfolder becomes a project as-is, nothing is moved, renamed, or restructured. - Real version history, not a blob store — every project directory is a real git working tree. Upload a new version from the UI, and it's a commit with your description as the message. Edit files directly over NFS/SMB instead, and a periodic scan (plus an optional live watcher) picks up the change and commits it automatically — the History tab visually distinguishes "you did this" from "we noticed this changed on disk." Any past version can be restored as a new commit.
- Local SQLite metadata — tags, descriptions, and thumbnail state live in a single SQLite file. The filesystem + git are always the source of truth for file content; the database is just a cache/index on top, so it can be safely deleted and rebuilt from what's on disk.
- Automatic thumbnails — a headless Chromium instance renders each project's primary model and writes a thumbnail to disk, using the exact same STL/3MF loading code as the interactive 3D viewer.
- Tags & search — organize and filter your library by tag, with case-insensitive dedup.
- OIDC auth, optional — plug in Authelia, Authentik, Keycloak, or any standard OIDC provider. Leave it unconfigured and the app runs in single-user mode with no login screen at all.
- Sleek, modern UI — React + shadcn/ui, dark mode included, with an interactive react-three-fiber viewer for STL/3MF files.
docker compose up -dEdit docker-compose.yml first: set the bind mount under volumes to your
actual library directory. The app is then at http://localhost:4000.
Pre-built images are published to ghcr.io/mirceanton/model-hub on every
release; docker-compose.yml builds from source by default, but you can
point it at ghcr.io/mirceanton/model-hub:latest instead.
By default there's no login (single-user mode). To put it behind OIDC
(Authelia, Authentik, Keycloak, etc.), uncomment and fill in the
OIDC_*/SESSION_SECRET environment variables in docker-compose.yml —
see apps/server/.env.example for what each one does.
If your library lives on an NFS/SMB mount, set
LIBRARY_WATCH_USE_POLLING: "true", since inotify events are often
unreliable across network filesystems; a periodic full-library scan runs
regardless as a backstop.
GET /metrics exposes Prometheus-format metrics (thumbnail queue depth,
sync scan duration, HTTP request counts, etc.) and, like GET /healthz,
is unauthenticated by design — even when OIDC is enabled — so it stays
scrapeable without a session or token. If this instance is reachable
beyond a trusted network, firewall /metrics off at the network level.
For scripts that need API access without a browser session — e.g. a slicer post-processing hook that pushes a new model version after every successful print — create a personal API token from the API Tokens page in the UI. The plaintext value is shown exactly once, at creation; only its hash is ever stored. A token authenticates as you, with your current role, and can be revoked at any time from the same page.
curl -H "Authorization: Bearer mh_pat_<your token>" \
http://localhost:4000/api/modelsThis works the same whether or not OIDC is configured — in single-user mode every request already succeeds regardless, so a token there is mostly useful for keeping scripts forward-compatible with a future move to OIDC.
Monorepo (pnpm workspaces): apps/server (Fastify API + sync engine +
thumbnail pipeline), apps/web (Vite/React frontend), packages/shared
(shared TS types).
pnpm install
cp apps/server/.env.example apps/server/.env # then edit LIBRARY_ROOT/DATABASE_PATH
pnpm --filter @model-hub/server dev # API on :4000
pnpm --filter @model-hub/web dev # web on :5173, proxies /api to :4000Run tests with pnpm test (server unit tests) or pnpm --filter @model-hub/web build
for a production build/typecheck of the frontend.
See CLAUDE.md for the full architecture.


