All apps · 0 apps
ModelHub
Docker app from allornothing's Repository
Overview
Readme
View on GitHubModel Hub (self-hosted)
Source: github.com/aon082910/model-hub · Image: hub.docker.com/r/allornothing/model-hub
An open, self-hosted clone of meshory.com's feature set for Unraid: STL/3MF/OBJ/STEP/FBX library management, thumbnails, duplicate detection, AI auto-tagging + semantic search (local Ollama or an external API — your choice, switchable in Settings), smart collections, filament inventory, and a print queue. Meshory itself is closed-source and desktop-only; this is a from-scratch reimplementation of its feature list, not a repackage of Meshory.
Run it locally / test before deploying to Unraid
docker compose up --build
Then open http://localhost:8420. Put some STL/3MF files in ./data, click
Rescan Library.
Deploy to Unraid
Image is published at allornothing/model-hub on Docker Hub — Unraid can pull it directly, no build step needed. (To build your own fork instead:
docker build -t <you>/model-hub:latest . && docker push <you>/model-hub:latest, then editunraid/model-hub.xmlto match.)(Optional, for local AI) Install Ollama from Community Applications first — search "ollama" in the Apps tab, install it, then in its container console run:
ollama pull llava ollama pull nomic-embed-textAdd the Model Hub template: Docker tab → Add Container → toggle "Template" mode off → in the Template field near the top paste:
https://raw.githubusercontent.com/aon082910/model-hub/master/unraid/model-hub.xml— Unraid fetches it and pre-fills everything below. (Alternative: copyunraid/model-hub.xmlto/boot/config/plugins/dockerMan/templates-user/on the flash drive and it'll appear under "User templates" instead.)Set Library Path to the Unraid share holding your model files (e.g.
/mnt/user/models/) and App Config/DB to an appdata folder. PUID/PGID default to 99/100 (Unraid'snobody:users) — the container runs as this uid on startup and chowns/configto it, so files it creates show up owned by a real host user instead of root. Apply.Open the WebUI, go to Settings, pick AI mode:
- Local (Ollama) — point "Host" at
http://<unraid-ip>:11434(or the Ollama container's name if both are on the same custom Docker network). Nothing leaves your network. - API — paste an OpenRouter/OpenAI-compatible key. Same model Meshory itself uses today.
- Off — pure manual tagging, no AI calls at all.
- Local (Ollama) — point "Host" at
Click Rescan Library, then Tag All (AI) if you want auto-tagging.
First time you open the WebUI you'll land on a Create admin account screen (skipped if you set Admin Username/Password in step 4). This gates the whole app — see Auth below.
What's implemented vs. Meshory's roadmap
| Feature | Status |
|---|---|
| STL/3MF/OBJ/FBX viewer & thumbnails | STL/OBJ/3MF/FBX all wired to a live Three.js viewer |
| STEP/STP | Parsed for thumbnails/hashing via trimesh where possible; full CAD-assembly preservation is not implemented (no live 3D viewer — three.js has no native STEP support) |
| Duplicate detection (hash + geometry) | Done — SHA256 content hash + normalized-vertex geometry hash |
| Collections | Done |
| Smart/rule-based collections | Done — field/operator/value rules (app/smart_collections.py) |
| AI auto-tagging, local or API | Done — pluggable provider (app/ai/), pausable/resumable batch job, rough cost estimate for paid API mode |
| Semantic search | Done — embeddings stored per-model, cosine similarity search |
| Slicer hand-off | Implemented as network-path + direct-download hand-off (app/routers/slicer.py) — a server container cannot launch an app on your desktop, so this exposes the same share path your slicer can watch/import from, rather than faking a "send to slicer" button |
| Metadata/license/designer tracking | Done — fields on each model, editable in the viewer |
| Filament inventory | Done — CRUD + automatic consumption tracking (deducted when a print queue item is marked "done", see Print estimates) |
| Print queue | Done — ordered queue with status, filament assignment, and estimated grams/time per job |
| Browser extension for Printables/MakerWorld import | Done — browser-extension/ (Manifest V3), see Browser extension below |
| Login/auth | Done — see Auth below |
| Print time / filament weight estimate | Done — see Print estimates below |
| Notifications | Done — see Notifications below |
Auth
A single admin account gates the entire app and API (except /api/health and the
login/setup endpoints themselves). Session is a signed, HttpOnly cookie, 30-day expiry;
password is PBKDF2-SHA256 hashed (200k iterations), never stored or returned in plaintext.
- First run: the WebUI shows a Create admin account screen. Or set
AUTH_USERNAMEAUTH_PASSWORDcontainer env vars to skip it (the account is created from those on first startup only — changing them later does nothing once an account exists).
- Change password: Settings → Account.
- The browser extension does not use this login. It authenticates with a separate,
narrower extension API key (Settings → Browser Extension → copy the key into the
extension popup). That key only unlocks
/api/library/import— nothing else, so a leaked/synced extension key can't read your settings, change your password, or touch the rest of the library. Regenerate it any time from the same Settings panel.
Print time / filament estimates
Every mesh's volume is computed at scan time (app/thumbnails.py → mesh_stats, uses the
real mesh volume for watertight meshes, the convex hull as an approximation otherwise).
From the viewer, pick a material + infill % and click Estimate Print to get a grams/
minutes estimate, or Add to Print Queue to attach that estimate to a queue entry.
Two estimate sources:
- Heuristic (default, no setup) — a volumetric approximation (shell + infill volume ×
material density, flow-rate-based time). Clearly labeled
"source": "heuristic"and typically within ~30-50% of a real slice for simple shapes — good enough for filament budgeting, not for scheduling a print queue to the minute. - Exact (opt-in) — set
SLICER_CLI_PATHto a headless slicer CLI binary bind-mounted into the container (e.g. a PrusaSlicer/OrcaSlicer AppImage extracted with--appimage-extract, since AppImages need FUSE the container doesn't have). When set, the model is actually sliced andestimated_grams/estimated_minutescome straight out of the generated G-code header. Response is then labeled"source": "slicer".
When a print queue item's status is set to done and it has a filament assigned, its
estimated_grams is deducted from that spool's remaining_g exactly once.
Notifications
Settings → Notifications → a webhook URL, fired (best-effort, failures are logged and never block the underlying job) when a background scan finds new files or a tagging job finishes. There's no way for a container to reach into the Unraid host and call its native notify script, so this is a generic JSON POST instead — point it at:
- ntfy.sh (free, has an Unraid Community Apps entry for push notifications), or
- a Discord/Slack incoming webhook URL, or
- an Unraid User Script configured with a webhook trigger.
Browser extension
browser-extension/ is a separate, small Chrome/Edge (Manifest V3) extension. It is not
part of the Docker image — it installs in your browser and talks to your running Model Hub
server over the network.
Install (unpacked, until it's published to a store):
- Open
chrome://extensions, enable Developer mode. - Load unpacked → select the
browser-extension/folder. - In Model Hub, go to Settings → Browser Extension and copy the API key.
- Click the extension icon → enter your Model Hub server URL (e.g.
http://192.168.1.50:8420— your Unraid host's IP and the WebUI port) and paste the API key → Save (grants the extension permission to reach that one origin) → Test Connection.
Use: open a model page on printables.com or makerworld.com. A 📦 Send to Model Hub
button appears bottom-right. It reads the page's schema.org JSON-LD (title/author/license —
the same structured data search engines use, which is far less brittle than scraping CSS
classes) and scans the page for direct .stl/.3mf/.step/.obj/.fbx/.zip links, lets you
pick which files and edit designer/license, then downloads each file and POSTs it to
/api/library/import on your server, which files it under imported/ in your library and
tags it with the source URL/designer/license automatically.
Caveat: Printables sits behind a Cloudflare bot-check on some requests/regions, and both sites can change their markup. The extension deliberately avoids hardcoded CSS selectors (hence the JSON-LD approach) to stay resilient, but if a site's download links are themselves gated behind JS or auth you'll need to open the direct file URL in a tab first.
Architecture
- Backend: FastAPI + SQLModel (SQLite) —
app/main.py,app/routers/* - Scanning:
app/scanner.pywalks the mounted library, hashes files, detects duplicates - Thumbnails:
app/thumbnails.pyvia trimesh (headless render, matplotlib fallback); also computes volume/watertightness for print estimates - AI:
app/ai/—OllamaProvider(local) andAPIProvider(OpenAI/OpenRouter-compatible), selected per theai_modesetting - Auth:
app/auth.py+app/routers/auth_router.py— session cookie for the WebUI, a separate narrower-scoped API key for the browser extension, enforced by a single ASGI middleware inmain.py - Print estimates:
app/estimate.py— volumetric heuristic by default, or exact numbers via an optional external slicer CLI (SLICER_CLI_PATH) - Notifications:
app/notify.py— generic webhook POST, best-effort - Migrations:
app/db.pyauto-adds new columns to existing SQLite tables on startup (no Alembic; fine for this project's size, but note it if you fork it) - Frontend: vanilla JS + Three.js, no build step (
app/static/)
CI / Tests
tests/test_app.py is an end-to-end integration suite against a real (temp-dir) instance
of the app — auth setup/login/logout, the extension API key's scope (import-only), mesh
volume parsing on a real generated STL, duplicate detection, print estimates, and the
filament-deduction-happens-exactly-once behavior. Run locally:
pip install -r requirements-dev.txt
pytest
.github/workflows/ci.yml runs that suite on every push/PR, then (on push to master or
a v* tag, and only once tests pass) builds and pushes the image to Docker Hub. That push
step needs two repository secrets that aren't set by default — add them under
Settings → Secrets and variables → Actions:
DOCKERHUB_USERNAME— your Docker Hub usernameDOCKERHUB_TOKEN— a Docker Hub access token (not your password)
Without those secrets the test job still runs fine; only the docker job (which needs
them to authenticate) will fail.
Install ModelHub on Unraid in a few clicks.
Find ModelHub in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Requirements
Community Applications first) if you want local AI instead of an API key.
Categories
Download Statistics
Related apps
Explore more like this
Explore allDetails
allornothing/model-hub:latestRuntime arguments
- Web UI
http://[IP]:[PORT:8420]/- Network
bridge- Shell
bash- Privileged
- false
Template configuration
Port for the Model Hub web interface.
- Target
- 8420
- Default
- 8420
- Value
- 8420
Folder containing your STL/3MF/OBJ/STEP/FBX files. Read-only usage: files are hashed and thumbnailed, never modified.
- Target
- /data
- Default
- /mnt/user/models/model-hub-library
- Value
- /mnt/user/models/model-hub-library
Where the SQLite database, thumbnails, and settings are stored.
- Target
- /config
- Default
- /mnt/user/appdata/model-hub
- Value
- /mnt/user/appdata/model-hub
Seconds between automatic background library scans.
- Target
- SCAN_INTERVAL_SECONDS
- Default
- 300
- Value
- 300
Address of your Ollama container/instance for local AI tagging. Only used when AI mode is set to 'local' in the app's Settings tab.
- Target
- OLLAMA_HOST
- Default
- http://ollama:11434
- Value
- http://ollama:11434
Vision-capable Ollama model used for auto-tagging thumbnails.
- Target
- OLLAMA_VISION_MODEL
- Default
- llava
- Value
- llava
Ollama embedding model used for semantic search.
- Target
- OLLAMA_EMBED_MODEL
- Default
- nomic-embed-text
- Value
- nomic-embed-text
Creates the admin login automatically on first start. Leave blank to set it up from the WebUI's first-run screen instead.
- Target
- AUTH_USERNAME
Paired with Admin Username above. Leave both blank to use the in-app setup screen instead (recommended -- avoids the password sitting in plain text in the container's env).
- Target
- AUTH_PASSWORD
Optional: absolute path *inside the container* to a headless slicer CLI binary (e.g. an extracted PrusaSlicer/OrcaSlicer AppImage) for exact print time/filament estimates instead of the built-in volumetric approximation. Bind-mount the binary in via 'Add another Path' first, then point this at it.
- Target
- SLICER_CLI_PATH