marreta: content-cleaning proxy on Dankclaw, gluetun netns, guard-chained

Deploy manualdousuario/marreta (Laravel / serversideup php:8.4-fpm-nginx)
as a rootless Quadlet pair (marreta + marreta-browser/Lightpanda), both
joining gluetun's netns so all fetch egress exits the same ProtonVPN
tunnel as SearXNG.

- marreta relocated to NGINX_HTTP_PORT=8085 (netns already has searxng on
  8080); gluetun publishes 8082:8085; Caddy dials 192.168.0.143:8082.
- Guard chain: marreta.franksdank.cloud -> Anubis :8923 -> Authentik ->
  post-auth :8017 @marreta -> 8082. Access = any authenticated user.
- Secrets seeded to /home/dank/Containers/marreta/env (0600) from
  MARRETA_APP_KEY / MARRETA_ADMIN_EMAIL / MARRETA_ADMIN_PASSWORD.
- deploy-plan.py: marreta joins the gluetun netns family.
- New seam tests/marreta_quadlet.bats (17 contracts); scoping test updated
  for the widened netns family. Doc: Dankclaw/Marreta.md + index + log.
This commit is contained in:
Frank
2026-09-12 10:09:12 -07:00
parent 9530bffad6
commit 73e46a1d85
13 changed files with 547 additions and 6 deletions
+13 -4
View File
@@ -23,6 +23,7 @@ SERVICE_ORDER = (
"gluetun",
"searxng-valkey",
"searxng",
"marreta",
"pihole",
"open-webui",
"gitea-postgres",
@@ -67,6 +68,7 @@ DANKCLAW_SERVICES = {
"gluetun",
"searxng-valkey",
"searxng",
"marreta",
"pihole",
"open-webui",
"gitea-postgres",
@@ -279,15 +281,22 @@ def services_for_path(path: str, target: str) -> set[str]:
if len(parts) >= 4 and parts[2] == "quadlet":
name = parts[3]
if name == "gluetun.container":
# SearXNG shares Gluetun's network namespace. A Gluetun
# recreation requires both units to be restarted.
_add(services, "gluetun", "searxng")
# SearXNG and Marreta (+ its browser companion) share
# Gluetun's network namespace. A Gluetun recreation
# tears the netns down, so every sharer must restart.
_add(services, "gluetun", "searxng", "marreta")
elif name == "searxng.container":
_add(services, "searxng")
elif name == "searxng-valkey.container":
_add(services, "searxng-valkey", "searxng")
elif name == "searxng.network":
_add(services, "gluetun", "searxng-valkey", "searxng")
_add(services, "gluetun", "searxng-valkey", "searxng", "marreta")
return services
if len(parts) >= 2 and parts[1] == "marreta":
# One deployable owns both the app and its browser companion;
# both live in gluetun's netns. A change to either unit selects
# the marreta service, which restarts browser-then-app.
_add(services, "marreta")
return services
if len(parts) >= 2 and parts[1] == "pihole":
_add(services, "pihole")
+34 -1
View File
@@ -89,7 +89,7 @@ service_is_allowed() {
dankclaw:loki|dankclaw:alloy|dankclaw:node-exporter|\
dankclaw:authentik-postgres|dankclaw:authentik-server|\
dankclaw:authentik-worker|dankclaw:authentik-outpost|dankclaw:authentik-outpost-ldap|dankclaw:anubis|\
dankclaw:caddy|dankclaw:homepage|dankclaw:seerr|\
dankclaw:caddy|dankclaw:homepage|dankclaw:seerr|dankclaw:marreta|\
dankclaw:frank-large|\
dankserver:romm-mariadb|dankserver:romm-redis|dankserver:romm|\
dankserver:copyparty|dankserver:jellyfin|dankserver:calibre-web|dankserver:plex|dankserver:tautulli|\
@@ -508,6 +508,38 @@ deploy_searxng_valkey() {
systemctl --user restart searxng-valkey.service searxng.service
}
# Marreta's secrets are seeded into a per-service env file under the deploy
# user's home (rootless-readable), following the grafana convention rather
# than the /opt/searxng one, so the rootless unit can read it without any
# ownership gymnastics. Only the three MARRETA_* values are copied; the
# quadlet maps them to the app's APP_KEY / ADMIN_EMAIL / ADMIN_PASSWORD.
seed_marreta_env() {
local dest=/home/dank/Containers/marreta/env
mkdir -p /home/dank/Containers/marreta
if [[ ! -f "$dest" ]]; then
awk -F= '/^MARRETA_APP_KEY=/ || /^MARRETA_ADMIN_EMAIL=/ || /^MARRETA_ADMIN_PASSWORD=/ { print }' \
dankclaw/.env > "$dest"
chmod 600 "$dest"
fi
local var
for var in MARRETA_APP_KEY MARRETA_ADMIN_EMAIL MARRETA_ADMIN_PASSWORD; do
grep -q "^${var}=." "$dest" \
|| { echo "marreta env missing ${var}; set it in dankclaw/.env" >&2; return 1; }
done
}
deploy_marreta() {
seed_marreta_env
install_quadlet marreta dankclaw/marreta/quadlet marreta
install_quadlet marreta-browser dankclaw/marreta/quadlet marreta-browser
systemctl --user daemon-reload
# Both containers share gluetun's netns; the tunnel must be up first.
systemctl --user start gluetun.service
# Browser before app: marreta drives the CDP endpoint on loopback.
systemctl --user restart marreta-browser.service
systemctl --user restart marreta.service
}
deploy_open_webui() {
install_quadlet open-webui dankclaw/open-webui/quadlet open-webui
mkdir -p /home/dank/Containers/open-webui/data
@@ -1153,6 +1185,7 @@ deploy_service() {
;;
anubis) deploy_anubis ;;
seerr) deploy_seerr ;;
marreta) deploy_marreta ;;
frank-large) deploy_llm_large ;;
frank-small) deploy_llm_small ;;
hermes|hermes-dashboard|hermes-webui) deploy_hermes ;;