add app for each plugin
@@ -0,0 +1,172 @@
|
||||
name: metafeederinternetarchive
|
||||
|
||||
# =============================================================================
|
||||
# MetaFeeder · Internet Archive — free audio from archive.org for MetaMesh.
|
||||
#
|
||||
# Bridges the Internet Archive's open audio collections (etree live recordings,
|
||||
# netlabels, 78rpm transfers) into MetaMesh. Keyless and open: no account, no
|
||||
# API key, nothing to configure. Covers are taken from the archive's own
|
||||
# thumbnail service, which answers for every item — without a cover the
|
||||
# consumer's quality gate would drop the whole free tier into the raw bucket.
|
||||
#
|
||||
# It needs, over the shared `pcs` network:
|
||||
# - the **MetaGateway** app — which must list this feeder's URL
|
||||
# (`http://metafeederinternetarchive-feeder:8080`) in its feeders config so it gets discovered.
|
||||
# Discovery is startup-only, so restart the gateway afterwards.
|
||||
#
|
||||
# Everything is scoped under the `metafeederinternetarchive-*` prefix + the per-stack `metafeederinternetarchive` network +
|
||||
# /DATA/AppData/metafeederinternetarchive/ data root, so it installs beside the other Meta* apps with
|
||||
# zero collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metafeederinternetarchive-feeder — the feeder itself. The gateway reaches it directly on
|
||||
# `pcs` for discovery/query/compute; the human sees it through the AppShield
|
||||
# gate below.
|
||||
# ---------------------------------------------------------------------------
|
||||
metafeederinternetarchive-feeder:
|
||||
image: ghcr.io/worph/meta-feeder-internetarchive:1.1.2
|
||||
cpu_shares: 30
|
||||
container_name: metafeederinternetarchive-feeder
|
||||
# Bytes tier: a single archive item can list hundreds of files and one
|
||||
# track is buffered whole before it is handed over, so 256m (the identity
|
||||
# feeders' figure) is too tight. Swap disabled → OOM instead of thrash.
|
||||
mem_limit: 512m
|
||||
memswap_limit: 512m
|
||||
hostname: metafeederinternetarchive-feeder
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
RUST_LOG: "info,meta_feeder=info,internetarchive_feeder=info"
|
||||
META_FEEDER_STATE_DIR: "/data/meta-feeder"
|
||||
TZ: "$TZ"
|
||||
volumes:
|
||||
# Persist feeder config.json + redb cache so it survives recreate.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/metafeederinternetarchive/state:/data/meta-feeder:rw
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
networks:
|
||||
- metafeederinternetarchive
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the feeder's config page behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). The gateway still reaches the feeder directly on `pcs`; index → /config.
|
||||
# ---------------------------------------------------------------------------
|
||||
metafeederinternetarchive:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metafeederinternetarchive
|
||||
hostname: metafeederinternetarchive
|
||||
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: "metafeederinternetarchive-feeder"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metafeederinternetarchive-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metafeederinternetarchive-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metafeederinternetarchive-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metafeederinternetarchive-feeder
|
||||
networks:
|
||||
- metafeederinternetarchive
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metafeederinternetarchive:
|
||||
name: metafeederinternetarchive
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Media
|
||||
developer: Aptero Team
|
||||
main: metafeederinternetarchive
|
||||
webui_port: 80
|
||||
index: /config
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**Free audio from the Internet Archive, on the MetaMesh network.**
|
||||
|
||||
MetaFeeder · Internet Archive bridges archive.org's open audio collections —
|
||||
**etree** live recordings, **netlabels**, **78rpm** transfers — into MetaMesh.
|
||||
Keyless and open: nothing to sign up for, nothing to configure.
|
||||
|
||||
It is a *bytes* tier: the audio it finds is fetched and seeded to the swarm,
|
||||
so a track stays playable for the next peer that asks.
|
||||
**Requires:** **MetaGateway** — its meta-core stores the resolved records and
|
||||
announces them to the swarm. The gateway must list this feeder in its config.
|
||||
fr_fr: |
|
||||
**L'audio libre d'Internet Archive, sur le réseau MetaMesh.**
|
||||
|
||||
MetaFeeder · Internet Archive connecte les collections audio ouvertes
|
||||
d'archive.org (**etree**, **netlabels**, **78rpm**) à MetaMesh. Sans clé et
|
||||
sans configuration. C'est une couche *octets* : l'audio trouvé est récupéré
|
||||
puis partagé au swarm. **Nécessite** l'app **MetaGateway**.
|
||||
tagline:
|
||||
en_us: Internet Archive free audio (etree, netlabels, 78rpm) for MetaMesh
|
||||
fr_fr: L'audio libre d'Internet Archive (etree, netlabels, 78rpm) pour MetaMesh
|
||||
|
||||
title:
|
||||
en_us: MetaFeeder · Internet Archive
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederInternetArchive/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederInternetArchive/thumbnail.png
|
||||
|
||||
store_app_id: metafeederinternetarchive
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
**Install order:** install **MetaGateway** first (its meta-core stores and
|
||||
announces what this feeder finds).
|
||||
|
||||
**After installing this app:** in **MetaGateway**, add this feeder from the
|
||||
dashboard's *Add a feeder* box — name `internetarchive-feeder`, URL `http://metafeederinternetarchive-feeder:8080`.
|
||||
The gateway restarts itself: feeder discovery is startup-only.
|
||||
No API key and no account: the feeder answers as soon as the gateway
|
||||
discovers it. Pairs with **MetaListen** for a music client.
|
||||
**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/metafeederinternetarchive/state
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metafeederinternetarchive-${domain}
|
||||
webui-path: "/config"
|
||||
|
After Width: | Height: | Size: 131 KiB |
@@ -0,0 +1,35 @@
|
||||
# MetaFeeder · Internet Archive — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metafeederinternetarchive-feeder` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
Carried over unchanged from the MetaFeeder listings this app is a sibling of, so the
|
||||
feeder behaves exactly as they do. The MetaFeeder images write their state and redb
|
||||
cache as the container user and some reach MetaCore's root-owned tree; a uid-1000
|
||||
process is refused.
|
||||
|
||||
## 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/metafeederinternetarchive/`, declared in `x-compose-app.folders` and owned `$PUID:$PGID`.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for this image; the sibling MetaFeeder apps all
|
||||
run `0:0`, and changing the runtime user here would make this listing the odd one out
|
||||
for no measured benefit.
|
||||
|
||||
## Data protection
|
||||
|
||||
The feeder holds no user media. Its entire state lives under `/DATA/AppData/metafeederinternetarchive/`, which
|
||||
is the unit Maison archives on uninstall and restores from backup.
|
||||
|
After Width: | Height: | Size: 131 KiB |
@@ -0,0 +1,179 @@
|
||||
name: metafeederjamendo
|
||||
|
||||
# =============================================================================
|
||||
# MetaFeeder · Jamendo — Creative-Commons music for MetaMesh.
|
||||
#
|
||||
# Opt-in: soft-skips (healthy, answers nothing) unless JAMENDO_CLIENT_ID is set.
|
||||
#
|
||||
# ⚠ Jamendo's non-commercial tier is a MONTHLY BUDGET (35,000 requests), not a
|
||||
# per-second rate. Spend it and the upstream is gone until the calendar turns —
|
||||
# a backoff does not recover it. The feeder tracks the budget itself; don't
|
||||
# raise it here without reading its consts.rs.
|
||||
#
|
||||
# It needs, over the shared `pcs` network:
|
||||
# - the **MetaGateway** app — which must list this feeder's URL
|
||||
# (`http://metafeederjamendo-feeder:8080`) in its feeders config so it gets discovered.
|
||||
# Discovery is startup-only, so restart the gateway afterwards.
|
||||
#
|
||||
# Everything is scoped under the `metafeederjamendo-*` prefix + the per-stack `metafeederjamendo` network +
|
||||
# /DATA/AppData/metafeederjamendo/ data root, so it installs beside the other Meta* apps with
|
||||
# zero collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metafeederjamendo-feeder — the feeder itself. The gateway reaches it directly on
|
||||
# `pcs` for discovery/query/compute; the human sees it through the AppShield
|
||||
# gate below.
|
||||
# ---------------------------------------------------------------------------
|
||||
metafeederjamendo-feeder:
|
||||
image: ghcr.io/worph/meta-feeder-jamendo:1.1.2
|
||||
cpu_shares: 30
|
||||
container_name: metafeederjamendo-feeder
|
||||
# Bytes tier: one mp32 derivative is buffered whole before hand-off.
|
||||
# Swap disabled (memswap_limit == mem_limit) → OOM instead of thrash.
|
||||
mem_limit: 512m
|
||||
memswap_limit: 512m
|
||||
hostname: metafeederjamendo-feeder
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
RUST_LOG: "info,meta_feeder=info,jamendo_feeder=info"
|
||||
META_FEEDER_STATE_DIR: "/data/meta-feeder"
|
||||
# Jamendo is opt-in. Blank → the feeder soft-skips. This image has no
|
||||
# config-UI field for the client id yet, so it is read from the environment.
|
||||
JAMENDO_CLIENT_ID: "${JAMENDO_CLIENT_ID:-}"
|
||||
TZ: "$TZ"
|
||||
volumes:
|
||||
# Persist feeder config.json + redb cache so it survives recreate.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/metafeederjamendo/state:/data/meta-feeder:rw
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
networks:
|
||||
- metafeederjamendo
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the feeder's config page behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). The gateway still reaches the feeder directly on `pcs`; index → /config.
|
||||
# ---------------------------------------------------------------------------
|
||||
metafeederjamendo:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metafeederjamendo
|
||||
hostname: metafeederjamendo
|
||||
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: "metafeederjamendo-feeder"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metafeederjamendo-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metafeederjamendo-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metafeederjamendo-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metafeederjamendo-feeder
|
||||
networks:
|
||||
- metafeederjamendo
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metafeederjamendo:
|
||||
name: metafeederjamendo
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Media
|
||||
developer: Aptero Team
|
||||
main: metafeederjamendo
|
||||
webui_port: 80
|
||||
index: /config
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**Creative-Commons music for the MetaMesh network.**
|
||||
|
||||
MetaFeeder · Jamendo bridges **Jamendo**'s Creative-Commons catalogue into
|
||||
MetaMesh as a *bytes* tier: tracks are fetched and seeded to the swarm.
|
||||
|
||||
**Opt-in.** It needs a free Jamendo client id; without one it stays healthy
|
||||
and simply finds nothing. Jamendo's non-commercial tier is a **monthly
|
||||
budget** (35,000 requests), not a rate limit — the feeder paces itself
|
||||
against it.
|
||||
**Requires:** **MetaGateway** — its meta-core stores the resolved records and
|
||||
announces them to the swarm. The gateway must list this feeder in its config.
|
||||
fr_fr: |
|
||||
**La musique Creative Commons pour le réseau MetaMesh.**
|
||||
|
||||
MetaFeeder · Jamendo connecte le catalogue Creative Commons de **Jamendo**
|
||||
à MetaMesh : les morceaux sont récupérés puis partagés au swarm.
|
||||
|
||||
**Sur activation** : nécessite un identifiant client Jamendo gratuit ; sans
|
||||
lui, le feeder reste sain et ne trouve rien. Le quota Jamendo est **mensuel**
|
||||
(35 000 requêtes). **Nécessite** l'app **MetaGateway**.
|
||||
tagline:
|
||||
en_us: Jamendo Creative-Commons music for MetaMesh (client id required)
|
||||
fr_fr: Musique Creative Commons Jamendo pour MetaMesh (identifiant client requis)
|
||||
|
||||
title:
|
||||
en_us: MetaFeeder · Jamendo
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederJamendo/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederJamendo/thumbnail.png
|
||||
|
||||
store_app_id: metafeederjamendo
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
**Install order:** install **MetaGateway** first (its meta-core stores and
|
||||
announces what this feeder finds).
|
||||
|
||||
**After installing this app:** in **MetaGateway**, add this feeder from the
|
||||
dashboard's *Add a feeder* box — name `jamendo-feeder`, URL `http://metafeederjamendo-feeder:8080`.
|
||||
The gateway restarts itself: feeder discovery is startup-only.
|
||||
**Client id:** register a free app at developer.jamendo.com, set
|
||||
`JAMENDO_CLIENT_ID` in this app's environment, then restart the app.
|
||||
Without it the feeder soft-skips. Pairs with **MetaListen**.
|
||||
**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/metafeederjamendo/state
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metafeederjamendo-${domain}
|
||||
webui-path: "/config"
|
||||
|
After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,35 @@
|
||||
# MetaFeeder · Jamendo — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metafeederjamendo-feeder` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
Carried over unchanged from the MetaFeeder listings this app is a sibling of, so the
|
||||
feeder behaves exactly as they do. The MetaFeeder images write their state and redb
|
||||
cache as the container user and some reach MetaCore's root-owned tree; a uid-1000
|
||||
process is refused.
|
||||
|
||||
## 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/metafeederjamendo/`, declared in `x-compose-app.folders` and owned `$PUID:$PGID`.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for this image; the sibling MetaFeeder apps all
|
||||
run `0:0`, and changing the runtime user here would make this listing the odd one out
|
||||
for no measured benefit.
|
||||
|
||||
## Data protection
|
||||
|
||||
The feeder holds no user media. Its entire state lives under `/DATA/AppData/metafeederjamendo/`, which
|
||||
is the unit Maison archives on uninstall and restores from backup.
|
||||
|
After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,180 @@
|
||||
name: metafeedermusicbrainz
|
||||
|
||||
# =============================================================================
|
||||
# MetaFeeder · MusicBrainz — the MUSIC IDENTITY tier for MetaMesh.
|
||||
#
|
||||
# MusicBrainz names the *work*: album, artist, release group, cover art (via the
|
||||
# Cover Art Archive) and the ids the audio tiers anchor on. Keyless, no config.
|
||||
# It serves no audio and must not — pair it with a bytes tier
|
||||
# (Internet Archive, Jamendo) or the delegated YouTube tier.
|
||||
#
|
||||
# ⚠ MusicBrainz publishes a hard 1 request/second ceiling, enforced per source
|
||||
# IP — one misbehaving feeder blocks every peer behind the same NAT. The rate
|
||||
# limiter is in the image; there is no knob here and there should not be.
|
||||
#
|
||||
# It needs, over the shared `pcs` network:
|
||||
# - the **MetaGateway** app — which must list this feeder's URL
|
||||
# (`http://metafeedermusicbrainz-feeder:8080`) in its feeders config so it gets discovered.
|
||||
# Discovery is startup-only, so restart the gateway afterwards.
|
||||
#
|
||||
# Everything is scoped under the `metafeedermusicbrainz-*` prefix + the per-stack `metafeedermusicbrainz` network +
|
||||
# /DATA/AppData/metafeedermusicbrainz/ data root, so it installs beside the other Meta* apps with
|
||||
# zero collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metafeedermusicbrainz-feeder — the feeder itself. The gateway reaches it directly on
|
||||
# `pcs` for discovery/query/compute; the human sees it through the AppShield
|
||||
# gate below.
|
||||
# ---------------------------------------------------------------------------
|
||||
metafeedermusicbrainz-feeder:
|
||||
image: ghcr.io/worph/meta-feeder-musicbrainz:1.1.2
|
||||
cpu_shares: 30
|
||||
container_name: metafeedermusicbrainz-feeder
|
||||
# Identity tier: JSON in, cards out, nothing buffered. Swap disabled
|
||||
# (memswap_limit == mem_limit) → OOM instead of thrash.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
hostname: metafeedermusicbrainz-feeder
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
RUST_LOG: "info,meta_feeder=info,musicbrainz_feeder=info"
|
||||
META_FEEDER_STATE_DIR: "/data/meta-feeder"
|
||||
TZ: "$TZ"
|
||||
volumes:
|
||||
# Persist feeder config.json + redb cache so it survives recreate.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/metafeedermusicbrainz/state:/data/meta-feeder:rw
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
networks:
|
||||
- metafeedermusicbrainz
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the feeder's config page behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). The gateway still reaches the feeder directly on `pcs`; index → /config.
|
||||
# ---------------------------------------------------------------------------
|
||||
metafeedermusicbrainz:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metafeedermusicbrainz
|
||||
hostname: metafeedermusicbrainz
|
||||
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: "metafeedermusicbrainz-feeder"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metafeedermusicbrainz-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metafeedermusicbrainz-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metafeedermusicbrainz-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metafeedermusicbrainz-feeder
|
||||
networks:
|
||||
- metafeedermusicbrainz
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metafeedermusicbrainz:
|
||||
name: metafeedermusicbrainz
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Media
|
||||
developer: Aptero Team
|
||||
main: metafeedermusicbrainz
|
||||
webui_port: 80
|
||||
index: /config
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The music identity tier for MetaMesh.**
|
||||
|
||||
MetaFeeder · MusicBrainz names the *work* — album, artist, release group,
|
||||
cover art from the Cover Art Archive, and the ids every audio tier anchors
|
||||
on. Keyless and cheap.
|
||||
|
||||
It serves **no audio**. Pair it with a bytes tier — **MetaFeeder · Internet
|
||||
Archive** or **MetaFeeder · Jamendo** — or with the delegated **MetaFeeder ·
|
||||
YouTube**: recordings matched to one MusicBrainz work converge on a single
|
||||
album page instead of fragmenting into one tile per spelling.
|
||||
**Requires:** **MetaGateway** — its meta-core stores the resolved records and
|
||||
announces them to the swarm. The gateway must list this feeder in its config.
|
||||
fr_fr: |
|
||||
**La couche d'identité musicale de MetaMesh.**
|
||||
|
||||
MetaFeeder · MusicBrainz nomme l'*œuvre* — album, artiste, groupe de sortie,
|
||||
pochette (Cover Art Archive) et les identifiants sur lesquels s'ancrent les
|
||||
couches audio. Sans clé.
|
||||
|
||||
Il ne sert **aucun audio** : à associer à **MetaFeeder · Internet Archive**,
|
||||
**MetaFeeder · Jamendo** ou **MetaFeeder · YouTube**. **Nécessite** l'app
|
||||
**MetaGateway**.
|
||||
tagline:
|
||||
en_us: MusicBrainz — the music identity tier for MetaMesh
|
||||
fr_fr: MusicBrainz — la couche d'identité musicale de MetaMesh
|
||||
|
||||
title:
|
||||
en_us: MetaFeeder · MusicBrainz
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederMusicBrainz/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederMusicBrainz/thumbnail.png
|
||||
|
||||
store_app_id: metafeedermusicbrainz
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
**Install order:** install **MetaGateway** first (its meta-core stores and
|
||||
announces what this feeder finds).
|
||||
|
||||
**After installing this app:** in **MetaGateway**, add this feeder from the
|
||||
dashboard's *Add a feeder* box — name `musicbrainz-feeder`, URL `http://metafeedermusicbrainz-feeder:8080`.
|
||||
The gateway restarts itself: feeder discovery is startup-only.
|
||||
No API key. For anything playable, also install a bytes tier
|
||||
(**MetaFeeder · Internet Archive**, **MetaFeeder · Jamendo**) or
|
||||
**MetaFeeder · YouTube**. Pairs with **MetaListen**.
|
||||
**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/metafeedermusicbrainz/state
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metafeedermusicbrainz-${domain}
|
||||
webui-path: "/config"
|
||||
|
After Width: | Height: | Size: 138 KiB |
@@ -0,0 +1,35 @@
|
||||
# MetaFeeder · MusicBrainz — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metafeedermusicbrainz-feeder` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
Carried over unchanged from the MetaFeeder listings this app is a sibling of, so the
|
||||
feeder behaves exactly as they do. The MetaFeeder images write their state and redb
|
||||
cache as the container user and some reach MetaCore's root-owned tree; a uid-1000
|
||||
process is refused.
|
||||
|
||||
## 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/metafeedermusicbrainz/`, declared in `x-compose-app.folders` and owned `$PUID:$PGID`.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for this image; the sibling MetaFeeder apps all
|
||||
run `0:0`, and changing the runtime user here would make this listing the odd one out
|
||||
for no measured benefit.
|
||||
|
||||
## Data protection
|
||||
|
||||
The feeder holds no user media. Its entire state lives under `/DATA/AppData/metafeedermusicbrainz/`, which
|
||||
is the unit Maison archives on uninstall and restores from backup.
|
||||
|
After Width: | Height: | Size: 138 KiB |
@@ -0,0 +1,179 @@
|
||||
name: metafeederyoutube
|
||||
|
||||
# =============================================================================
|
||||
# MetaFeeder · YouTube — the DELEGATED playback tier for MetaMesh.
|
||||
#
|
||||
# ⚠ No bytes are ever fetched, transported or seeded by this feeder. It resolves
|
||||
# a reference — an 11-character video id — and playback happens on YouTube. That
|
||||
# is the whole design, and it is why the feeder is cheap and why a dead upstream
|
||||
# instance is a retry rather than a broken stream.
|
||||
#
|
||||
# Video ids are read through a POOL of public Piped instances, tried in order.
|
||||
# Measured during the source study: of four instances, one 502'd, two
|
||||
# 301-redirected away and one answered. The pool is the mitigation; do not pin
|
||||
# it to a single hostname.
|
||||
#
|
||||
# It needs, over the shared `pcs` network:
|
||||
# - the **MetaGateway** app — which must list this feeder's URL
|
||||
# (`http://metafeederyoutube-feeder:8080`) in its feeders config so it gets discovered.
|
||||
# Discovery is startup-only, so restart the gateway afterwards.
|
||||
#
|
||||
# Everything is scoped under the `metafeederyoutube-*` prefix + the per-stack `metafeederyoutube` network +
|
||||
# /DATA/AppData/metafeederyoutube/ data root, so it installs beside the other Meta* apps with
|
||||
# zero collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metafeederyoutube-feeder — the feeder itself. The gateway reaches it directly on
|
||||
# `pcs` for discovery/query/compute; the human sees it through the AppShield
|
||||
# gate below.
|
||||
# ---------------------------------------------------------------------------
|
||||
metafeederyoutube-feeder:
|
||||
image: ghcr.io/worph/meta-feeder-youtube:1.1.1
|
||||
cpu_shares: 30
|
||||
container_name: metafeederyoutube-feeder
|
||||
# Delegated tier: it moves an 11-character video id, never bytes.
|
||||
# Swap disabled (memswap_limit == mem_limit) → OOM instead of thrash.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
hostname: metafeederyoutube-feeder
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
RUST_LOG: "info,meta_feeder=info,youtube_feeder=info"
|
||||
META_FEEDER_STATE_DIR: "/data/meta-feeder"
|
||||
TZ: "$TZ"
|
||||
volumes:
|
||||
# Persist feeder config.json + redb cache so it survives recreate.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/metafeederyoutube/state:/data/meta-feeder:rw
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
networks:
|
||||
- metafeederyoutube
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the feeder's config page behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). The gateway still reaches the feeder directly on `pcs`; index → /config.
|
||||
# ---------------------------------------------------------------------------
|
||||
metafeederyoutube:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metafeederyoutube
|
||||
hostname: metafeederyoutube
|
||||
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: "metafeederyoutube-feeder"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metafeederyoutube-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metafeederyoutube-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metafeederyoutube-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metafeederyoutube-feeder
|
||||
networks:
|
||||
- metafeederyoutube
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metafeederyoutube:
|
||||
name: metafeederyoutube
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Media
|
||||
developer: Aptero Team
|
||||
main: metafeederyoutube
|
||||
webui_port: 80
|
||||
index: /config
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**YouTube as a delegated playback tier for MetaMesh.**
|
||||
|
||||
MetaFeeder · YouTube resolves a *reference*, not a file: playback happens on
|
||||
YouTube and **no bytes are ever transported or seeded**. That makes it the
|
||||
cheapest way to make a catalogue playable — and the one tier that adds
|
||||
nothing to the swarm.
|
||||
|
||||
Video ids are read through a pool of public **Piped** instances, rotating
|
||||
past a dead one. Keyless, nothing to configure.
|
||||
**Pairs with MetaFeeder · MusicBrainz**, which names the recording a
|
||||
candidate has to match (±10 s runtime) before it is accepted.
|
||||
**Requires:** **MetaGateway** — its meta-core stores the resolved records and
|
||||
announces them to the swarm. The gateway must list this feeder in its config.
|
||||
fr_fr: |
|
||||
**YouTube comme couche de lecture déléguée pour MetaMesh.**
|
||||
|
||||
MetaFeeder · YouTube résout une *référence*, pas un fichier : la lecture a
|
||||
lieu sur YouTube et **aucun octet n'est transporté ni partagé**. Les
|
||||
identifiants vidéo sont lus via un pool d'instances **Piped** publiques.
|
||||
Sans clé. **Va avec MetaFeeder · MusicBrainz**. **Nécessite** l'app
|
||||
**MetaGateway**.
|
||||
tagline:
|
||||
en_us: YouTube references for MetaMesh — delegated playback, no bytes seeded
|
||||
fr_fr: Références YouTube pour MetaMesh — lecture déléguée, aucun octet partagé
|
||||
|
||||
title:
|
||||
en_us: MetaFeeder · YouTube
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederYouTube/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaFeederYouTube/thumbnail.png
|
||||
|
||||
store_app_id: metafeederyoutube
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
**Install order:** install **MetaGateway** first (its meta-core stores and
|
||||
announces what this feeder finds).
|
||||
|
||||
**After installing this app:** in **MetaGateway**, add this feeder from the
|
||||
dashboard's *Add a feeder* box — name `youtube-feeder`, URL `http://metafeederyoutube-feeder:8080`.
|
||||
The gateway restarts itself: feeder discovery is startup-only.
|
||||
No API key. For music, also install **MetaFeeder · MusicBrainz** — it
|
||||
names the recording a YouTube candidate must match before it is accepted.
|
||||
**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/metafeederyoutube/state
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metafeederyoutube-${domain}
|
||||
webui-path: "/config"
|
||||
|
After Width: | Height: | Size: 131 KiB |
@@ -0,0 +1,35 @@
|
||||
# MetaFeeder · YouTube — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metafeederyoutube-feeder` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
Carried over unchanged from the MetaFeeder listings this app is a sibling of, so the
|
||||
feeder behaves exactly as they do. The MetaFeeder images write their state and redb
|
||||
cache as the container user and some reach MetaCore's root-owned tree; a uid-1000
|
||||
process is refused.
|
||||
|
||||
## 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/metafeederyoutube/`, declared in `x-compose-app.folders` and owned `$PUID:$PGID`.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for this image; the sibling MetaFeeder apps all
|
||||
run `0:0`, and changing the runtime user here would make this listing the odd one out
|
||||
for no measured benefit.
|
||||
|
||||
## Data protection
|
||||
|
||||
The feeder holds no user media. Its entire state lives under `/DATA/AppData/metafeederyoutube/`, which
|
||||
is the unit Maison archives on uninstall and restores from backup.
|
||||
|
After Width: | Height: | Size: 131 KiB |
@@ -0,0 +1,174 @@
|
||||
name: metapluginanimedetector
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · Anime Detector — the `anime-detector` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ NOTHING IN THE FAMILY CONSUMES IT BY URL YET — install it knowing that.
|
||||
# MetaSort starts its own plugin containers from `plugins.yml` (`image:`) and
|
||||
# has no URL slot. The feeder SDK has exactly three sidecar slots
|
||||
# (`FILENAME_PARSER_URL`, `TMDB_PLUGIN_URL`, `OPENSUBTITLES_PLUGIN_URL`) and
|
||||
# `anime-detector` is not one of them. What this app gives you today is the plugin
|
||||
# running and addressable at `http://metapluginanimedetector-plugin:8080` for anything that
|
||||
# speaks the four-verb plugin contract — your own caller, a probe, a future
|
||||
# consumer. It is packaging ahead of the consumer, on purpose.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginanimedetector-*` prefix + the per-stack `metapluginanimedetector`
|
||||
# network, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginanimedetector-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: fast.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginanimedetector-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-anime-detector:1.0.0
|
||||
cpu_shares: 30
|
||||
container_name: metapluginanimedetector-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
hostname: metapluginanimedetector-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Nothing to configure: this plugin takes its work from the /process
|
||||
# payload and writes back through the caller. No key, no WebDAV.
|
||||
TZ: "$TZ"
|
||||
networks:
|
||||
- metapluginanimedetector
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginanimedetector:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginanimedetector
|
||||
hostname: metapluginanimedetector
|
||||
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: "metapluginanimedetector-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginanimedetector-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginanimedetector-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginanimedetector-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginanimedetector-plugin
|
||||
networks:
|
||||
- metapluginanimedetector
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginanimedetector:
|
||||
name: metapluginanimedetector
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginanimedetector
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `anime-detector` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Flags anime content from release keywords, Japanese text in the title and the audio tracks present.
|
||||
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginanimedetector-plugin:8080`.
|
||||
|
||||
⚠ **No MetaMesh app calls it by URL yet.** MetaSort starts its own plugin
|
||||
containers from `plugins.yml`, and the MetaFeeder SDK's sidecar slots cover
|
||||
only filename-parser, TMDB and OpenSubtitles — not `anime-detector`. Installing this
|
||||
gives you the plugin running and addressable on the `pcs` network for
|
||||
anything that speaks the plugin contract; it does **not** add `anime-detector` to
|
||||
MetaSort.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `anime-detector` de MetaMesh, en app autonome.**
|
||||
|
||||
Détecte les contenus anime à partir des mots-clés de release, du texte japonais dans le titre et des pistes audio présentes.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginanimedetector-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: Detects anime content from keywords, Japanese text and audio
|
||||
fr_fr: Détecte les contenus anime (mots-clés, texte japonais, audio)
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · Anime Detector
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginAnimeDetector/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginAnimeDetector/thumbnail.png
|
||||
|
||||
store_app_id: metapluginanimedetector
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
⚠ **Read this first: nothing calls `anime-detector` by URL yet.** MetaSort spawns
|
||||
its own plugin containers from `plugins.yml` (`image:`) and has no URL
|
||||
slot, and the MetaFeeder SDK's three sidecar slots are filename-parser,
|
||||
TMDB and OpenSubtitles only. Install this if you want `anime-detector` running and
|
||||
addressable at `http://metapluginanimedetector-plugin:8080` for your own caller — not
|
||||
expecting it to light up MetaSort or a MetaFeeder on its own.
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginanimedetector-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,42 @@
|
||||
# MetaPlugin · Anime Detector — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginanimedetector-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are reachable
|
||||
only from the `pcs` Docker network.
|
||||
- The container mounts **no user directories** — not `/DATA/Documents`, `/DATA/Downloads`,
|
||||
`/DATA/Media` or `/DATA/Gallery`. It binds no host path at all. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. It is stateless: there is nothing to back up, and
|
||||
a reinstall loses nothing.
|
||||
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,194 @@
|
||||
name: metapluginffmpeg
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · FFmpeg — the `ffmpeg` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ NOTHING IN THE FAMILY CONSUMES IT BY URL YET — install it knowing that.
|
||||
# MetaSort starts its own plugin containers from `plugins.yml` (`image:`) and
|
||||
# has no URL slot. The feeder SDK has exactly three sidecar slots
|
||||
# (`FILENAME_PARSER_URL`, `TMDB_PLUGIN_URL`, `OPENSUBTITLES_PLUGIN_URL`) and
|
||||
# `ffmpeg` is not one of them. What this app gives you today is the plugin
|
||||
# running and addressable at `http://metapluginffmpeg-plugin:8080` for anything that
|
||||
# speaks the four-verb plugin contract — your own caller, a probe, a future
|
||||
# consumer. It is packaging ahead of the consumer, on purpose.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginffmpeg-*` prefix + the per-stack `metapluginffmpeg`
|
||||
# network + /DATA/AppData/metapluginffmpeg/ data root, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginffmpeg-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: fast.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginffmpeg-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-ffmpeg:1.0.0
|
||||
cpu_shares: 30
|
||||
container_name: metapluginffmpeg-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 512m
|
||||
memswap_limit: 512m
|
||||
hostname: metapluginffmpeg-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Where the plugin writes the artefacts it produces (posters, stills,
|
||||
# extracted subtitles) — the shared MetaCore's WebDAV over `pcs`.
|
||||
# LOAD-BEARING: the published images resolve WebDAV from this var, and
|
||||
# without it they log "WebDAV client unavailable" and silently write
|
||||
# nothing. Every record then fails MetaWatch's poster gate.
|
||||
WEBDAV_URL: "http://metacore-app:9000/webdav"
|
||||
TZ: "$TZ"
|
||||
volumes:
|
||||
# Plugin cache. Persisted so a recreate does not redo work that is
|
||||
# expensive to redo; safe to delete, it rebuilds.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/metapluginffmpeg/cache:/cache:rw
|
||||
networks:
|
||||
- metapluginffmpeg
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginffmpeg:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginffmpeg
|
||||
hostname: metapluginffmpeg
|
||||
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: "metapluginffmpeg-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginffmpeg-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginffmpeg-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginffmpeg-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginffmpeg-plugin
|
||||
networks:
|
||||
- metapluginffmpeg
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginffmpeg:
|
||||
name: metapluginffmpeg
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginffmpeg
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `ffmpeg` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Probes video, audio and subtitle streams with FFprobe and writes the `stream/*` keys — codec, resolution, duration, per-track language.
|
||||
|
||||
Its probe is the *authoritative* source for anything a filename can only
|
||||
guess at — `real-probe > title-guess`.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginffmpeg-plugin:8080`.
|
||||
|
||||
⚠ **No MetaMesh app calls it by URL yet.** MetaSort starts its own plugin
|
||||
containers from `plugins.yml`, and the MetaFeeder SDK's sidecar slots cover
|
||||
only filename-parser, TMDB and OpenSubtitles — not `ffmpeg`. Installing this
|
||||
gives you the plugin running and addressable on the `pcs` network for
|
||||
anything that speaks the plugin contract; it does **not** add `ffmpeg` to
|
||||
MetaSort.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `ffmpeg` de MetaMesh, en app autonome.**
|
||||
|
||||
Sonde les flux vidéo, audio et sous-titres avec FFprobe et écrit les clés `stream/*` — codec, résolution, durée, langue par piste.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginffmpeg-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: FFprobe stream metadata for MetaMesh records
|
||||
fr_fr: Métadonnées de flux FFprobe pour les enregistrements MetaMesh
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · FFmpeg
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginFFmpeg/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginFFmpeg/thumbnail.png
|
||||
|
||||
store_app_id: metapluginffmpeg
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
⚠ **Read this first: nothing calls `ffmpeg` by URL yet.** MetaSort spawns
|
||||
its own plugin containers from `plugins.yml` (`image:`) and has no URL
|
||||
slot, and the MetaFeeder SDK's three sidecar slots are filename-parser,
|
||||
TMDB and OpenSubtitles only. Install this if you want `ffmpeg` running and
|
||||
addressable at `http://metapluginffmpeg-plugin:8080` for your own caller — not
|
||||
expecting it to light up MetaSort or a MetaFeeder on its own.
|
||||
|
||||
**Requires MetaCore** on the same box: this plugin writes what it
|
||||
produces to MetaCore's WebDAV (`http://metacore-app:9000/webdav`).
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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/metapluginffmpeg/cache
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginffmpeg-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 129 KiB |
@@ -0,0 +1,44 @@
|
||||
# MetaPlugin · FFmpeg — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginffmpeg-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are 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 cache under `/DATA/AppData/metapluginffmpeg/`, declared in
|
||||
`x-compose-app.folders` and owned `$PUID:$PGID`. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. Its only state is a rebuildable cache under
|
||||
`/DATA/AppData/metapluginffmpeg/`, which is the unit Maison archives on uninstall.
|
||||
|
After Width: | Height: | Size: 129 KiB |
@@ -0,0 +1,192 @@
|
||||
name: metapluginfileinfo
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · File Info — the `file-info` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ NOTHING IN THE FAMILY CONSUMES IT BY URL YET — install it knowing that.
|
||||
# MetaSort starts its own plugin containers from `plugins.yml` (`image:`) and
|
||||
# has no URL slot. The feeder SDK has exactly three sidecar slots
|
||||
# (`FILENAME_PARSER_URL`, `TMDB_PLUGIN_URL`, `OPENSUBTITLES_PLUGIN_URL`) and
|
||||
# `file-info` is not one of them. What this app gives you today is the plugin
|
||||
# running and addressable at `http://metapluginfileinfo-plugin:8080` for anything that
|
||||
# speaks the four-verb plugin contract — your own caller, a probe, a future
|
||||
# consumer. It is packaging ahead of the consumer, on purpose.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginfileinfo-*` prefix + the per-stack `metapluginfileinfo`
|
||||
# network + /DATA/AppData/metapluginfileinfo/ data root, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginfileinfo-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: fast.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginfileinfo-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-file-info:1.0.0
|
||||
cpu_shares: 30
|
||||
container_name: metapluginfileinfo-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
hostname: metapluginfileinfo-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Where the plugin writes the artefacts it produces (posters, stills,
|
||||
# extracted subtitles) — the shared MetaCore's WebDAV over `pcs`.
|
||||
# LOAD-BEARING: the published images resolve WebDAV from this var, and
|
||||
# without it they log "WebDAV client unavailable" and silently write
|
||||
# nothing. Every record then fails MetaWatch's poster gate.
|
||||
WEBDAV_URL: "http://metacore-app:9000/webdav"
|
||||
TZ: "$TZ"
|
||||
volumes:
|
||||
# Plugin cache. Persisted so a recreate does not redo work that is
|
||||
# expensive to redo; safe to delete, it rebuilds.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/metapluginfileinfo/cache:/cache:rw
|
||||
networks:
|
||||
- metapluginfileinfo
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginfileinfo:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginfileinfo
|
||||
hostname: metapluginfileinfo
|
||||
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: "metapluginfileinfo-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginfileinfo-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginfileinfo-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginfileinfo-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginfileinfo-plugin
|
||||
networks:
|
||||
- metapluginfileinfo
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginfileinfo:
|
||||
name: metapluginfileinfo
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginfileinfo
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `file-info` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Extracts basic file information — type, MIME, size.
|
||||
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginfileinfo-plugin:8080`.
|
||||
|
||||
⚠ **No MetaMesh app calls it by URL yet.** MetaSort starts its own plugin
|
||||
containers from `plugins.yml`, and the MetaFeeder SDK's sidecar slots cover
|
||||
only filename-parser, TMDB and OpenSubtitles — not `file-info`. Installing this
|
||||
gives you the plugin running and addressable on the `pcs` network for
|
||||
anything that speaks the plugin contract; it does **not** add `file-info` to
|
||||
MetaSort.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `file-info` de MetaMesh, en app autonome.**
|
||||
|
||||
Extrait les informations de base d'un fichier — type, MIME, taille.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginfileinfo-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: File type, MIME and size for MetaMesh records
|
||||
fr_fr: Type, MIME et taille de fichier pour les enregistrements MetaMesh
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · File Info
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginFileInfo/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginFileInfo/thumbnail.png
|
||||
|
||||
store_app_id: metapluginfileinfo
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
⚠ **Read this first: nothing calls `file-info` by URL yet.** MetaSort spawns
|
||||
its own plugin containers from `plugins.yml` (`image:`) and has no URL
|
||||
slot, and the MetaFeeder SDK's three sidecar slots are filename-parser,
|
||||
TMDB and OpenSubtitles only. Install this if you want `file-info` running and
|
||||
addressable at `http://metapluginfileinfo-plugin:8080` for your own caller — not
|
||||
expecting it to light up MetaSort or a MetaFeeder on its own.
|
||||
|
||||
**Requires MetaCore** on the same box: this plugin writes what it
|
||||
produces to MetaCore's WebDAV (`http://metacore-app:9000/webdav`).
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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/metapluginfileinfo/cache
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginfileinfo-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,44 @@
|
||||
# MetaPlugin · File Info — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginfileinfo-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are 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 cache under `/DATA/AppData/metapluginfileinfo/`, declared in
|
||||
`x-compose-app.folders` and owned `$PUID:$PGID`. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. Its only state is a rebuildable cache under
|
||||
`/DATA/AppData/metapluginfileinfo/`, which is the unit Maison archives on uninstall.
|
||||
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,186 @@
|
||||
name: metapluginfilenameparser
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · Filename Parser — the `filename-parser` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ WHAT CONSUMES IT. A MetaFeeder's enrichment sidecar slot: set
|
||||
# `FILENAME_PARSER_URL` on the feeder to `http://metapluginfilenameparser-plugin:8080` and it uses this
|
||||
# shared instance instead of its own bundled copy. (The feeder SDK otherwise
|
||||
# DERIVES the sidecar URL from its own hostname, so the explicit variable is
|
||||
# what makes sharing possible at all.)
|
||||
# **NOT MetaSort**: MetaSort starts its own plugin containers from its
|
||||
# `plugins.yml` (`image:`) and has no URL slot, so installing this app does
|
||||
# not add a plugin to MetaSort.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginfilenameparser-*` prefix + the per-stack `metapluginfilenameparser`
|
||||
# network, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginfilenameparser-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: fast.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginfilenameparser-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-filename-parser:1.0.3
|
||||
cpu_shares: 30
|
||||
container_name: metapluginfilenameparser-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
hostname: metapluginfilenameparser-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# ⚠ CONSUMER-SPECIFIC, and the one reason not to share a single instance
|
||||
# between a gateway feeder and MetaSort. These are title *guesses*
|
||||
# (quality / codec / languages). A gateway feeder has no real file and
|
||||
# wants them (`true`); MetaSort probes the real stream and must NOT have
|
||||
# them, or a guessed `languages/<iso>` member pollutes the probed set.
|
||||
# Blank = off = the plugin's own default, which is MetaSort's semantics.
|
||||
EMIT_TITLE_TAGS: "${EMIT_TITLE_TAGS:-}"
|
||||
TZ: "$TZ"
|
||||
networks:
|
||||
- metapluginfilenameparser
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginfilenameparser:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginfilenameparser
|
||||
hostname: metapluginfilenameparser
|
||||
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: "metapluginfilenameparser-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginfilenameparser-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginfilenameparser-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginfilenameparser-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginfilenameparser-plugin
|
||||
networks:
|
||||
- metapluginfilenameparser
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginfilenameparser:
|
||||
name: metapluginfilenameparser
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginfilenameparser
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `filename-parser` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Parses a release filename into title, season, episode, year and quality.
|
||||
|
||||
⚠ **One instance cannot serve both a gateway feeder and MetaSort.**
|
||||
`EMIT_TITLE_TAGS` is a process-level switch, and the two want opposite
|
||||
values — see this app's environment. Install a second copy, or keep the
|
||||
feeder's bundled sidecar, rather than flipping it back and forth.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginfilenameparser-plugin:8080`.
|
||||
|
||||
**Who can use it today:** a **MetaFeeder** app, through its
|
||||
`FILENAME_PARSER_URL` setting — one shared copy instead of one bundled per feeder.
|
||||
|
||||
**Not a MetaSort plugin.** MetaSort starts its own plugin containers from
|
||||
`plugins.yml` and has no slot for a plugin URL.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `filename-parser` de MetaMesh, en app autonome.**
|
||||
|
||||
Analyse un nom de fichier de release en titre, saison, épisode, année et qualité.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginfilenameparser-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: Filename → title, season, episode, year, quality
|
||||
fr_fr: Nom de fichier → titre, saison, épisode, année, qualité
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · Filename Parser
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginFilenameParser/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginFilenameParser/thumbnail.png
|
||||
|
||||
store_app_id: metapluginfilenameparser
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
**What this is for:** running the `filename-parser` plugin **once, shared**, instead of
|
||||
one bundled copy inside every app that needs it.
|
||||
|
||||
**After installing:** set `FILENAME_PARSER_URL` to `http://metapluginfilenameparser-plugin:8080` in the
|
||||
MetaFeeder app that should use it, then restart that feeder. Left unset,
|
||||
the feeder keeps using its own bundled copy.
|
||||
|
||||
**It is not a MetaSort plugin.** MetaSort spawns its own plugin containers
|
||||
from `plugins.yml` (`image:`), with no URL slot — installing this app does
|
||||
not add `filename-parser` to MetaSort.
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginfilenameparser-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,42 @@
|
||||
# MetaPlugin · Filename Parser — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginfilenameparser-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are reachable
|
||||
only from the `pcs` Docker network.
|
||||
- The container mounts **no user directories** — not `/DATA/Documents`, `/DATA/Downloads`,
|
||||
`/DATA/Media` or `/DATA/Gallery`. It binds no host path at all. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. It is stateless: there is nothing to back up, and
|
||||
a reinstall loses nothing.
|
||||
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,189 @@
|
||||
name: metapluginfullhash
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · Full Hash — the `full-hash` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ NOTHING IN THE FAMILY CONSUMES IT BY URL YET — install it knowing that.
|
||||
# MetaSort starts its own plugin containers from `plugins.yml` (`image:`) and
|
||||
# has no URL slot. The feeder SDK has exactly three sidecar slots
|
||||
# (`FILENAME_PARSER_URL`, `TMDB_PLUGIN_URL`, `OPENSUBTITLES_PLUGIN_URL`) and
|
||||
# `full-hash` is not one of them. What this app gives you today is the plugin
|
||||
# running and addressable at `http://metapluginfullhash-plugin:8080` for anything that
|
||||
# speaks the four-verb plugin contract — your own caller, a probe, a future
|
||||
# consumer. It is packaging ahead of the consumer, on purpose.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginfullhash-*` prefix + the per-stack `metapluginfullhash`
|
||||
# network + /DATA/AppData/metapluginfullhash/ data root, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginfullhash-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: background.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginfullhash-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-fullhash:1.0.0
|
||||
cpu_shares: 30
|
||||
container_name: metapluginfullhash-plugin
|
||||
# 2 GB, swap disabled (memswap_limit == mem_limit) → OOM instead of
|
||||
# swap. Sized for whole-file hashing of large media over WebDAV; this
|
||||
# is the one plugin that genuinely needs it.
|
||||
mem_limit: 2g
|
||||
memswap_limit: 2g
|
||||
hostname: metapluginfullhash-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Nothing to configure: this plugin takes its work from the /process
|
||||
# payload and writes back through the caller. No key, no WebDAV.
|
||||
TZ: "$TZ"
|
||||
volumes:
|
||||
# Plugin cache. Persisted so a recreate does not redo work that is
|
||||
# expensive to redo; safe to delete, it rebuilds.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/metapluginfullhash/cache:/cache:rw
|
||||
networks:
|
||||
- metapluginfullhash
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginfullhash:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginfullhash
|
||||
hostname: metapluginfullhash
|
||||
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: "metapluginfullhash-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginfullhash-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginfullhash-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginfullhash-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginfullhash-plugin
|
||||
networks:
|
||||
- metapluginfullhash
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginfullhash:
|
||||
name: metapluginfullhash
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginfullhash
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `full-hash` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Hashes a whole file — high-performance parallel hashing with optimised I/O — and writes the SHA-256 CID that duplicate detection keys on.
|
||||
|
||||
⚠ **The expensive one.** It reads every byte of every file it is given,
|
||||
over WebDAV, on the background queue. The 2 GB limit and the 5-minute
|
||||
timeout are sized for large media files, not padding.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginfullhash-plugin:8080`.
|
||||
|
||||
⚠ **No MetaMesh app calls it by URL yet.** MetaSort starts its own plugin
|
||||
containers from `plugins.yml`, and the MetaFeeder SDK's sidecar slots cover
|
||||
only filename-parser, TMDB and OpenSubtitles — not `full-hash`. Installing this
|
||||
gives you the plugin running and addressable on the `pcs` network for
|
||||
anything that speaks the plugin contract; it does **not** add `full-hash` to
|
||||
MetaSort.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `full-hash` de MetaMesh, en app autonome.**
|
||||
|
||||
Hache un fichier entier — hachage parallèle haute performance — et écrit le CID SHA-256 sur lequel la détection de doublons s'appuie.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginfullhash-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: Whole-file SHA-256 hashing for MetaMesh (duplicate detection)
|
||||
fr_fr: Hachage SHA-256 de fichiers entiers pour MetaMesh (détection de doublons)
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · Full Hash
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginFullHash/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginFullHash/thumbnail.png
|
||||
|
||||
store_app_id: metapluginfullhash
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
⚠ **Read this first: nothing calls `full-hash` by URL yet.** MetaSort spawns
|
||||
its own plugin containers from `plugins.yml` (`image:`) and has no URL
|
||||
slot, and the MetaFeeder SDK's three sidecar slots are filename-parser,
|
||||
TMDB and OpenSubtitles only. Install this if you want `full-hash` running and
|
||||
addressable at `http://metapluginfullhash-plugin:8080` for your own caller — not
|
||||
expecting it to light up MetaSort or a MetaFeeder on its own.
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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/metapluginfullhash/cache
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginfullhash-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 129 KiB |
@@ -0,0 +1,44 @@
|
||||
# MetaPlugin · Full Hash — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginfullhash-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are 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 cache under `/DATA/AppData/metapluginfullhash/`, declared in
|
||||
`x-compose-app.folders` and owned `$PUID:$PGID`. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. Its only state is a rebuildable cache under
|
||||
`/DATA/AppData/metapluginfullhash/`, which is the unit Maison archives on uninstall.
|
||||
|
After Width: | Height: | Size: 129 KiB |
@@ -0,0 +1,183 @@
|
||||
name: metapluginjellyfinnfo
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · Jellyfin NFO — the `jellyfin-nfo` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ NOTHING IN THE FAMILY CONSUMES IT BY URL YET — install it knowing that.
|
||||
# MetaSort starts its own plugin containers from `plugins.yml` (`image:`) and
|
||||
# has no URL slot. The feeder SDK has exactly three sidecar slots
|
||||
# (`FILENAME_PARSER_URL`, `TMDB_PLUGIN_URL`, `OPENSUBTITLES_PLUGIN_URL`) and
|
||||
# `jellyfin-nfo` is not one of them. What this app gives you today is the plugin
|
||||
# running and addressable at `http://metapluginjellyfinnfo-plugin:8080` for anything that
|
||||
# speaks the four-verb plugin contract — your own caller, a probe, a future
|
||||
# consumer. It is packaging ahead of the consumer, on purpose.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginjellyfinnfo-*` prefix + the per-stack `metapluginjellyfinnfo`
|
||||
# network, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginjellyfinnfo-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: fast.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginjellyfinnfo-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-jellyfin-nfo:1.0.0
|
||||
cpu_shares: 30
|
||||
container_name: metapluginjellyfinnfo-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
hostname: metapluginjellyfinnfo-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Where the plugin writes the artefacts it produces (posters, stills,
|
||||
# extracted subtitles) — the shared MetaCore's WebDAV over `pcs`.
|
||||
# LOAD-BEARING: the published images resolve WebDAV from this var, and
|
||||
# without it they log "WebDAV client unavailable" and silently write
|
||||
# nothing. Every record then fails MetaWatch's poster gate.
|
||||
WEBDAV_URL: "http://metacore-app:9000/webdav"
|
||||
TZ: "$TZ"
|
||||
networks:
|
||||
- metapluginjellyfinnfo
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginjellyfinnfo:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginjellyfinnfo
|
||||
hostname: metapluginjellyfinnfo
|
||||
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: "metapluginjellyfinnfo-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginjellyfinnfo-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginjellyfinnfo-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginjellyfinnfo-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginjellyfinnfo-plugin
|
||||
networks:
|
||||
- metapluginjellyfinnfo
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginjellyfinnfo:
|
||||
name: metapluginjellyfinnfo
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginjellyfinnfo
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `jellyfin-nfo` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Reads the `.nfo` files a Jellyfin or Kodi library already carries and lifts their ids — `imdbid`, `tmdbid`, `anidbid` — onto the MetaMesh record.
|
||||
|
||||
The cheapest identity source there is: the work was already done by
|
||||
whatever wrote the NFO, so it costs no API call and no key.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginjellyfinnfo-plugin:8080`.
|
||||
|
||||
⚠ **No MetaMesh app calls it by URL yet.** MetaSort starts its own plugin
|
||||
containers from `plugins.yml`, and the MetaFeeder SDK's sidecar slots cover
|
||||
only filename-parser, TMDB and OpenSubtitles — not `jellyfin-nfo`. Installing this
|
||||
gives you the plugin running and addressable on the `pcs` network for
|
||||
anything that speaks the plugin contract; it does **not** add `jellyfin-nfo` to
|
||||
MetaSort.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `jellyfin-nfo` de MetaMesh, en app autonome.**
|
||||
|
||||
Lit les fichiers `.nfo` d'une bibliothèque Jellyfin ou Kodi et reporte leurs identifiants — `imdbid`, `tmdbid`, `anidbid` — sur l'enregistrement MetaMesh.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginjellyfinnfo-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: Reads Jellyfin/Kodi .nfo ids into MetaMesh records
|
||||
fr_fr: Importe les identifiants .nfo Jellyfin/Kodi dans MetaMesh
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · Jellyfin NFO
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginJellyfinNFO/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginJellyfinNFO/thumbnail.png
|
||||
|
||||
store_app_id: metapluginjellyfinnfo
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
⚠ **Read this first: nothing calls `jellyfin-nfo` by URL yet.** MetaSort spawns
|
||||
its own plugin containers from `plugins.yml` (`image:`) and has no URL
|
||||
slot, and the MetaFeeder SDK's three sidecar slots are filename-parser,
|
||||
TMDB and OpenSubtitles only. Install this if you want `jellyfin-nfo` running and
|
||||
addressable at `http://metapluginjellyfinnfo-plugin:8080` for your own caller — not
|
||||
expecting it to light up MetaSort or a MetaFeeder on its own.
|
||||
|
||||
**Requires MetaCore** on the same box: this plugin writes what it
|
||||
produces to MetaCore's WebDAV (`http://metacore-app:9000/webdav`).
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginjellyfinnfo-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,42 @@
|
||||
# MetaPlugin · Jellyfin NFO — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginjellyfinnfo-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are reachable
|
||||
only from the `pcs` Docker network.
|
||||
- The container mounts **no user directories** — not `/DATA/Documents`, `/DATA/Downloads`,
|
||||
`/DATA/Media` or `/DATA/Gallery`. It binds no host path at all. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. It is stateless: there is nothing to back up, and
|
||||
a reinstall loses nothing.
|
||||
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,176 @@
|
||||
name: metapluginlanguage
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · Language — the `language` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ NOTHING IN THE FAMILY CONSUMES IT BY URL YET — install it knowing that.
|
||||
# MetaSort starts its own plugin containers from `plugins.yml` (`image:`) and
|
||||
# has no URL slot. The feeder SDK has exactly three sidecar slots
|
||||
# (`FILENAME_PARSER_URL`, `TMDB_PLUGIN_URL`, `OPENSUBTITLES_PLUGIN_URL`) and
|
||||
# `language` is not one of them. What this app gives you today is the plugin
|
||||
# running and addressable at `http://metapluginlanguage-plugin:8080` for anything that
|
||||
# speaks the four-verb plugin contract — your own caller, a probe, a future
|
||||
# consumer. It is packaging ahead of the consumer, on purpose.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginlanguage-*` prefix + the per-stack `metapluginlanguage`
|
||||
# network, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginlanguage-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: fast.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginlanguage-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-language:1.0.1
|
||||
cpu_shares: 30
|
||||
container_name: metapluginlanguage-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
hostname: metapluginlanguage-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Nothing to configure: this plugin takes its work from the /process
|
||||
# payload and writes back through the caller. No key, no WebDAV.
|
||||
TZ: "$TZ"
|
||||
networks:
|
||||
- metapluginlanguage
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginlanguage:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginlanguage
|
||||
hostname: metapluginlanguage
|
||||
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: "metapluginlanguage-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginlanguage-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginlanguage-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginlanguage-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginlanguage-plugin
|
||||
networks:
|
||||
- metapluginlanguage
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginlanguage:
|
||||
name: metapluginlanguage
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginlanguage
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `language` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Aggregates the audio-track languages a probe found into the record's `languages/<lang3>` key-set.
|
||||
|
||||
Runs **after** a probe: it aggregates what **MetaPlugin · FFmpeg** found,
|
||||
so install that one too or there is nothing to aggregate.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginlanguage-plugin:8080`.
|
||||
|
||||
⚠ **No MetaMesh app calls it by URL yet.** MetaSort starts its own plugin
|
||||
containers from `plugins.yml`, and the MetaFeeder SDK's sidecar slots cover
|
||||
only filename-parser, TMDB and OpenSubtitles — not `language`. Installing this
|
||||
gives you the plugin running and addressable on the `pcs` network for
|
||||
anything that speaks the plugin contract; it does **not** add `language` to
|
||||
MetaSort.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `language` de MetaMesh, en app autonome.**
|
||||
|
||||
Agrège les langues des pistes audio détectées dans l'ensemble de clés `languages/<lang3>` de l'enregistrement.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginlanguage-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: Audio-track languages as a MetaMesh key-set
|
||||
fr_fr: Langues des pistes audio en ensemble de clés MetaMesh
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · Language
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginLanguage/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginLanguage/thumbnail.png
|
||||
|
||||
store_app_id: metapluginlanguage
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
⚠ **Read this first: nothing calls `language` by URL yet.** MetaSort spawns
|
||||
its own plugin containers from `plugins.yml` (`image:`) and has no URL
|
||||
slot, and the MetaFeeder SDK's three sidecar slots are filename-parser,
|
||||
TMDB and OpenSubtitles only. Install this if you want `language` running and
|
||||
addressable at `http://metapluginlanguage-plugin:8080` for your own caller — not
|
||||
expecting it to light up MetaSort or a MetaFeeder on its own.
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginlanguage-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 132 KiB |
@@ -0,0 +1,42 @@
|
||||
# MetaPlugin · Language — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginlanguage-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are reachable
|
||||
only from the `pcs` Docker network.
|
||||
- The container mounts **no user directories** — not `/DATA/Documents`, `/DATA/Downloads`,
|
||||
`/DATA/Media` or `/DATA/Gallery`. It binds no host path at all. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. It is stateless: there is nothing to back up, and
|
||||
a reinstall loses nothing.
|
||||
|
After Width: | Height: | Size: 132 KiB |
@@ -0,0 +1,187 @@
|
||||
name: metapluginopensubtitles
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · OpenSubtitles — the `opensubtitles` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ WHAT CONSUMES IT. A MetaFeeder's enrichment sidecar slot: set
|
||||
# `OPENSUBTITLES_PLUGIN_URL` on the feeder to `http://metapluginopensubtitles-plugin:8080` and it uses this
|
||||
# shared instance instead of its own bundled copy. (The feeder SDK otherwise
|
||||
# DERIVES the sidecar URL from its own hostname, so the explicit variable is
|
||||
# what makes sharing possible at all.)
|
||||
# **NOT MetaSort**: MetaSort starts its own plugin containers from its
|
||||
# `plugins.yml` (`image:`) and has no URL slot, so installing this app does
|
||||
# not add a plugin to MetaSort.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginopensubtitles-*` prefix + the per-stack `metapluginopensubtitles`
|
||||
# network, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginopensubtitles-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: background.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginopensubtitles-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-opensubtitle:1.0.0
|
||||
cpu_shares: 30
|
||||
container_name: metapluginopensubtitles-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
hostname: metapluginopensubtitles-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Where the plugin writes the artefacts it produces (posters, stills,
|
||||
# extracted subtitles) — the shared MetaCore's WebDAV over `pcs`.
|
||||
# LOAD-BEARING: the published images resolve WebDAV from this var, and
|
||||
# without it they log "WebDAV client unavailable" and silently write
|
||||
# nothing. Every record then fails MetaWatch's poster gate.
|
||||
WEBDAV_URL: "http://metacore-app:9000/webdav"
|
||||
TZ: "$TZ"
|
||||
networks:
|
||||
- metapluginopensubtitles
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginopensubtitles:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginopensubtitles
|
||||
hostname: metapluginopensubtitles
|
||||
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: "metapluginopensubtitles-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginopensubtitles-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginopensubtitles-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginopensubtitles-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginopensubtitles-plugin
|
||||
networks:
|
||||
- metapluginopensubtitles
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginopensubtitles:
|
||||
name: metapluginopensubtitles
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginopensubtitles
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `opensubtitles` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Fetches subtitle sidecars from **OpenSubtitles** for videos that have none, and links them to the video.
|
||||
|
||||
**Bring your own account.** The plugin stores no credentials: the caller
|
||||
POSTs its own API key, username and password to `/configure` before each
|
||||
use. Without them it soft-skips.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginopensubtitles-plugin:8080`.
|
||||
|
||||
**Who can use it today:** a **MetaFeeder** app, through its
|
||||
`OPENSUBTITLES_PLUGIN_URL` setting — one shared copy instead of one bundled per feeder.
|
||||
|
||||
**Not a MetaSort plugin.** MetaSort starts its own plugin containers from
|
||||
`plugins.yml` and has no slot for a plugin URL.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `opensubtitles` de MetaMesh, en app autonome.**
|
||||
|
||||
Récupère des sous-titres depuis **OpenSubtitles** pour les vidéos qui n'en ont pas et les relie à la vidéo.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginopensubtitles-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: Fetches missing subtitles from OpenSubtitles
|
||||
fr_fr: Récupère les sous-titres manquants depuis OpenSubtitles
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · OpenSubtitles
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginOpenSubtitles/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginOpenSubtitles/thumbnail.png
|
||||
|
||||
store_app_id: metapluginopensubtitles
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
**What this is for:** running the `opensubtitles` plugin **once, shared**, instead of
|
||||
one bundled copy inside every app that needs it.
|
||||
|
||||
**After installing:** set `OPENSUBTITLES_PLUGIN_URL` to `http://metapluginopensubtitles-plugin:8080` in the
|
||||
MetaFeeder app that should use it, then restart that feeder. Left unset,
|
||||
the feeder keeps using its own bundled copy.
|
||||
|
||||
**It is not a MetaSort plugin.** MetaSort spawns its own plugin containers
|
||||
from `plugins.yml` (`image:`), with no URL slot — installing this app does
|
||||
not add `opensubtitles` to MetaSort.
|
||||
|
||||
**Requires MetaCore** on the same box: this plugin writes what it
|
||||
produces to MetaCore's WebDAV (`http://metacore-app:9000/webdav`).
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginopensubtitles-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 137 KiB |
@@ -0,0 +1,42 @@
|
||||
# MetaPlugin · OpenSubtitles — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginopensubtitles-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are reachable
|
||||
only from the `pcs` Docker network.
|
||||
- The container mounts **no user directories** — not `/DATA/Documents`, `/DATA/Downloads`,
|
||||
`/DATA/Media` or `/DATA/Gallery`. It binds no host path at all. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. It is stateless: there is nothing to back up, and
|
||||
a reinstall loses nothing.
|
||||
|
After Width: | Height: | Size: 137 KiB |
@@ -0,0 +1,183 @@
|
||||
name: metapluginstillextractor
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · Still Extractor — the `still-extractor` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ NOTHING IN THE FAMILY CONSUMES IT BY URL YET — install it knowing that.
|
||||
# MetaSort starts its own plugin containers from `plugins.yml` (`image:`) and
|
||||
# has no URL slot. The feeder SDK has exactly three sidecar slots
|
||||
# (`FILENAME_PARSER_URL`, `TMDB_PLUGIN_URL`, `OPENSUBTITLES_PLUGIN_URL`) and
|
||||
# `still-extractor` is not one of them. What this app gives you today is the plugin
|
||||
# running and addressable at `http://metapluginstillextractor-plugin:8080` for anything that
|
||||
# speaks the four-verb plugin contract — your own caller, a probe, a future
|
||||
# consumer. It is packaging ahead of the consumer, on purpose.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginstillextractor-*` prefix + the per-stack `metapluginstillextractor`
|
||||
# network, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginstillextractor-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: background.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginstillextractor-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-still-extractor:1.0.1
|
||||
cpu_shares: 30
|
||||
container_name: metapluginstillextractor-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 512m
|
||||
memswap_limit: 512m
|
||||
hostname: metapluginstillextractor-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Where the plugin writes the artefacts it produces (posters, stills,
|
||||
# extracted subtitles) — the shared MetaCore's WebDAV over `pcs`.
|
||||
# LOAD-BEARING: the published images resolve WebDAV from this var, and
|
||||
# without it they log "WebDAV client unavailable" and silently write
|
||||
# nothing. Every record then fails MetaWatch's poster gate.
|
||||
WEBDAV_URL: "http://metacore-app:9000/webdav"
|
||||
TZ: "$TZ"
|
||||
networks:
|
||||
- metapluginstillextractor
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginstillextractor:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginstillextractor
|
||||
hostname: metapluginstillextractor
|
||||
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: "metapluginstillextractor-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginstillextractor-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginstillextractor-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginstillextractor-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginstillextractor-plugin
|
||||
networks:
|
||||
- metapluginstillextractor
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginstillextractor:
|
||||
name: metapluginstillextractor
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginstillextractor
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `still-extractor` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Grabs a representative frame out of a video and stores it as the record's `still` — a best-effort episode thumbnail.
|
||||
|
||||
It **never** writes `poster`: a still is a frame, not artwork, and the two
|
||||
are different keys on purpose. One HTTP seek + decode per candidate offset.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginstillextractor-plugin:8080`.
|
||||
|
||||
⚠ **No MetaMesh app calls it by URL yet.** MetaSort starts its own plugin
|
||||
containers from `plugins.yml`, and the MetaFeeder SDK's sidecar slots cover
|
||||
only filename-parser, TMDB and OpenSubtitles — not `still-extractor`. Installing this
|
||||
gives you the plugin running and addressable on the `pcs` network for
|
||||
anything that speaks the plugin contract; it does **not** add `still-extractor` to
|
||||
MetaSort.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `still-extractor` de MetaMesh, en app autonome.**
|
||||
|
||||
Extrait une image représentative d'une vidéo et l'enregistre comme `still` — une vignette d'épisode au mieux.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginstillextractor-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: A representative frame as each video's still thumbnail
|
||||
fr_fr: Une image représentative comme vignette de chaque vidéo
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · Still Extractor
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginStillExtractor/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginStillExtractor/thumbnail.png
|
||||
|
||||
store_app_id: metapluginstillextractor
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
⚠ **Read this first: nothing calls `still-extractor` by URL yet.** MetaSort spawns
|
||||
its own plugin containers from `plugins.yml` (`image:`) and has no URL
|
||||
slot, and the MetaFeeder SDK's three sidecar slots are filename-parser,
|
||||
TMDB and OpenSubtitles only. Install this if you want `still-extractor` running and
|
||||
addressable at `http://metapluginstillextractor-plugin:8080` for your own caller — not
|
||||
expecting it to light up MetaSort or a MetaFeeder on its own.
|
||||
|
||||
**Requires MetaCore** on the same box: this plugin writes what it
|
||||
produces to MetaCore's WebDAV (`http://metacore-app:9000/webdav`).
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginstillextractor-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,42 @@
|
||||
# MetaPlugin · Still Extractor — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginstillextractor-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are reachable
|
||||
only from the `pcs` Docker network.
|
||||
- The container mounts **no user directories** — not `/DATA/Documents`, `/DATA/Downloads`,
|
||||
`/DATA/Media` or `/DATA/Gallery`. It binds no host path at all. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. It is stateless: there is nothing to back up, and
|
||||
a reinstall loses nothing.
|
||||
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,184 @@
|
||||
name: metapluginsubtitle
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · Subtitle — the `subtitle` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ NOTHING IN THE FAMILY CONSUMES IT BY URL YET — install it knowing that.
|
||||
# MetaSort starts its own plugin containers from `plugins.yml` (`image:`) and
|
||||
# has no URL slot. The feeder SDK has exactly three sidecar slots
|
||||
# (`FILENAME_PARSER_URL`, `TMDB_PLUGIN_URL`, `OPENSUBTITLES_PLUGIN_URL`) and
|
||||
# `subtitle` is not one of them. What this app gives you today is the plugin
|
||||
# running and addressable at `http://metapluginsubtitle-plugin:8080` for anything that
|
||||
# speaks the four-verb plugin contract — your own caller, a probe, a future
|
||||
# consumer. It is packaging ahead of the consumer, on purpose.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginsubtitle-*` prefix + the per-stack `metapluginsubtitle`
|
||||
# network, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginsubtitle-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: fast.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginsubtitle-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-subtitle:1.0.0
|
||||
cpu_shares: 30
|
||||
container_name: metapluginsubtitle-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 256m
|
||||
memswap_limit: 256m
|
||||
hostname: metapluginsubtitle-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Where the plugin writes the artefacts it produces (posters, stills,
|
||||
# extracted subtitles) — the shared MetaCore's WebDAV over `pcs`.
|
||||
# LOAD-BEARING: the published images resolve WebDAV from this var, and
|
||||
# without it they log "WebDAV client unavailable" and silently write
|
||||
# nothing. Every record then fails MetaWatch's poster gate.
|
||||
WEBDAV_URL: "http://metacore-app:9000/webdav"
|
||||
TZ: "$TZ"
|
||||
networks:
|
||||
- metapluginsubtitle
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginsubtitle:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginsubtitle
|
||||
hostname: metapluginsubtitle
|
||||
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: "metapluginsubtitle-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginsubtitle-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginsubtitle-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginsubtitle-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginsubtitle-plugin
|
||||
networks:
|
||||
- metapluginsubtitle
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginsubtitle:
|
||||
name: metapluginsubtitle
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginsubtitle
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `subtitle` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Links subtitle sidecar files sitting next to a video to that video, and detects each one's language.
|
||||
|
||||
It only *links* what is already on disk. For subtitles embedded in the
|
||||
container see **MetaPlugin · Subtitle Extractor**; to fetch missing ones
|
||||
see **MetaPlugin · OpenSubtitles**.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginsubtitle-plugin:8080`.
|
||||
|
||||
⚠ **No MetaMesh app calls it by URL yet.** MetaSort starts its own plugin
|
||||
containers from `plugins.yml`, and the MetaFeeder SDK's sidecar slots cover
|
||||
only filename-parser, TMDB and OpenSubtitles — not `subtitle`. Installing this
|
||||
gives you the plugin running and addressable on the `pcs` network for
|
||||
anything that speaks the plugin contract; it does **not** add `subtitle` to
|
||||
MetaSort.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `subtitle` de MetaMesh, en app autonome.**
|
||||
|
||||
Associe les fichiers de sous-titres posés à côté d'une vidéo à cette vidéo et détecte la langue de chacun.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginsubtitle-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: Links subtitle sidecars to their video and detects language
|
||||
fr_fr: Associe les sous-titres à leur vidéo et détecte la langue
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · Subtitle
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginSubtitle/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginSubtitle/thumbnail.png
|
||||
|
||||
store_app_id: metapluginsubtitle
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
⚠ **Read this first: nothing calls `subtitle` by URL yet.** MetaSort spawns
|
||||
its own plugin containers from `plugins.yml` (`image:`) and has no URL
|
||||
slot, and the MetaFeeder SDK's three sidecar slots are filename-parser,
|
||||
TMDB and OpenSubtitles only. Install this if you want `subtitle` running and
|
||||
addressable at `http://metapluginsubtitle-plugin:8080` for your own caller — not
|
||||
expecting it to light up MetaSort or a MetaFeeder on its own.
|
||||
|
||||
**Requires MetaCore** on the same box: this plugin writes what it
|
||||
produces to MetaCore's WebDAV (`http://metacore-app:9000/webdav`).
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginsubtitle-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,42 @@
|
||||
# MetaPlugin · Subtitle — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginsubtitle-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are reachable
|
||||
only from the `pcs` Docker network.
|
||||
- The container mounts **no user directories** — not `/DATA/Documents`, `/DATA/Downloads`,
|
||||
`/DATA/Media` or `/DATA/Gallery`. It binds no host path at all. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. It is stateless: there is nothing to back up, and
|
||||
a reinstall loses nothing.
|
||||
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,195 @@
|
||||
name: metapluginsubtitleextractor
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · Subtitle Extractor — the `subtitle-extractor` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ NOTHING IN THE FAMILY CONSUMES IT BY URL YET — install it knowing that.
|
||||
# MetaSort starts its own plugin containers from `plugins.yml` (`image:`) and
|
||||
# has no URL slot. The feeder SDK has exactly three sidecar slots
|
||||
# (`FILENAME_PARSER_URL`, `TMDB_PLUGIN_URL`, `OPENSUBTITLES_PLUGIN_URL`) and
|
||||
# `subtitle-extractor` is not one of them. What this app gives you today is the plugin
|
||||
# running and addressable at `http://metapluginsubtitleextractor-plugin:8080` for anything that
|
||||
# speaks the four-verb plugin contract — your own caller, a probe, a future
|
||||
# consumer. It is packaging ahead of the consumer, on purpose.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metapluginsubtitleextractor-*` prefix + the per-stack `metapluginsubtitleextractor`
|
||||
# network + /DATA/AppData/metapluginsubtitleextractor/ data root, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metapluginsubtitleextractor-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: background.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginsubtitleextractor-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-subtitle-extractor:1.0.0
|
||||
cpu_shares: 30
|
||||
container_name: metapluginsubtitleextractor-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 512m
|
||||
memswap_limit: 512m
|
||||
hostname: metapluginsubtitleextractor-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Where the plugin writes the artefacts it produces (posters, stills,
|
||||
# extracted subtitles) — the shared MetaCore's WebDAV over `pcs`.
|
||||
# LOAD-BEARING: the published images resolve WebDAV from this var, and
|
||||
# without it they log "WebDAV client unavailable" and silently write
|
||||
# nothing. Every record then fails MetaWatch's poster gate.
|
||||
WEBDAV_URL: "http://metacore-app:9000/webdav"
|
||||
TZ: "$TZ"
|
||||
volumes:
|
||||
# Plugin cache. Persisted so a recreate does not redo work that is
|
||||
# expensive to redo; safe to delete, it rebuilds.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/metapluginsubtitleextractor/cache:/cache:rw
|
||||
networks:
|
||||
- metapluginsubtitleextractor
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metapluginsubtitleextractor:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metapluginsubtitleextractor
|
||||
hostname: metapluginsubtitleextractor
|
||||
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: "metapluginsubtitleextractor-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metapluginsubtitleextractor-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metapluginsubtitleextractor-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metapluginsubtitleextractor-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metapluginsubtitleextractor-plugin
|
||||
networks:
|
||||
- metapluginsubtitleextractor
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metapluginsubtitleextractor:
|
||||
name: metapluginsubtitleextractor
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metapluginsubtitleextractor
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `subtitle-extractor` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Extracts text subtitle tracks embedded inside a video container, writes them out as sidecars and links them back to the video.
|
||||
|
||||
⚠ **Known cost:** one full read of the video per run. It is on the
|
||||
background queue for that reason, and on a peer that also serves playback
|
||||
that read competes with streaming.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metapluginsubtitleextractor-plugin:8080`.
|
||||
|
||||
⚠ **No MetaMesh app calls it by URL yet.** MetaSort starts its own plugin
|
||||
containers from `plugins.yml`, and the MetaFeeder SDK's sidecar slots cover
|
||||
only filename-parser, TMDB and OpenSubtitles — not `subtitle-extractor`. Installing this
|
||||
gives you the plugin running and addressable on the `pcs` network for
|
||||
anything that speaks the plugin contract; it does **not** add `subtitle-extractor` to
|
||||
MetaSort.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `subtitle-extractor` de MetaMesh, en app autonome.**
|
||||
|
||||
Extrait les pistes de sous-titres texte intégrées à un conteneur vidéo, les écrit en fichiers séparés et les relie à la vidéo.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metapluginsubtitleextractor-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: Extracts embedded text subtitles out of video containers
|
||||
fr_fr: Extrait les sous-titres texte intégrés aux conteneurs vidéo
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · Subtitle Extractor
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginSubtitleExtractor/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginSubtitleExtractor/thumbnail.png
|
||||
|
||||
store_app_id: metapluginsubtitleextractor
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
⚠ **Read this first: nothing calls `subtitle-extractor` by URL yet.** MetaSort spawns
|
||||
its own plugin containers from `plugins.yml` (`image:`) and has no URL
|
||||
slot, and the MetaFeeder SDK's three sidecar slots are filename-parser,
|
||||
TMDB and OpenSubtitles only. Install this if you want `subtitle-extractor` running and
|
||||
addressable at `http://metapluginsubtitleextractor-plugin:8080` for your own caller — not
|
||||
expecting it to light up MetaSort or a MetaFeeder on its own.
|
||||
|
||||
**Requires MetaCore** on the same box: this plugin writes what it
|
||||
produces to MetaCore's WebDAV (`http://metacore-app:9000/webdav`).
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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/metapluginsubtitleextractor/cache
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metapluginsubtitleextractor-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,44 @@
|
||||
# MetaPlugin · Subtitle Extractor — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metapluginsubtitleextractor-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are 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 cache under `/DATA/AppData/metapluginsubtitleextractor/`, declared in
|
||||
`x-compose-app.folders` and owned `$PUID:$PGID`. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. Its only state is a rebuildable cache under
|
||||
`/DATA/AppData/metapluginsubtitleextractor/`, which is the unit Maison archives on uninstall.
|
||||
|
After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,199 @@
|
||||
name: metaplugintmdb
|
||||
|
||||
# =============================================================================
|
||||
# MetaPlugin · TMDB — the `tmdb` enrichment plugin, as a standalone app.
|
||||
#
|
||||
# A MetaMesh enrichment plugin is a HEADLESS worker: it speaks the four-verb
|
||||
# plugin HTTP contract (`/manifest`, `/health`, `/configure`, `/process`) and
|
||||
# nothing else. There is no dashboard — the web UI on this tile is the plugin's
|
||||
# own `/manifest`, which is how you confirm it is alive and what version it is.
|
||||
#
|
||||
# ⚠ WHAT CONSUMES IT. A MetaFeeder's enrichment sidecar slot: set
|
||||
# `TMDB_PLUGIN_URL` on the feeder to `http://metaplugintmdb-plugin:8080` and it uses this
|
||||
# shared instance instead of its own bundled copy. (The feeder SDK otherwise
|
||||
# DERIVES the sidecar URL from its own hostname, so the explicit variable is
|
||||
# what makes sharing possible at all.)
|
||||
# **NOT MetaSort**: MetaSort starts its own plugin containers from its
|
||||
# `plugins.yml` (`image:`) and has no URL slot, so installing this app does
|
||||
# not add a plugin to MetaSort.
|
||||
#
|
||||
# ⚠ IT HOLDS NO CREDENTIALS. The caller POSTs `/configure` with its own key
|
||||
# (TMDB token, OpenSubtitles account) before use. Two consumers sharing one
|
||||
# instance are last-writer-wins on that config — same key, no problem; different
|
||||
# keys, install a second copy.
|
||||
#
|
||||
# Everything is scoped under the `metaplugintmdb-*` prefix + the per-stack `metaplugintmdb`
|
||||
# network + /DATA/AppData/metaplugintmdb/ data root, so it installs beside the other Meta* apps with zero
|
||||
# collisions.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ---------------------------------------------------------------------------
|
||||
# metaplugintmdb-plugin — the plugin. Listens on 8080; joined to `pcs` so any
|
||||
# consumer on the box can reach it by name. Default queue: background.
|
||||
# ---------------------------------------------------------------------------
|
||||
metaplugintmdb-plugin:
|
||||
image: ghcr.io/worph/metamesh-plugin-tmdb:1.0.3
|
||||
cpu_shares: 30
|
||||
container_name: metaplugintmdb-plugin
|
||||
# Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM
|
||||
# instead of swap. Mirrors the limits the bundled sidecars run with.
|
||||
mem_limit: 384m
|
||||
memswap_limit: 384m
|
||||
hostname: metaplugintmdb-plugin
|
||||
restart: unless-stopped
|
||||
user: "0:0"
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
# Where the plugin writes the artefacts it produces (posters, stills,
|
||||
# extracted subtitles) — the shared MetaCore's WebDAV over `pcs`.
|
||||
# LOAD-BEARING: the published images resolve WebDAV from this var, and
|
||||
# without it they log "WebDAV client unavailable" and silently write
|
||||
# nothing. Every record then fails MetaWatch's poster gate.
|
||||
WEBDAV_URL: "http://metacore-app:9000/webdav"
|
||||
TZ: "$TZ"
|
||||
volumes:
|
||||
# Plugin cache. Persisted so a recreate does not redo work that is
|
||||
# expensive to redo; safe to delete, it rebuilds.
|
||||
- ${DATA_ROOT:-/DATA}/AppData/metaplugintmdb/cache:/cache:rw
|
||||
networks:
|
||||
- metaplugintmdb
|
||||
- pcs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AppShield gate — the plugin's `/manifest` behind Yundera OIDC SSO.
|
||||
# container_name == subdomain prefix (the auth-registrar derives the client_id
|
||||
# from it). Consumers still reach the plugin directly on `pcs`; this gate
|
||||
# exists so `/process` and `/configure` are never reachable from the internet
|
||||
# without a session.
|
||||
# ---------------------------------------------------------------------------
|
||||
metaplugintmdb:
|
||||
image: ghcr.io/yundera/appshield:2.0.9
|
||||
cpu_shares: 80
|
||||
container_name: metaplugintmdb
|
||||
hostname: metaplugintmdb
|
||||
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: "metaplugintmdb-plugin"
|
||||
BACKEND_PORT: "8080"
|
||||
LISTEN_PORT: "80"
|
||||
labels:
|
||||
caddy_0: metaplugintmdb-${APP_DOMAIN}
|
||||
caddy_0.import: gateway_tls
|
||||
caddy_0.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_1: metaplugintmdb-${APP_PUBLIC_IP_DASH}.nip.io
|
||||
caddy_1.import: gateway_tls
|
||||
caddy_1.reverse_proxy: "{{upstreams 80}}"
|
||||
caddy_2: metaplugintmdb-${APP_PUBLIC_IP_DASH}.sslip.io
|
||||
caddy_2.reverse_proxy: "{{upstreams 80}}"
|
||||
depends_on:
|
||||
- metaplugintmdb-plugin
|
||||
networks:
|
||||
- metaplugintmdb
|
||||
- pcs
|
||||
|
||||
networks:
|
||||
metaplugintmdb:
|
||||
name: metaplugintmdb
|
||||
driver: bridge
|
||||
pcs:
|
||||
name: ${APP_NET:-pcs}
|
||||
external: true
|
||||
|
||||
x-casaos:
|
||||
architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
author: Worph Team
|
||||
category: Developer
|
||||
developer: Aptero Team
|
||||
main: metaplugintmdb
|
||||
webui_port: 80
|
||||
index: /manifest
|
||||
|
||||
description:
|
||||
en_us: |
|
||||
**The `tmdb` enrichment plugin for MetaMesh, as its own app.**
|
||||
|
||||
Looks a title up on **The Movie Database** and writes the identity a record is anchored by — `tmdbid`, `imdbid`, localized titles — plus the poster and backdrop artwork.
|
||||
|
||||
**Bring your own key.** The plugin stores no credentials: the caller
|
||||
POSTs its own TMDB token to `/configure` before each use.
|
||||
**Requires MetaCore** — the artwork is written to its WebDAV, and a record
|
||||
without a poster is hidden by MetaWatch's quality gate.
|
||||
|
||||
**Headless.** It answers the plugin HTTP contract — `/manifest`, `/health`,
|
||||
`/configure`, `/process` — and has no dashboard; this tile opens its
|
||||
`/manifest`. Point a consumer at `http://metaplugintmdb-plugin:8080`.
|
||||
|
||||
**Who can use it today:** a **MetaFeeder** app, through its
|
||||
`TMDB_PLUGIN_URL` setting — one shared copy instead of one bundled per feeder.
|
||||
|
||||
**Not a MetaSort plugin.** MetaSort starts its own plugin containers from
|
||||
`plugins.yml` and has no slot for a plugin URL.
|
||||
fr_fr: |
|
||||
**Le plugin d'enrichissement `tmdb` de MetaMesh, en app autonome.**
|
||||
|
||||
Recherche un titre sur **The Movie Database** et écrit l'identité d'un enregistrement — `tmdbid`, `imdbid`, titres localisés — ainsi que l'affiche et l'image d'arrière-plan.
|
||||
|
||||
**Sans interface.** Il répond au contrat HTTP des plugins — `/manifest`,
|
||||
`/health`, `/configure`, `/process` — et n'a pas de tableau de bord ; cette
|
||||
tuile ouvre son `/manifest`. Dirigez un consommateur vers
|
||||
`http://metaplugintmdb-plugin:8080`.
|
||||
|
||||
**Ce n'est pas un plugin MetaSort** : MetaSort démarre ses propres
|
||||
conteneurs de plugins depuis `plugins.yml` et n'accepte pas d'URL.
|
||||
tagline:
|
||||
en_us: TMDB identity and artwork for MetaMesh records
|
||||
fr_fr: Identité et visuels TMDB pour les enregistrements MetaMesh
|
||||
|
||||
title:
|
||||
en_us: MetaPlugin · TMDB
|
||||
|
||||
icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginTMDB/icon.png
|
||||
thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaPluginTMDB/thumbnail.png
|
||||
|
||||
store_app_id: metaplugintmdb
|
||||
is_uncontrolled: false
|
||||
|
||||
tips:
|
||||
before_install:
|
||||
en_us: |
|
||||
**What this is for:** running the `tmdb` plugin **once, shared**, instead of
|
||||
one bundled copy inside every app that needs it.
|
||||
|
||||
**After installing:** set `TMDB_PLUGIN_URL` to `http://metaplugintmdb-plugin:8080` in the
|
||||
MetaFeeder app that should use it, then restart that feeder. Left unset,
|
||||
the feeder keeps using its own bundled copy.
|
||||
|
||||
**It is not a MetaSort plugin.** MetaSort spawns its own plugin containers
|
||||
from `plugins.yml` (`image:`), with no URL slot — installing this app does
|
||||
not add `tmdb` to MetaSort.
|
||||
|
||||
**Requires MetaCore** on the same box: this plugin writes what it
|
||||
produces to MetaCore's WebDAV (`http://metacore-app:9000/webdav`).
|
||||
|
||||
**Authentication:** the `/manifest` page is protected by Yundera's Authelia
|
||||
(OIDC). Consumers on the `pcs` network bypass the gate by design.
|
||||
|
||||
# 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/metaplugintmdb/cache
|
||||
user: $PUID
|
||||
group: $PGID
|
||||
# Same string as the caddy_0 label, so the tile link and the route agree.
|
||||
webui-host: metaplugintmdb-${domain}
|
||||
webui-path: "/manifest"
|
||||
|
After Width: | Height: | Size: 131 KiB |
@@ -0,0 +1,44 @@
|
||||
# MetaPlugin · TMDB — Rationale
|
||||
|
||||
## What deviation / exception is being requested
|
||||
|
||||
`metaplugintmdb-plugin` runs `user: 0:0`.
|
||||
|
||||
## Why it is necessary
|
||||
|
||||
The `metamesh-plugin-*` images declare no `USER` and have always run as root, both
|
||||
when MetaSort spawns them and when a MetaFeeder bundles them as an enrichment
|
||||
sidecar. This listing is a packaging change — the same image, started by Maison
|
||||
instead of by MetaSort — so it keeps the runtime user the image was built and
|
||||
validated with.
|
||||
|
||||
## 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 plugin carries no
|
||||
Caddy labels and publishes no host port, so `/process` and `/configure` are 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 cache under `/DATA/AppData/metaplugintmdb/`, declared in
|
||||
`x-compose-app.folders` and owned `$PUID:$PGID`. It reads the files it works on over
|
||||
MetaCore's WebDAV, never from a bind mount.
|
||||
- It holds **no credentials of its own**: keys arrive per-caller over `/configure` and
|
||||
are never written to this app's storage.
|
||||
- `cpu_shares` and a hard `mem_limit` (swap disabled) set on every service; image tags
|
||||
pinned; no `:latest`.
|
||||
|
||||
## Alternatives considered and rejected
|
||||
|
||||
- **Run as `$PUID:$PGID`.** Not validated for these images; a uid change belongs in the
|
||||
plugin image, not in a store listing that is otherwise a pure repackaging.
|
||||
- **No AppShield gate, no public route at all.** Rejected: the tile would have no way to
|
||||
show whether the plugin is alive, and `/manifest` is the only status surface a headless
|
||||
plugin has.
|
||||
- **Expose it unauthenticated.** Rejected outright — `/process` takes URLs and does work
|
||||
on request, and `/configure` accepts credentials.
|
||||
|
||||
## Data protection
|
||||
|
||||
The plugin holds no user media and no user account. Its only state is a rebuildable cache under
|
||||
`/DATA/AppData/metaplugintmdb/`, which is the unit Maison archives on uninstall.
|
||||
|
After Width: | Height: | Size: 131 KiB |