23 apps (MetaCore, the library/network/client tiers and 13 MetaFeeder*) move out of the general-purpose Worph/AppStore into their own store so the MetaMesh family releases on its own cadence. - asset URLs repointed to cdn.jsdelivr.net/gh/Worph/MetaAppStore@main - store scaffolding added (category/recommend/featured lists, licence, contributing, gitattributes) - psd-source/meta-icons-v2 icon generator follows the apps MetaMCP stays in Worph/AppStore: it is third-party metatool-ai software, not part of MetaMesh.
60 lines
3.3 KiB
Markdown
60 lines
3.3 KiB
Markdown
# MetaWatch — Rationale
|
|
|
|
## What deviation / exception is being requested
|
|
|
|
1. `metawatch-core`, `metawatch-search` and `metawatch-share` run `user: 0:0`, and
|
|
`metawatch-core` runs `privileged: true`.
|
|
2. The stack publishes two host ports: `4003/tcp` and `4004/tcp` (libp2p).
|
|
3. The app exposes **three** public hostnames — `metawatch-`, `metawatchshare-` and
|
|
`metawatchsearch-` — each with its own AppShield sidecar, so two sidecar
|
|
`container_name`s differ from the compose top-level `name:`.
|
|
|
|
## Why it is necessary
|
|
|
|
1. `metawatch-core` is a bundled MetaCore: rclone remote mounts need a mount namespace
|
|
(`SYS_ADMIN` + `/dev/fuse` + an unconfined AppArmor profile), plus nginx and Redis.
|
|
`metawatch-search` and `metawatch-share` write service registrations into the root-owned
|
|
`/meta-core/services/`.
|
|
2. libp2p is **not HTTP** — Caddy cannot proxy it, and remote peers resolve this node's
|
|
public multiaddr from the kad-DHT and dial it directly. Without inbound reachability the
|
|
node never serves bytes to the swarm. The app therefore carries the reserved
|
|
`needs-public-ip` tag.
|
|
3. Each hostname is a distinct UI, and `auth-registrar` derives an app's OIDC `client_id`
|
|
from the sidecar's container name via PTR lookup, so three routes need three stable,
|
|
distinct names. `x-casaos.main` points at `metawatch`, the sidecar carrying the primary
|
|
route.
|
|
|
|
## Security mitigations in place
|
|
|
|
- Every publicly-routed hostname terminates at its own AppShield sidecar
|
|
(`ghcr.io/yundera/appshield:2.0.9`) behind the PCS's Authelia SSO. Each sidecar sets
|
|
`hostname:` equal to its `container_name` so `auth-registrar` can attest it via PTR and
|
|
accept its redirect URIs; the names are app-prefixed, so they cannot collide with another
|
|
app's services in the shared `pcs` DNS namespace.
|
|
- The backends carry no Caddy labels and publish **no HTTP port** to the host. The only
|
|
published ports are raw libp2p TCP.
|
|
- libp2p transport is authenticated and encrypted (Noise) and peer identity is a public
|
|
key, so an unauthenticated dial cannot read anything the node has not chosen to publish
|
|
to the swarm.
|
|
- No user directories are mounted — no `/DATA/Documents`, `/DATA/Downloads`, `/DATA/Media`
|
|
or `/DATA/Gallery`.
|
|
- `cpu_shares` on every service; memory-heavy services carry a hard `mem_limit` with
|
|
`memswap_limit` equal to it. All image tags pinned.
|
|
|
|
## Alternatives considered and rejected
|
|
|
|
- **Proxy libp2p through Caddy.** Not possible; the DHT advertises a raw TCP multiaddr.
|
|
- **One sidecar with path-based routing.** The three backends generate absolute URLs and
|
|
set cookies on their own hostnames; path-mounting them breaks both.
|
|
- **Drop `privileged` from `metawatch-core` for a `cap_add` list.** Same container as
|
|
MetaCore; the reduced form was tried and still failed to bring up the supervised daemons.
|
|
- **Use the shared-backend variant instead.** That is `MetaWatchShared`, for a box that
|
|
already runs MetaCore and MetaShare. This app is the self-contained one.
|
|
|
|
## Data protection
|
|
|
|
All state lives under `/DATA/AppData/metawatch/`, declared in `x-compose-app.folders` and
|
|
archived by Maison on uninstall, so an uninstall/reinstall with "keep user data" returns My
|
|
List and Continue Watching intact. No user media directory is mounted into any service;
|
|
bytes are served through the app's own transport peer.
|