Files
MetaAppStore/Apps/MetaShare/docker-compose.yml
T
2026-10-03 19:24:34 +02:00

403 lines
18 KiB
YAML

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/<cid> 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/<v6>/…` 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-<user>.<domain>`.
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: "/"