name: metawatch # ============================================================================= # MetaWatch — Stremio-style, video-only client for the MetaMesh network. # # Self-contained stack (mirrors packages/meta-watch/dev/): the meta-watch app # plus its OWN dedicated discovery (meta-search), transport (meta-share) and # meta-core. It does NOT share the standalone MetaCore app — its core holds # only this client's identity keystore + ingested records, so MetaWatch can be # installed alongside MetaGateway and the standalone Meta* apps with zero # container-name / data-path collisions. # # metawatch-core → /DATA/AppData/metawatch/metacore (per-stack meta-core) # metawatch-search → discovery (Kamilata federated search, identity proxy) # metawatch-share → pure content transport (byte Range, bitswap, BT) # metawatch-app → the UI/back-end the user browses (behind OIDC) # # Federation: meta-search/meta-share join the PUBLIC libp2p swarm (kad-DHT, # default cohort) on distinct host ports (4003/4004) + mDNS on `pcs`, so they # discover MetaGateway's records both same-host (mDNS) and across nodes (kad). # ============================================================================= services: # --------------------------------------------------------------------------- # metawatch-core — dedicated meta-core for this client (identity keystore + # ingested records). File watcher OFF: MetaWatch is a consumer, not a library # indexer. Data lives under /DATA/AppData/metawatch/ so CasaOS archives it. # --------------------------------------------------------------------------- metawatch-core: # 1.0.1 carries the identity API (commit 52f5b5c) required for MetaWatch # sign-in / keypair generation; 1.0.0 predates it. image: ghcr.io/worph/meta-core:1.0.23 container_name: metawatch-core # Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM # instead of swap. Mirrors the dev sub-stack limits. mem_limit: 768m memswap_limit: 768m hostname: metawatch-core restart: unless-stopped privileged: true user: "0:0" cpu_shares: 30 expose: - 9000 volumes: - ${DATA_ROOT:-/DATA}/AppData/metawatch/metacore:/meta-core:rw # Subtitle store: player-uploaded subtitles land in /files/plugin/subtitles # (WebDAV PUT + POST /api/files/register, needs meta-core with that route). # Plugin output only — no /files/watch mount, watcher stays off. - ${DATA_ROOT:-/DATA}/AppData/metawatch/files/plugin:/files/plugin:rw environment: - BASE_URL=http://metawatch-core:9000 - ENABLE_FILE_WATCHER=false - TZ=$TZ healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:9000/health"] interval: 5s timeout: 3s retries: 30 start_period: 30s networks: - metawatch # --------------------------------------------------------------------------- # metawatch-search — DISCOVERY peer. Federated Kamilata search, record # metadata, poster/thumbnail recovery, shared prefs, User Data Layer and the # identity/sign chain (meta-watch → meta-search → meta-core). Public libp2p on # host port 4004; mDNS on pcs for same-host peer discovery. # --------------------------------------------------------------------------- metawatch-search: # 1.0.2 = rsa-enabled libp2p — required to join the public IPFS DHT (the # RSA-keyed bootstrap.libp2p.io nodes) and therefore to DISCOVER remote # gateways via the kad `metamesh-gateway` provider namespace. Without it # this peer only ever sees same-host mDNS peers. image: ghcr.io/worph/meta-search:1.2.8 container_name: metawatch-search # Hard RAM cap, swap disabled (memswap_limit == mem_limit) → OOM # instead of swap. Mirrors the dev sub-stack limits. mem_limit: 1g memswap_limit: 1g hostname: metawatch-search restart: unless-stopped user: "0:0" cpu_shares: 30 expose: - 3000 ports: # libp2p TCP — host-published so the swarm can dial this peer. - "4004:4001" volumes: # No /meta-core mount: this service locates meta-core over UDP # (beacon v2) instead of reading kv-leader.info off a shared # volume. See docs/project-architecture/beacon-v2.md. # META_SHARE_DATA (default /data/meta-share): prefs.json (language prefs) # + the redb blockstore holding poster/thumbnail blocks recovered off the # swarm. Without this mount both are on the container's writable layer and # every image bump loses the prefs and re-fetches every poster. - ${DATA_ROOT:-/DATA}/AppData/metawatch/search-data:/data:rw environment: P2P_LISTEN: "/ip4/0.0.0.0/tcp/4001" # Public IPFS DHT bootstraps by explicit /dns4/.../tcp/4001 (the nested # dnsaddr default resolves to wss-only in some container resolvers). # NOTE: no PUBLIC_ADDR here ON PURPOSE. meta-search supports it, but this # peer only needs to WALK the DHT: with the bootstraps below it discovers # remote gateways (kad `metamesh-gateway` provider records) and dials them # outbound. Announcing would publish this client's index to the swarm for # no benefit. The gateway is the peer that must be dialable — see # MetaGateway's META_GATEWAY_PUBLIC_ADDR. # Includes mars.i.ipfs.io — the only surviving legacy *.i.ipfs.io # bootstrapper (neptune/pluto/uranus/earth/saturn/venus are all NXDOMAIN). # It is here for resilience: the four IPFS-Foundation bootstrappers can # refuse an individual IP, and when they do a peer cannot join the DHT AT # ALL — no provider record, no discovery, no gateway fan-out. Observed on # watch.nsl.sh 2026-09-01: 2 connections with the four alone, 126 once a # reachable fifth was added. Costs nothing when the four work. 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" # Was LOAD-BEARING up to meta-search:1.0.2, which defaulted this to 8s — too # short for the gateway fan-out (Prowlarr -> public indexers), so search # returned empty. 300 is the default on the 1.1.0 pin; kept explicit so a # future default change can't silently shorten it again. SEARCH_TIMEOUT_SECS: "300" META_CORE_URL: "http://metawatch-core:9000" BASE_URL: "http://metawatch-search:3000" # Renamed at the 1.1.0 bump (2026-08-26), as the old note here said to do: # META_SHARE_PEER_API_URL was the pre-1.0.8 spelling and now only logs a # deprecation warning. Do NOT set both. INTERNAL_BASE_URL: "http://metawatch-search:3000" # NOTE: KAD_NAMESPACE was dropped — it merely restated the default cohort # (`metamesh-share-default`). Set it to a non-default value on EVERY peer # to scope a private cohort; they must all agree or search returns zero. TZ: "$TZ" depends_on: metawatch-core: condition: service_healthy healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:3000/health"] interval: 5s timeout: 3s retries: 10 start_period: 5s networks: - metawatch - pcs # --------------------------------------------------------------------------- # metawatch-share (+ metawatch-share-ipfs) — content TRANSPORT peer: the # meta-share hull with one ipfs plugin. Byte Range proxy for playback/probe/ # warm/playback-focus over bitswap; seeds whatever it fetches; Usenet cids are # forwarded to a credentialed gateway. Public libp2p on host port 4003. # --------------------------------------------------------------------------- metawatch-share: # The meta-share HULL (2.x): HTTP API, material index, seed manifest, # eviction, playback focus, meta-core ingest. Every byte-moving protocol is # a transport plugin; this bundle ships ONE — metawatch-share-ipfs below — which # makes it the playback cache: bitswap fetch + re-seed, and Usenet cids forwarded to a credentialed gateway. Torrent and local NZB tiers are not bundled # (META_SHARE_PLUGIN_{TORRENT,NZB}_URL=off keeps the hull from seeding the # MetaShare app's `metashare-` hostnames, which on a shared `pcs` # would be ANOTHER hull's plugins writing to another /data). # - 3000 HTTP API - 3001 plugin callback API (internal only) image: ghcr.io/worph/meta-share:2.4.0 container_name: metawatch-share # The swarm and blockstore moved to the ipfs plugin, so the hull needs far # less than the monolith did. mem_limit: 1g memswap_limit: 1g hostname: metawatch-share restart: unless-stopped user: "0:0" cpu_shares: 30 # material.redb writes its allocator state on Drop; give SIGTERM room. stop_grace_period: 30s expose: - 3000 - 3001 volumes: # settings.json — network endpoints and the plugin list. - ${DATA_ROOT:-/DATA}/AppData/metawatch/metashare-config:/config:rw # The shared data volume: cache/, tmp/, seeds.json, material.redb are the # hull's; ipfs/blocks.redb is the ipfs plugin's corner of the SAME folder # (layout unchanged from 1.0.x, so an existing cache carries over). - ${DATA_ROOT:-/DATA}/AppData/metawatch/share-data:/data:rw environment: META_CORE_URL: "http://metawatch-core:9000" BASE_URL: "http://metawatch-share:3000" INTERNAL_BASE_URL: "http://metawatch-share:3000" META_SHARE_PLUGIN_IPFS_URL: "http://metawatch-share-ipfs:3000" META_SHARE_PLUGIN_TORRENT_URL: "off" META_SHARE_PLUGIN_NZB_URL: "off" TZ: "$TZ" depends_on: metawatch-core: condition: service_healthy healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:3000/health"] interval: 5s timeout: 3s retries: 10 start_period: 10s networks: - metawatch - pcs metawatch-share-ipfs: # The hull's only transport plugin: the libp2p swarm (kad-DHT, identify, # mDNS, bitswap), the blockstore, gateway discovery and the peer directory # the hull uses to forward Usenet cids. Waits up to 60 s for the hull's # internal /health before opening blocks.redb, so no depends_on. image: ghcr.io/worph/meta-transport-ipfs:1.3.0 container_name: metawatch-share-ipfs hostname: metawatch-share-ipfs restart: unless-stopped user: "0:0" cpu_shares: 30 mem_limit: 2g memswap_limit: 2g stop_grace_period: 30s expose: - 3000 # libp2p TCP must be host-published (kad lookups dial it directly; Caddy # cannot proxy it). Same host port the 1.0.x monolith used. ports: - "4003:4001" volumes: - ${DATA_ROOT:-/DATA}/AppData/metawatch/share-data:/data:rw - ${DATA_ROOT:-/DATA}/AppData/metawatch/share-ipfs-state:/state:rw environment: META_SHARE_HULL_URL: "http://metawatch-share:3001" # Beacon v2: only this bundle's hull may list this plugin. BEACON_BINDS: "metawatch-share" # ${APP_PUBLIC_IPV4}, NOT ${APP_PUBLIC_IP}: on an IPv6-primary PCS the # latter is a v6 address, and /ip4//… is not a parseable multiaddr. PUBLIC_ADDR: "/ip4/${APP_PUBLIC_IPV4}/tcp/4003" # Public IPFS DHT bootstraps by explicit /dns4/…/tcp/4001, plus # mars.i.ipfs.io in case the foundation four refuse this IP. 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" TZ: "$TZ" healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:3000/health"] interval: 10s timeout: 3s retries: 10 start_period: 30s networks: - metawatch - pcs # --------------------------------------------------------------------------- # metawatch-app — the back-end the browser talks to. Zero media work: flat # Range/206 byte proxy to metawatch-share; search/identity via metawatch-search. # My List + Continue Watching persist in /DATA/AppData/metawatch/data. # --------------------------------------------------------------------------- metawatch-app: image: ghcr.io/worph/meta-watch:1.0.83 container_name: metawatch-app hostname: metawatch-app restart: unless-stopped cpu_shares: 30 # Hard RAM cap with swap disabled (memswap_limit == mem_limit → zero swap # budget on cgroup v2) so the container is OOM-killed instead of pushing # the host into swap. Same value the dev stack pins (METAWATCH_MEM=512m). mem_limit: 512m memswap_limit: 512m expose: - 3000 volumes: - ${DATA_ROOT:-/DATA}/AppData/metawatch/data:/app/data environment: META_SHARE_URL: "http://metawatch-share:3000" META_SEARCH_URL: "http://metawatch-search:3000" # Browser-reachable topbar chips — the AppShield gates below expose the # co-located meta-share transport and meta-search discovery peers behind # Yundera OIDC, and these URLs are what the topbar chip clicks open. META_SHARE_PUBLIC_URL: "https://metawatchshare-${APP_DOMAIN}" META_SEARCH_PUBLIC_URL: "https://metawatchsearch-${APP_DOMAIN}" META_WATCH_SYNC_PEERS: "http://metawatch-search:3000" # This box's own core — subtitle store only (uploads + curated matches). META_CORE_URL: "http://metawatch-core:9000" TZ: "$TZ" depends_on: metawatch-search: condition: service_healthy metawatch-share: condition: service_healthy healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:3000/health"] interval: 5s timeout: 3s retries: 10 start_period: 5s networks: - metawatch # --------------------------------------------------------------------------- # OIDC perimeter for the MetaWatch UI. Self-registers as an OIDC client with # the platform's auth-registrar on first login. container_name MUST equal the # URL prefix (`metawatch`) so the registrar's redirect-URI allowlist matches # `metawatch-.`. # --------------------------------------------------------------------------- metawatch: image: ghcr.io/yundera/appshield:2.0.9 cpu_shares: 80 container_name: metawatch hostname: metawatch restart: unless-stopped user: "0:0" expose: - 80 environment: OIDC_REGISTRAR_URL: "http://auth-registrar:9092" BACKEND_HOST: "metawatch-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: - metawatch-app labels: # ---- metawatch-${APP_DOMAIN} (gateway TLS) ---- caddy_0: metawatch-${APP_DOMAIN} caddy_0.import: gateway_tls caddy_0.0_handle: "/health" caddy_0.0_handle.reverse_proxy: "metawatch-app:3000" caddy_0.1_handle: "" caddy_0.1_handle.reverse_proxy: "{{upstreams 80}}" # ---- metawatch-IP.nip.io (gateway TLS) ---- caddy_1: metawatch-${APP_PUBLIC_IP_DASH}.nip.io caddy_1.import: gateway_tls caddy_1.0_handle: "/health" caddy_1.0_handle.reverse_proxy: "metawatch-app:3000" caddy_1.1_handle: "" caddy_1.1_handle.reverse_proxy: "{{upstreams 80}}" # ---- metawatch-IP.sslip.io (Let's Encrypt) ---- caddy_2: metawatch-${APP_PUBLIC_IP_DASH}.sslip.io caddy_2.0_handle: "/health" caddy_2.0_handle.reverse_proxy: "metawatch-app:3000" caddy_2.1_handle: "" caddy_2.1_handle.reverse_proxy: "{{upstreams 80}}" networks: - metawatch - pcs # --------------------------------------------------------------------------- # AppShield gates — expose metawatch-share and metawatch-search dashboards # behind Yundera OIDC SSO so the meta-watch topbar chips (🔎 meta-search / # 🔗 meta-share) can open them. container_name MUST equal the subdomain # prefix (auth-registrar derives the OIDC client_id from it), so these use # collision-free prefixes — the co-located backends are `metawatch-*` and # keep their internal ports. Same pattern MetaGateway uses for its own # metagatewaycore / metagatewayshare gates. # --------------------------------------------------------------------------- metawatchshare: image: ghcr.io/yundera/appshield:2.0.9 cpu_shares: 80 container_name: metawatchshare hostname: metawatchshare 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: "metawatch-share" BACKEND_PORT: "3000" LISTEN_PORT: "80" labels: caddy_0: metawatchshare-${APP_DOMAIN} caddy_0.import: gateway_tls caddy_0.reverse_proxy: "{{upstreams 80}}" caddy_1: metawatchshare-${APP_PUBLIC_IP_DASH}.nip.io caddy_1.import: gateway_tls caddy_1.reverse_proxy: "{{upstreams 80}}" caddy_2: metawatchshare-${APP_PUBLIC_IP_DASH}.sslip.io caddy_2.reverse_proxy: "{{upstreams 80}}" depends_on: - metawatch-share networks: - metawatch - pcs metawatchsearch: image: ghcr.io/yundera/appshield:2.0.9 cpu_shares: 80 container_name: metawatchsearch hostname: metawatchsearch 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: "metawatch-search" BACKEND_PORT: "3000" LISTEN_PORT: "80" labels: caddy_0: metawatchsearch-${APP_DOMAIN} caddy_0.import: gateway_tls caddy_0.reverse_proxy: "{{upstreams 80}}" caddy_1: metawatchsearch-${APP_PUBLIC_IP_DASH}.nip.io caddy_1.import: gateway_tls caddy_1.reverse_proxy: "{{upstreams 80}}" caddy_2: metawatchsearch-${APP_PUBLIC_IP_DASH}.sslip.io caddy_2.reverse_proxy: "{{upstreams 80}}" depends_on: - metawatch-search networks: - metawatch - pcs networks: metawatch: name: metawatch driver: bridge 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: | **A Stremio-style, video-only player for the decentralized MetaMesh network.** MetaWatch browses curated rows and filtered search across the MetaMesh federation and plays back in your browser — the server does zero media work (flat byte-range streaming, no transcode). Anything without a poster and a description is hidden, so the wall is always clean. This is a **self-contained** stack: it ships its own discovery (meta-search), content transport (meta-share) and a dedicated meta-core for identity and state. Install it on its own — it does not depend on the standalone MetaCore app, and coexists with MetaGateway without conflicts. **Key Features:** - **Curated Rows + Search** - Popular / trending / top-rated / anime rows, plus federated search - **Browser-Side Playback** - SegmentPlayer streams via Range/206; the server never transcodes - **Poster-Gated Wall** - Only titles with poster + description are shown - **Show Aggregation** - Per-episode records grouped into show pages by tmdbid/imdbid/title - **My List + Continue Watching** - Persistent, backed by the User Data Layer op-log - **TV Remote Navigation** - D-pad spatial navigation for the 10-foot UI - **Federated Discovery** - Joins the public libp2p swarm to find MetaGateway records fr_fr: | **Un lecteur vidéo façon Stremio pour le réseau décentralisé MetaMesh.** MetaWatch parcourt des rangées sélectionnées et une recherche filtrée à travers la fédération MetaMesh et lit la vidéo dans votre navigateur — le serveur ne fait aucun travail média (streaming par plages d'octets, sans transcodage). Tout ce qui n'a ni affiche ni description est masqué. C'est une stack **autonome** : elle embarque sa propre découverte (meta-search), son transport de contenu (meta-share) et un meta-core dédié pour l'identité et l'état. Installez-la seule — elle ne dépend pas de l'app MetaCore et cohabite avec MetaGateway sans conflit. **Fonctionnalités Clés:** - **Rangées + Recherche** - Populaire / tendances / mieux notés / anime, plus recherche fédérée - **Lecture Côté Navigateur** - SegmentPlayer en Range/206 ; le serveur ne transcode jamais - **Mur à Affiches** - Seuls les titres avec affiche + description sont montrés - **Agrégation de Séries** - Épisodes regroupés en pages de série par tmdbid/imdbid/titre - **Ma Liste + Reprendre** - Persistant, via l'op-log de la couche de données utilisateur - **Navigation Télécommande** - Navigation spatiale D-pad pour l'interface salon tagline: en_us: Stremio-style video player for the decentralized MetaMesh network fr_fr: Lecteur vidéo façon Stremio pour le réseau décentralisé MetaMesh title: en_us: MetaWatch icon: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaWatch/icon.png screenshot_link: - https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaWatch/screenshot-1.png - https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaWatch/screenshot-2.png - https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaWatch/screenshot-3.png thumbnail: https://cdn.jsdelivr.net/gh/Worph/MetaAppStore@main/Apps/MetaWatch/thumbnail.png index: / store_app_id: metawatch main: metawatch is_uncontrolled: false tips: before_install: en_us: | **Self-contained — no MetaCore required.** MetaWatch ships its own discovery, transport and meta-core. Just install and open the dashboard. **Authentication:** The UI is protected by Yundera's built-in Authelia via OIDC — sign in with your existing Yundera account on first open. **Federation ports (optional).** meta-search and meta-share host-publish their libp2p listeners on TCP **4004** and **4003** so the public IPFS kad-DHT and other nodes can dial in. Open them inbound if you want this client to be reachable across networks; same-host discovery (e.g. a MetaGateway on the same box) works over mDNS without them. **Content comes from gateways.** To see anything, at least one MetaGateway (or another MetaShare archivist) must be reachable on the swarm. Install **MetaGateway** for the torznab/Prowlarr + Tribler feed. **After installation:** 1. Open `https://metawatch-$APP_DOMAIN/` 2. Sign in via the Authelia portal 3. Browse the rows or search; results stream in as gateways respond # 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/metawatch/metacore user: $PUID group: $PGID - path: /DATA/AppData/metawatch/search-data user: $PUID group: $PGID - path: /DATA/AppData/metawatch/metashare-config user: $PUID group: $PGID - path: /DATA/AppData/metawatch/share-data user: $PUID group: $PGID - path: /DATA/AppData/metawatch/share-ipfs-state user: $PUID group: $PGID - path: /DATA/AppData/metawatch/data user: $PUID group: $PGID # Same string as the caddy_0 label, so the tile link and the route agree. webui-host: metawatch-${domain} webui-path: "/"