Import the MetaMesh app family from Worph/AppStore

23 apps (MetaCore, the library/network/client tiers and 13 MetaFeeder*)
move out of the general-purpose Worph/AppStore into their own store so the
MetaMesh family releases on its own cadence.

- asset URLs repointed to cdn.jsdelivr.net/gh/Worph/MetaAppStore@main
- store scaffolding added (category/recommend/featured lists, licence,
  contributing, gitattributes)
- psd-source/meta-icons-v2 icon generator follows the apps

MetaMCP stays in Worph/AppStore: it is third-party metatool-ai software,
not part of MetaMesh.
This commit is contained in:
Worph
2026-09-13 16:18:29 +00:00
parent 998eba19f4
commit 39f82e1f96
145 changed files with 12964 additions and 1 deletions
+263
View File
@@ -0,0 +1,263 @@
name: metafeedertmdb
# =============================================================================
# MetaFeeder · TMDB — the CARD TIER feeder for MetaMesh.
#
# A "card" is a metadata-only record identifying a *work* (a series, a film) —
# title, poster, description and an id bag (tmdb/imdb/…). It has no bytes and by
# design never will. This feeder resolves cards from **TMDB** so a free-text
# search in **MetaWatch** returns a clean poster grid of *works* instead of a
# wall of raw release names.
#
# Why it is its own tier: search is split in two halves.
# Phase 1 — CARD (this app): <free text> → one cached TMDB search
# → a poster grid of works
# Phase 2 — RETRIEVAL (indexer / usenet / tribler feeders): the user clicks a
# card → the full indexer budget is spent on
# that ONE work → the episode / version list.
# Previously a single query resolved ten speculative candidate works *and*
# searched every indexer for all of them against one shared rate bucket, so the
# work the user actually wanted got a tenth of the budget. Installing this app
# is what makes the first half cheap.
#
# Cheap and self-contained: it calls TMDB directly, holds no bytes, runs no
# enrichment sidecars, and never touches an indexer. It also does NOT talk to
# meta-core itself — the MetaGateway core stores and announces the cards it
# returns.
#
# It needs, over the shared `pcs` network:
# - the **MetaGateway** app — which must list this feeder's URL
# (`http://metafeedertmdb-feeder:8080`) in its feeders config so it gets
# discovered. Discovery is startup-only, so restart the gateway afterwards.
#
# Everything is scoped under the `metafeedertmdb-*` prefix + the per-stack
# `metafeedertmdb` network + /DATA/AppData/metafeedertmdb/ data root, so it
# installs alongside the other Meta* apps with zero collisions.
# =============================================================================
services:
# ---------------------------------------------------------------------------
# metafeedertmdb-feeder — the feeder (meta-feeder-tmdb) hosting the `tmdb`
# upstream. The gateway core reaches it directly on `pcs` for
# discovery/query/compute; the human configures it through the AppShield gate
# below.
# ---------------------------------------------------------------------------
metafeedertmdb-feeder:
image: ghcr.io/worph/meta-feeder-tmdb:1.0.2
cpu_shares: 30
container_name: metafeedertmdb-feeder
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM instead of
# swap. This feeder holds no bytes and does far less than the indexer feeder
# (one TMDB call + a redb cache), so this is generous, not tight.
mem_limit: 256m
memswap_limit: 256m
hostname: metafeedertmdb-feeder
restart: unless-stopped
user: "0:0"
expose:
- 8080
environment:
# INFRA-ONLY env (the "config: no env if in webui" rule). Operator config —
# TMDB token, rate/burst, cards-per-search — is read from the persisted
# config.json written by this feeder's config page (behind the AppShield
# gate below). TMDB_TOKEN / TMDB_RATE_PER_SEC / TMDB_BURST / CARD_TOP_N
# exist only as deprecated first-boot seeds; don't set them here.
# META_FEEDER_STATE_DIR is left at its default (/data/meta-feeder), which
# is what the volume below mounts.
RUST_LOG: "info,meta_feeder=info,card_feeder=info"
TZ: "$TZ"
volumes:
# Persist feeder config (config.json saved via the config UI) + the redb
# TMDB response cache. Wiping the cache is safe but re-burns TMDB budget
# on every card the swarm re-discovers.
- ${DATA_ROOT:-/DATA}/AppData/metafeedertmdb/state:/data/meta-feeder:rw
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 5s
timeout: 3s
retries: 10
start_period: 5s
networks:
- metafeedertmdb
- pcs
# ---------------------------------------------------------------------------
# AppShield gate — the feeder's config UI (set the TMDB token, tune the rate
# and cards-per-search) behind Yundera OIDC SSO. container_name == subdomain
# prefix. The gateway still reaches the feeder directly on `pcs` (this gate is
# for the human only); index → /config.
# ---------------------------------------------------------------------------
metafeedertmdb:
image: ghcr.io/yundera/appshield:2.0.9
cpu_shares: 80
container_name: metafeedertmdb
hostname: metafeedertmdb
restart: unless-stopped
user: "0:0"
expose:
- 80
environment:
OIDC_REGISTRAR_URL: "http://auth-registrar:9092"
REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io"
BACKEND_HOST: "metafeedertmdb-feeder"
BACKEND_PORT: "8080"
LISTEN_PORT: "80"
labels:
caddy_0: metafeedertmdb-${APP_DOMAIN}
caddy_0.import: gateway_tls
caddy_0.reverse_proxy: "{{upstreams 80}}"
caddy_1: metafeedertmdb-${APP_PUBLIC_IP_DASH}.nip.io
caddy_1.import: gateway_tls
caddy_1.reverse_proxy: "{{upstreams 80}}"
caddy_2: metafeedertmdb-${APP_PUBLIC_IP_DASH}.sslip.io
caddy_2.reverse_proxy: "{{upstreams 80}}"
depends_on:
- metafeedertmdb-feeder
networks:
- metafeedertmdb
- pcs
networks:
metafeedertmdb:
name: metafeedertmdb
driver: bridge
pcs:
name: ${APP_NET:-pcs}
external: true
x-casaos:
architectures:
- amd64
- arm64
author: Worph Team
category: Media
developer: Aptero Team
main: metafeedertmdb
webui_port: 80
index: /config
description:
en_us: |
**The card tier — the half of MetaMesh search that makes it feel like a
streaming app.**
MetaFeeder · TMDB resolves **cards** from **TMDB**: metadata-only records
identifying a *work* — a series or a film — with title, poster, plot and a
bag of cross-source ids. A card carries no bytes. Its job is to turn a
free-text search in **MetaWatch** into a clean **poster grid of works**
instead of a wall of raw release names.
**Why it exists.** Search is split in two:
1. **Card** (this app) — one cached TMDB lookup → a grid of works. Costs
no indexer requests at all.
2. **Retrieval** (**MetaFeeder · Prowlarr**, **· Usenet**, **· Tribler**) —
you click a card, and the *entire* indexer budget is spent on that one
work → its episode and version list.
Before the split, a single query speculatively resolved ten candidate works
*and* searched every indexer for all of them against one shared rate
bucket — so the work you actually wanted got a tenth of the budget.
**Cheap and self-contained:** it calls TMDB directly, holds no bytes, runs
no enrichment sidecars, and never touches an indexer. ~256 MB RAM.
**Requires:** **MetaGateway** — its meta-core stores the resolved cards and
announces them to the swarm. The gateway must list this feeder in its
config. You also need a free **TMDB** read token.
**Pairs with:** any retrieval feeder (**Indexer**, **Usenet**, **Tribler**)
— cards are what you click *before* those do their work.
fr_fr: |
**La couche « card » — la moitié de la recherche MetaMesh qui lui donne des
allures d'app de streaming.**
MetaFeeder · TMDB résout des **cards** depuis **TMDB** : des enregistrements
purement métadonnées identifiant une *œuvre* — une série, un film — avec
titre, affiche, synopsis et un jeu d'identifiants inter-sources. Une card ne
contient aucun octet. Son rôle : transformer une recherche libre dans
**MetaWatch** en une **grille d'affiches d'œuvres** plutôt qu'en un mur de
noms de releases bruts.
**Pourquoi.** La recherche est scindée en deux :
1. **Card** (cette app) — une requête TMDB mise en cache → une grille
d'œuvres. Ne consomme aucune requête d'indexeur.
2. **Récupération** (**MetaFeeder · Prowlarr**, **· Usenet**, **· Tribler**)
— vous cliquez une card, et *tout* le budget d'indexation est dépensé sur
cette seule œuvre → la liste de ses épisodes et versions.
Auparavant, une seule requête résolvait spéculativement dix œuvres
candidates *et* interrogeait tous les indexeurs pour chacune, sur un même
seau de débit — l'œuvre réellement voulue n'obtenait qu'un dixième du
budget.
**Léger et autonome :** appelle TMDB directement, ne stocke aucun octet,
aucun plugin d'enrichissement, ne touche jamais un indexeur. ~256 Mo de RAM.
**Nécessite :** l'app **MetaGateway** (son meta-core stocke et annonce les
cards) et un jeton de lecture **TMDB** gratuit.
tagline:
en_us: TMDB card tier — turns MetaMesh search into a poster grid of works
fr_fr: Couche « card » TMDB — transforme la recherche MetaMesh en grille d'affiches
title:
en_us: MetaFeeder · TMDB
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederTMDB/icon.png
screenshot_link:
- https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederTMDB/screenshot-1.png
- https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederTMDB/screenshot-2.png
- https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederTMDB/screenshot-3.png
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederTMDB/thumbnail.png
store_app_id: metafeedertmdb
is_uncontrolled: false
tips:
before_install:
en_us: |
**Install order:** install **MetaGateway** first (its meta-core stores
and announces the cards this feeder resolves).
**After installing this app:**
1. Get a free **TMDB v4 read access token** — themoviedb.org →
Settings → API. (The v4 "Read Access Token", not the v3 API key.)
2. Open this app's config UI (the dashboard link) and paste it into
**TMDB v4 token**. Leave the rate, burst and cards-per-search fields
blank unless you have a reason — the defaults (20 req/s, burst 20,
10 cards per search) are sane. Save, then restart this app: the
feeder reads its config at startup, there is no hot reload.
3. In **MetaGateway**, add this feeder to its feeders config —
name `tmdb-feeder`, URL `http://metafeedertmdb-feeder:8080` — from
the gateway dashboard's *Add a feeder* box. The gateway restarts
itself and picks up the `tmdb` upstream (it will show **LIVE**, with
the tags CARD / MOVIE / SERIES).
**Without a TMDB token** the feeder stays healthy but soft-skips: card
searches simply return nothing. That is by design, not a failure.
**Cards vs. results.** This app only produces the poster grid. To
actually get episodes and streams behind a card you also need a
retrieval feeder — **MetaFeeder · Prowlarr**, **· Usenet** or
**· Tribler**.
**Authentication:** the config UI is protected by Yundera's Authelia (OIDC).
# Maison (Yundera dashboard) configuration. Read in preference to x-casaos.
# schema_version 2 is required for `folders`: an older Maison refuses the app
# outright rather than starting it without its directories.
x-compose-app:
schema_version: 2
# Created and chowned by Maison before any image is pulled, before any hook
# runs and before the containers start — on the first boot and every one after.
# Compose would otherwise create a missing bind source as a root-owned dir.
folders:
- path: /DATA/AppData/metafeedertmdb/state
user: $PUID
group: $PGID
# Same string as the caddy_0 label, so the tile link and the route agree.
webui-host: metafeedertmdb-${domain}
webui-path: "/config"
Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

