name: metashare services: # Backend service (the hull). Reachable on the pcs network as `metashare-app`. # Public ingress goes through the `metashare` hash-lock proxy below. # # meta-share is the protocol-agnostic hull since 2.0.0: HTTP API + dashboard, # material index, seed manifest, eviction, playback focus, meta-core. The # byte-moving protocols are transport plugins — each its OWN store app since # 2.1.0 (MetaTransportTorrent / MetaTransportNzb / MetaTransportIpfs), found # by hostname (`metashare-:3000`) and managed live from the dashboard's # Plugins view. A plugin app that isn't installed is simply skipped. # - 3000 HTTP API + dashboard (proxied through the hash-lock + Caddy) # - 3001 plugin callback API — internal only, never published metashare-app: image: ghcr.io/worph/meta-share:2.2.0 container_name: metashare-app # Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM instead of # swap. The swarm, blockstore and torrent session moved to the plugins, so # the hull needs far less than the 3g the monolith did. mem_limit: 1g memswap_limit: 1g hostname: metashare-app restart: unless-stopped cpu_shares: 20 # Room for a clean SIGTERM shutdown: redb's `material.redb` writes its # allocator state on Drop, or the next open runs a full repair scan. stop_grace_period: 30s expose: - 3000 # HTTP API (Axum) - 3001 # plugin callback API (internal) volumes: # Operator settings (META_SHARE_CONFIG_DIR): settings.json holds the # network endpoints and the plugin list. MUST be persisted (else lost on # every image bump). Plugins keep their own settings in their own app. - ${DATA_ROOT:-/DATA}/AppData/metashare/config:/config:rw # The shared data volume (META_SHARE_DATA): cache/, tmp/, seeds.json and # material.redb are the hull's; bt-temp/ (torrent), tmp/ jobs (nzb) # and ipfs/blocks.redb (ipfs) are the plugin apps' corners of the SAME # host folder, which they mount too — so the tmp/ → cache/ rename(2) stays # atomic. Layout unchanged from the monolith: existing caches carry over. - ${DATA_ROOT:-/DATA}/AppData/metashare/data:/data:rw environment: # Service registration / neighbour announce. - BASE_URL=https://metashare-$APP_DOMAIN # meta-core ingestion — the local archivist's library. - META_CORE_URL=http://metacore-app:80 # Inter-peer fetch URL, announced in identify by the ipfs plugin. Same as # BASE_URL in prod (other peers fetch via the public Caddy URL). - INTERNAL_BASE_URL=https://metashare-$APP_DOMAIN - TZ=$TZ user: 0:0 healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:3000/health"] interval: 10s timeout: 3s retries: 10 start_period: 30s networks: - pcs # OIDC perimeter for the MetaShare dashboard. Self-registers as an OIDC client # with the platform's auth-registrar on first login (no secrets to manage). # # Naming: container_name MUST equal the URL-prefix of the public hostname so # the registrar's redirect-URI allowlist accepts `metashare-.`. metashare: image: ghcr.io/yundera/appshield:2.0.9 cpu_shares: 80 container_name: metashare hostname: metashare restart: unless-stopped user: "0:0" expose: - 80 environment: OIDC_REGISTRAR_URL: "http://auth-registrar:9092" BACKEND_HOST: "metashare-app" BACKEND_PORT: "3000" LISTEN_PORT: "80" REDIRECT_HOST_SUFFIXES: "${APP_DOMAIN},${APP_PUBLIC_IP_DASH}.nip.io,${APP_PUBLIC_IP_DASH}.sslip.io" depends_on: - metashare-app labels: # Peer-to-peer surface bypasses the hash-lock so other archivists' # meta-share peers can reach /api/peers, /api/peer/*, /api/file/*, # /api/search* and /api/services without an OIDC session. The # dashboard (everything else) sits behind hash-lock as usual. # ---- metashare-${APP_DOMAIN} (gateway TLS) ---- caddy_0: metashare-${APP_DOMAIN} caddy_0.import: gateway_tls caddy_0.0_handle: "/health" caddy_0.0_handle.reverse_proxy: "metashare-app:3000" caddy_0.1_handle: "/api/peers*" caddy_0.1_handle.reverse_proxy: "metashare-app:3000" caddy_0.2_handle: "/api/peer/*" caddy_0.2_handle.reverse_proxy: "metashare-app:3000" caddy_0.3_handle: "/api/file/*" caddy_0.3_handle.reverse_proxy: "metashare-app:3000" caddy_0.4_handle: "/api/search*" caddy_0.4_handle.reverse_proxy: "metashare-app:3000" caddy_0.5_handle: "/api/services" caddy_0.5_handle.reverse_proxy: "metashare-app:3000" caddy_0.6_handle: "" caddy_0.6_handle.reverse_proxy: "{{upstreams 80}}" # ---- metashare-IP.nip.io (gateway TLS) ---- caddy_1: metashare-${APP_PUBLIC_IP_DASH}.nip.io caddy_1.import: gateway_tls caddy_1.0_handle: "/health" caddy_1.0_handle.reverse_proxy: "metashare-app:3000" caddy_1.1_handle: "/api/peers*" caddy_1.1_handle.reverse_proxy: "metashare-app:3000" caddy_1.2_handle: "/api/peer/*" caddy_1.2_handle.reverse_proxy: "metashare-app:3000" caddy_1.3_handle: "/api/file/*" caddy_1.3_handle.reverse_proxy: "metashare-app:3000" caddy_1.4_handle: "/api/search*" caddy_1.4_handle.reverse_proxy: "metashare-app:3000" caddy_1.5_handle: "/api/services" caddy_1.5_handle.reverse_proxy: "metashare-app:3000" caddy_1.6_handle: "" caddy_1.6_handle.reverse_proxy: "{{upstreams 80}}" # ---- metashare-IP.sslip.io (Let's Encrypt) ---- caddy_2: metashare-${APP_PUBLIC_IP_DASH}.sslip.io caddy_2.0_handle: "/health" caddy_2.0_handle.reverse_proxy: "metashare-app:3000" caddy_2.1_handle: "/api/peers*" caddy_2.1_handle.reverse_proxy: "metashare-app:3000" caddy_2.2_handle: "/api/peer/*" caddy_2.2_handle.reverse_proxy: "metashare-app:3000" caddy_2.3_handle: "/api/file/*" caddy_2.3_handle.reverse_proxy: "metashare-app:3000" caddy_2.4_handle: "/api/search*" caddy_2.4_handle.reverse_proxy: "metashare-app:3000" caddy_2.5_handle: "/api/services" caddy_2.5_handle.reverse_proxy: "metashare-app:3000" caddy_2.6_handle: "" caddy_2.6_handle.reverse_proxy: "{{upstreams 80}}" networks: - pcs networks: pcs: name: ${APP_NET:-pcs} external: true x-casaos: architectures: - amd64 - arm64 author: Worph Team category: Media # `needs-public-ip` is the reserved functional tag: this app publishes a raw # libp2p TCP port that remote peers must dial inbound. Behind CGNAT or the # mesh-router tunnel it still installs and starts, but peers cannot reach it. tags: ["media"] developer: Aptero Team description: en_us: | **Decentralized metadata sharing across MetaMesh archivists.** MetaShare turns your local MetaMesh library into a peer in a federated discovery network. Archivists running their own Yundera nodes can search across each other's libraries without surrendering control of their files — discovery returns pointers (peer + CID), and any retrieval is an explicit HTTP fetch from the source peer. **Key Features:** - **Federated Search** - Type-once, query-many across the cohort using the Kamilata routing primitive - **Pointer-Only Discovery** - Search returns `(peer, cid)`; data never leaves the source until you ask for it - **Automatic Peer Discovery** - Public IPFS DHT (kad) finds other archivists; mDNS handles same-LAN cohorts; no designated bootstrap node - **Cohort Scoping** - `KAD_NAMESPACE` lets a private group of archivists isolate their swarm from the public default - **Built on libp2p** - QUIC + TCP transport, Noise-encrypted, peer-id authenticated - **MetaCore-Backed** - Ingests from your local MetaCore Redis and re-publishes records on the swarm **Requires:** MetaCore (install first; MetaShare reads its metadata from MetaCore's API) **Works with:** MetaSort (so MetaCore has records to share), MetaFuse, MetaStremio **Status:** v0.2 POC. In-memory record store (rebuilt from MetaCore on each start), no persistent peer-id, no announcement signing yet. Production-grade hardening lands in v0.2.1. **Dashboard Features:** Peer list with live connection status, federated search, per-record source attribution, swarm health diagnostics fr_fr: | **Partage de metadonnees decentralise entre archivistes MetaMesh.** MetaShare transforme votre bibliotheque MetaMesh locale en pair d'un reseau de decouverte federe. Les archivistes faisant tourner leur propre serveur Yundera peuvent chercher dans les bibliotheques des autres sans abandonner le controle de leurs fichiers — la decouverte renvoie des pointeurs (pair + CID), et toute recuperation est une requete HTTP explicite vers le pair source. **Fonctionnalites Cles:** - **Recherche Federee** - Une seule requete interroge tout le cohorte via la primitive de routage Kamilata - **Decouverte par Pointeurs** - La recherche renvoie `(pair, cid)` ; les donnees ne quittent le pair source que sur demande explicite - **Decouverte Automatique des Pairs** - Le DHT IPFS public (kad) trouve les autres archivistes ; mDNS gere les cohortes sur le meme LAN ; aucun noeud bootstrap designe - **Isolation par Cohorte** - `KAD_NAMESPACE` permet a un groupe prive d'archivistes d'isoler leur swarm du namespace public - **Construit sur libp2p** - Transport QUIC + TCP, chiffrement Noise, authentification par peer-id - **Adosse a MetaCore** - Ingere depuis votre Redis MetaCore local et republie les enregistrements sur le swarm **Necessite:** MetaCore (installer en premier ; MetaShare lit ses metadonnees depuis l'API MetaCore) **Fonctionne avec:** MetaSort (pour que MetaCore ait des enregistrements a partager), MetaFuse, MetaStremio **Statut:** POC v0.2. Stockage des enregistrements en memoire (reconstruit depuis MetaCore a chaque demarrage), pas de peer-id persistant, pas encore de signature des annonces. Le durcissement de niveau production arrive en v0.2.1. tagline: en_us: Decentralized metadata sharing - Federated search across archivists without surrendering your files fr_fr: Partage de metadonnees decentralise - Recherche federee entre archivistes sans abandonner vos fichiers title: en_us: MetaShare icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaShare/icon.png screenshot_link: - https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaShare/screenshot-1.png - https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaShare/screenshot-2.png - https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaShare/screenshot-3.png thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaShare/thumbnail.png index: / store_app_id: metashare main: metashare is_uncontrolled: false tips: before_install: en_us: | **Requires MetaCore** - Install MetaCore first. **Then install the transport plugins you want** — each is its own app: **MetaTransport · IPFS** (the mesh: peers, seeding — install this one), **MetaTransport · BitTorrent** and **MetaTransport · Usenet**. MetaShare picks each up within ~10 s; see and configure them in the dashboard's **Plugins** view. Without any plugin MetaShare can fetch nothing. **Ports** are published by the plugin apps, not here: 4001 (IPFS) must be reachable from the internet so other peers can dial you; 4240 (BitTorrent) for seeding. **Cohort scoping (optional).** Out of the box, MetaShare joins the global `metamesh-share-default` cohort. To run a private group of archivists, set the same namespace on every node: **Plugins → ipfs → configure**. **POC status.** v0.2 has an in-memory record store (rebuilt from MetaCore on every restart, ~2.5s for ~500 records) and no announcement signing yet. Don't rely on this for production federation until v0.2.1. The dashboard is protected by Yundera's built-in Authelia via OIDC — sign in with your existing Yundera account. The peer-to-peer API surface (`/api/peers`, `/api/peer/*`, `/api/file/*`, `/api/search*`) is intentionally bypassed so other peers can reach it without an OIDC session. # 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/metacore/meta-core user: $PUID group: $PGID - path: /DATA/AppData/metashare/config user: $PUID group: $PGID - path: /DATA/AppData/metashare/data user: $PUID group: $PGID # Fetched content only: everything under these can be pulled again off the # swarm/NNTP/HTTP (meta-share reclaim.rs, "cache means cache"). They are ~94G # on a busy peer, so without this every backup — and Maison's pre-update # rollback copy — duplicates the whole cache. A restore brings them back empty. # Kept: config/ (settings), data/meta-share/ipfs/ (blockstore + material # index), seeds.json and the other small state files. backup: exclude: - data/meta-share/cache/ - data/meta-share/bt-temp/ - data/meta-share/tmp/ # Same string as the caddy_0 label, so the tile link and the route agree. webui-host: metashare-${domain} webui-path: "/"