60 lines
3.3 KiB
Markdown
60 lines
3.3 KiB
Markdown
# MetaRead (shared backend) — Rationale
|
|
|
|
## What deviation / exception is being requested
|
|
|
|
1. `metaread-app` is **publicly routed with no AppShield sidecar**. It carries the
|
|
`caddy_*` labels itself.
|
|
2. `metaread-search` runs `user: 0:0` and publishes host port `4006/tcp` (libp2p).
|
|
3. The compose top-level `name:` is `metaread`, the same project id as the self-contained
|
|
`MetaRead` app in this store.
|
|
|
|
## Why it is necessary
|
|
|
|
1. meta-read has its **own** authentication: a secp256k1 keypair proven by signing a
|
|
challenge, with invite-only signup. Its router gates deny-by-default: a route not in
|
|
`open_routes()` answers 401. The open set is `/health`, `/api/config`, the
|
|
challenge/token pair, `/api/invite/check/:token` and `/api/image/:cid`. That is the "app's
|
|
own built-in auth" alternative the security checklist accepts. AppShield in front added a
|
|
second login and an OIDC redirect on every cover fetch, because `<img src>` cannot carry
|
|
an Authorization header. It also left the service worker's offline cache at the mercy of
|
|
a session cookie.
|
|
2. libp2p is not HTTP. Caddy cannot proxy it, and remote peers dial the advertised
|
|
multiaddr directly, so the port must be published and reachable inbound. The app carries
|
|
the reserved `needs-public-ip` tag.
|
|
3. The two MetaRead variants are alternatives, not companions. They share the project id
|
|
and the same `/DATA/AppData/metaread/` paths, so installing one over the other reuses the
|
|
same data. Only one may be installed at a time.
|
|
|
|
## Security mitigations in place
|
|
|
|
- meta-read's signature-challenge login is on by default, and signup is invite-only, so a
|
|
stranger reaching the public hostname cannot create an account.
|
|
- The `metaread-search` dashboard *is* behind an AppShield sidecar
|
|
(`ghcr.io/yundera/appshield:2.0.9`) with Authelia SSO; only the reader itself uses its own
|
|
gate.
|
|
- `metaread-app` runs unprivileged as `1000:1000` with a 512 MB hard memory cap and swap
|
|
disabled.
|
|
- The only published port is raw libp2p TCP; no HTTP port is published to the host.
|
|
- No user directories are mounted: no `/DATA/Documents`, `/DATA/Downloads`, `/DATA/Media`
|
|
or `/DATA/Gallery`.
|
|
- `cpu_shares` on every service; image tags pinned.
|
|
|
|
## Alternatives considered and rejected
|
|
|
|
- **AppShield in front of `metaread-app`.** Two identities for one user, plus the cover and
|
|
service-worker costs above.
|
|
- **Bundle a private MetaCore/MetaShare.** That is the self-contained `MetaRead` app. On a
|
|
box that already runs them, it doubles the memory footprint. It also gives the reader an
|
|
identity, a User Data Layer and a catalogue separate from MetaWatch's on the same box.
|
|
- **A distinct project id so both variants coexist.** They would fight over the same public
|
|
hostnames, the same `/DATA/AppData/metaread/` paths and host port 4006.
|
|
|
|
## Data protection
|
|
|
|
My List, Continue Reading, the session key and the invite book persist in
|
|
`/DATA/AppData/metaread/data`. The recovered cover blockstore and language prefs persist in
|
|
`/DATA/AppData/metaread/search-data`. Both are declared in `x-compose-app.folders` and
|
|
archived by Maison on uninstall. Because both MetaRead variants use those paths, switching
|
|
between them keeps the user's data. The MetaCore and MetaShare state this variant reads
|
|
belongs to those apps and is archived with them.
|