+41
View File
@@ -0,0 +1,41 @@
# MetaFeeder · TMDBs — Rationale
## What deviation / exception is being requested
`metafeedertmdb-feeder` runs `user: 0:0`.
## Why it is necessary
The feeder publishes its resolved records to the shared MetaCore over HTTP and writes its
own service registration and plugin artwork through MetaCore's WebDAV endpoint. Those
writes land in directories created and owned by MetaCore's root-running container; a
uid-1000 process is refused. The feeder image also runs a small supervised process group
(fetcher + enrichment plugin clients) that expects to be able to signal its children.
## Security mitigations in place
- Public ingress terminates at the AppShield sidecar (`ghcr.io/yundera/appshield:2.0.9`),
which gates every path behind the PCS's Authelia SSO over OIDC. The feeder carries no
Caddy labels and publishes no host port, so it is reachable only from the `pcs` Docker
network.
- The container mounts **no user directories** — not `/DATA/Documents`, `/DATA/Downloads`,
`/DATA/Media` or `/DATA/Gallery`. Its only bind is its own state under
`/DATA/AppData/metafeedertmdb/`, declared in `x-compose-app.folders` and owned `$PUID:$PGID`.
- `cpu_shares` set on every service; image tags pinned; no `:latest`.
## Alternatives considered and rejected
- **Run as `$PUID:$PGID`.** Writes into MetaCore's root-owned `/meta-core` tree and through
its WebDAV endpoint fail, and the feeder starts but silently publishes nothing.
- **Have MetaCore relax the ownership of its shared directory.** That would loosen
permissions for every MetaMesh app at once, which is strictly worse than one root
container with no user-data mounts.
- **Drop privileges after startup inside the image.** Upstream change; tracked, not
available in the shipped image.
## Data protection
The feeder holds no user media. Its entire state — configuration, cursor position, and
(where applicable) the TMDB cache — lives under `/DATA/AppData/metafeedertmdb/`, which is the unit
Maison archives on uninstall and restores from backup, so an uninstall/reinstall with "keep
user data" resumes where it left off rather than re-indexing from scratch.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB