apps.header.allAppsCount
media-vault
apps.detail.types.app from halvar100's Repository
apps.detail.sections.overview
Readme
View on GitHubmedia-vault
Working name — placeholder. A self-hosted, open-source catalog for physical media collections: video games, movies, vinyl LPs & singles, CDs. Your data lives on your server. Think "Jellyfin for physical-media collectors."
Each item automatically gets a cover image, a rating, and a description, fetched from the right database per media type (IGDB for games, TMDB for films, Discogs for vinyl/CD) and unified into one browsable spine shelf.
Why self-hosted
People who collect physical media do so precisely because they distrust digital-only, cloud-locked ownership. So this app is one you run on your own server, with your own data and your own free API keys. No global accounts, no SaaS lock-in.
Features (v1)
- Unified catalog across games / movies / vinyl LPs / singles / CDs — one item model.
- Automatic artwork — cover image + rating + description auto-fetched per media type.
- Signature spine-shelf view (colour = platform/format) plus a gallery grid.
- Add flows: title search with auto-fill, CSV bulk import, best-effort in-browser barcode scan.
- Server-side metadata cache — each title fetched once, respecting API terms.
- Valuation — per-item market value + a collection total, condition-aware. Music via Discogs marketplace (free), games via PriceCharting (paid API token required), and manual values for anything else.
- Multi-user within one instance (e.g. a household) via local email + password.
Metadata sources (bring your own free keys)
| Media | Source | Get a key |
|---|---|---|
| Games | IGDB (via Twitch app) | https://dev.twitch.tv/console/apps |
| Movies | TMDB | https://www.themoviedb.org/settings/api |
| Vinyl / Singles / CD | Discogs | https://www.discogs.com/settings/developers |
Enrichment stays idle for any source whose keys are blank — the app still runs.
Quick start (Docker)
cp .env.example .env # then fill in your keys + a SESSION_SECRET
docker compose up -d # pulls the prebuilt image (no build step)
Open http://localhost:8080, log in as the seed user (admin@media-vault.local /
SEED_USER_PASSWORD), then click Enrich collection to fetch covers/ratings/descriptions.
The app is a single image (API + UI on one port) plus a Postgres container.
Updating is just a pull — no rebuild:
docker compose pull && docker compose up -d
Images are published automatically to ghcr.io/halvar20000/media-vault on every commit.
Unraid
Install a postgres:16-alpine container first and point the DB variables at it, then either:
- Community Applications — search for media-vault in the Apps tab (once listed), or
- Docker → Add Container → Template with:
https://raw.githubusercontent.com/halvar20000/media-vault/main/templates/media-vault.xml, or - the Docker Compose Manager plugin with the
docker-compose.ymlabove.
The CA store listing is defined by ca_profile.xml + templates/media-vault.xml.
Local development (no Docker)
Requires Node 20+ and a local Postgres.
# 1. Postgres — create a db matching your .env, then:
cd backend && npm install && npm run migrate && npm run seed && npm run dev # API on :4000
cd frontend && npm install && npm run dev # UI on :5173 (proxies /api)
Configuration
All config is via environment variables — see .env.example for the full
list (database, session secret, app origin, and the three metadata-source keys).
MARKETPLACE picks which second-hand marketplace(s) the find deals / bundles buttons
search, so titles and lot phrasing come out in each site's language. Comma-separated — each
value adds its own button (e.g. MARKETPLACE=leboncoin,medimops,kleinanzeigen). Values:
leboncoin (France, default), kleinanzeigen (Germany), ebay-de, ebay-com, ebay-uk,
ebay-fr, marktplaats (Netherlands), wallapop (Spain), medimops (German used-media shop),
or none to hide the buttons.
Tech stack
- Backend — Node.js + TypeScript + Express, PostgreSQL (
pg, plain SQL migrations). - Frontend — React + Vite + TypeScript.
- Packaging — docker-compose (Postgres + backend + nginx). Works behind Cloudflare Tunnel / nginx / Traefik.
Project docs
PROJECT_BRIEF.md— founding spec.CHAT_SUMMARY.md— design reasoning.media-vault-prototype.html— original design prototype.
License
AGPL-3.0 — anyone who offers this as a network service must share their changes back to the community.
apps.marketingCta.appInstallTitle
apps.marketingCta.appInstallDescription
apps.detail.sections.requirements
apps.detail.sections.categories
apps.detail.sections.related
apps.detail.related.exploreCategories
apps.detail.related.exploreAllapps.detail.sections.links
apps.detail.sections.details
ghcr.io/halvar20000/media-vault:latestapps.detail.sections.runtime
- apps.detail.details.webui
http://[IP]:[PORT:4000]- apps.detail.details.network
bridge- apps.detail.details.shell
sh- apps.detail.details.privileged
- false
apps.detail.sections.configuration
Port the app is reached on.
- apps.detail.config.target
- 4000
- apps.detail.config.default
- 8080
- apps.detail.config.value
- 8080
Where cached cover images are stored.
- apps.detail.config.target
- /app/data/covers
- apps.detail.config.default
- /mnt/user/appdata/media-vault/covers
- apps.detail.config.value
- /mnt/user/appdata/media-vault/covers
Postgres container IP or hostname.
- apps.detail.config.target
- POSTGRES_HOST
Postgres port.
- apps.detail.config.target
- POSTGRES_PORT
- apps.detail.config.default
- 5432
- apps.detail.config.value
- 5432
Database name.
- apps.detail.config.target
- POSTGRES_DB
- apps.detail.config.default
- mediavault
- apps.detail.config.value
- mediavault
Database user.
- apps.detail.config.target
- POSTGRES_USER
- apps.detail.config.default
- mediavault
- apps.detail.config.value
- mediavault
Database password.
- apps.detail.config.target
- POSTGRES_PASSWORD
Long random string for signing session cookies.
- apps.detail.config.target
- SESSION_SECRET
Public URL of the app (e.g. http://TOWER-IP:8080).
- apps.detail.config.target
- APP_ORIGIN
- apps.detail.config.default
- http://localhost:8080
- apps.detail.config.value
- http://localhost:8080
Let new users self-register. Keep false on an exposed instance.
- apps.detail.config.target
- ALLOW_REGISTRATION
- apps.detail.config.default
- false
- apps.detail.config.value
- false
Which second-hand marketplace(s) the 'find deals / bundles' buttons search. Comma-separated — each value adds its own button (e.g. leboncoin,medimops,kleinanzeigen). Values: leboncoin (France), kleinanzeigen (Germany), ebay-de, ebay-com, ebay-uk, ebay-fr, marktplaats (Netherlands), wallapop (Spain), medimops (German used-media shop), or none to hide the buttons.
- apps.detail.config.target
- MARKETPLACE
- apps.detail.config.default
- leboncoin
- apps.detail.config.value
- leboncoin
Set to 'production' ONLY behind HTTPS (reverse proxy); otherwise leave 'development'.
- apps.detail.config.target
- NODE_ENV
- apps.detail.config.default
- development
- apps.detail.config.value
- development
Default login created on first boot.
- apps.detail.config.target
- SEED_USER_EMAIL
- apps.detail.config.default
- admin@media-vault.local
- apps.detail.config.value
- admin@media-vault.local
Change this before exposing the instance.
- apps.detail.config.target
- SEED_USER_PASSWORD
- apps.detail.config.default
- changeme
- apps.detail.config.value
- changeme
Twitch app Client ID (games).
- apps.detail.config.target
- IGDB_CLIENT_ID
Twitch app Client Secret (games).
- apps.detail.config.target
- IGDB_CLIENT_SECRET
TMDB v4 Read Access Token (movies).
- apps.detail.config.target
- TMDB_ACCESS_TOKEN
Movie metadata language, e.g. de-DE.
- apps.detail.config.target
- TMDB_LANGUAGE
- apps.detail.config.default
- en-US
- apps.detail.config.value
- en-US
Discogs consumer key (vinyl/CD).
- apps.detail.config.target
- DISCOGS_KEY
Discogs consumer secret (vinyl/CD).
- apps.detail.config.target
- DISCOGS_SECRET
PAID PriceCharting API token for game valuation (optional; music valuation uses Discogs, movies have no price source).
- apps.detail.config.target
- PRICECHARTING_TOKEN
Currency for Discogs marketplace prices (EUR, USD, GBP…).
- apps.detail.config.target
- VALUATION_CURRENCY
- apps.detail.config.default
- EUR
- apps.detail.config.value
- EUR