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 run as the three transport plugins below (the # `meta_feeder_sdk::transport` contract) and share its data volume: # - 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.0.1 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 NNTP # credentials and network endpoints. MUST be persisted (else lost on every # image bump). The nzb and ipfs plugins mount it read-only. - ${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 plugins' corners of the SAME mount — # one mount, 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: - META_SHARE_PLUGIN_TORRENT_URL=http://metashare-torrent:3000 - META_SHARE_PLUGIN_NZB_URL=http://metashare-nzb:3000 - META_SHARE_PLUGIN_IPFS_URL=http://metashare-ipfs:3000 # 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 # BitTorrent tier (librqbit, streaming-only fork). Its peer listener must be # host-published so the swarm can dial in (idle seeding without a dial storm). metashare-torrent: image: ghcr.io/worph/meta-transport-torrent:1.0.1 container_name: metashare-torrent hostname: metashare-torrent restart: unless-stopped mem_limit: 1g memswap_limit: 1g cpu_shares: 20 stop_grace_period: 30s expose: - 3000 ports: - "4240:4240" volumes: - ${DATA_ROOT:-/DATA}/AppData/metashare/data:/data:rw environment: - META_SHARE_HULL_URL=http://metashare-app:3001 - 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 # Usenet tier (NNTP, yEnc, par2/RAR). Reads the NNTP credentials from the # hull's settings.json; the hull restarts it when they change. metashare-nzb: image: ghcr.io/worph/meta-transport-nzb:1.0.1 container_name: metashare-nzb hostname: metashare-nzb restart: unless-stopped mem_limit: 3g memswap_limit: 3g cpu_shares: 20 stop_grace_period: 30s expose: - 3000 volumes: - ${DATA_ROOT:-/DATA}/AppData/metashare/config:/config:ro - ${DATA_ROOT:-/DATA}/AppData/metashare/data:/data:rw environment: - META_SHARE_HULL_URL=http://metashare-app:3001 - 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 # IPFS tier: the libp2p host (identify/kad/mdns/bitswap), the blockstore and # bitswap ingress. libp2p TCP must be host-published — Caddy can't proxy it and # other peers' kad lookups dial the public multiaddr directly. metashare-ipfs: image: ghcr.io/worph/meta-transport-ipfs:1.0.1 container_name: metashare-ipfs hostname: metashare-ipfs restart: unless-stopped # The blockstore's redb page cache + the swarm live here now. mem_limit: 2g memswap_limit: 2g cpu_shares: 20 stop_grace_period: 30s expose: - 3000 ports: - "4001:4001" volumes: - ${DATA_ROOT:-/DATA}/AppData/metashare/config:/config:ro - ${DATA_ROOT:-/DATA}/AppData/metashare/data:/data:rw environment: - META_SHARE_HULL_URL=http://metashare-app:3001 - P2P_LISTEN=/ip4/0.0.0.0/tcp/4001 # Advertise the routable address so kad provider records + identify carry # a dialable multiaddr. ${APP_PUBLIC_IPV4}, NOT ${APP_PUBLIC_IP}: on an # IPv6-primary PCS the latter is v6 and `/ip4//…` won't parse (the # plugin refuses to start rather than announce a lie). - PUBLIC_ADDR=/ip4/${APP_PUBLIC_IPV4}/tcp/4001 # Public IPFS DHT bootstraps by explicit /dns4/…/tcp/4001 (the nested # dnsaddr form resolves to wss-only in some container resolvers), plus # mars.i.ipfs.io — the four IPFS-Foundation bootstrappers can refuse an # individual IP (watch.nsl.sh 2026-09-01: 2 connections → 126 with a fifth). - KAD_BOOTSTRAP_PEERS=/dns4/sg1.bootstrap.libp2p.io/tcp/4001/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt,/dns4/sv15.bootstrap.libp2p.io/tcp/4001/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN,/dns4/ny5.bootstrap.libp2p.io/tcp/4001/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa,/dns4/am6.bootstrap.libp2p.io/tcp/4001/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb,/dns4/mars.i.ipfs.io/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ # Seed fallbacks when the hull's settings.json doesn't exist yet. - META_CORE_URL=http://metacore-app:80 - INTERNAL_BASE_URL=https://metashare-$APP_DOMAIN - TZ=$TZ user: 0:0 # The hull migrates its material index out of ipfs/blocks.redb on its first # 2.x boot; redb is single-process, so the plugin waits for it. depends_on: metashare-app: condition: service_healthy 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", "needs-public-ip"] 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. **Port 4001 must be reachable from the public internet.** MetaShare's libp2p TCP listener is host-published on port 4001 so other archivists' peers (and the public IPFS kad-DHT) can dial this node directly. If your Yundera host is behind a strict firewall, open TCP/4001 inbound — without it, this peer can still *find* others but won't be findable by them. **Cohort scoping (optional).** Out of the box, MetaShare joins the global `metamesh-share-default` cohort. To run a private group of archivists, change `KAD_NAMESPACE` to a shared secret string on every node in the group — only nodes with the matching namespace will see each other. **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: "/"