Files
serverAppConfigs/shared/services.yml
T
Frank d4ce76b68b refact(comments): state the mechanism, not the incident
Every comment that invoked a 'law' (50 word-boundary sites across 28
tracked files) now states what is going on and why, in terms that
transfer to any project: gate twins drift until one side greenlights
what the other refuses; a tag re-points while a digest pins; a
restrictive engine behavior belongs to the runtime that imposed it and
must be re-tested, not inherited; a speculative head is tensors in the
file, not a flag; snapshot links dangle where the container resolves
them; exit 0 is a clean stop to systemd, so a self-exiting daemon on
on-failure goes dark reporting success. Event narratives and dates are
gone from these comments; the mechanics carry the argument.

Behavior-identical: bash -n, py_compile, yaml parse, full bats suite
408/408 green. One symbol rename rides along (stack script local
scale_law -> scale_contract, with its log text).
2026-10-14 13:36:40 -07:00

599 lines
20 KiB
YAML

# =============================================================================
# services.yml — THE service inventory.
#
# One entry per deployable service, in canonical deploy order (dependencies
# before consumers, the LLM fleet last). Four hand-maintained lists used to
# agree with each other by discipline alone; every one of them now READS this
# file:
#
# scripts/deploy-plan.py which services a changed path selects
# scripts/deploy-remote.sh which services a target accepts (allow-list)
# scripts/test-changed.sh which Bats seams a changed path must re-run
# scripts/service-map.py renderers + drift gates (`check`)
#
# `service-map.py check` is the referee: every quadlet unit file and every
# root unit in the tree is claimed by exactly one entry, every repo file is
# owned by a service or listed in `unowned:` with a reason, every seam exists,
# the rendered Pi-hole hosts file equals the render, and Prometheus' scrape
# targets plus the homepage probes equal the `scrape:` / `probe:` facts.
#
# --- fields ------------------------------------------------------------------
# name the deployer's service word == the systemd unit name for
# quadlet / root-unit / vm-quadlet kinds
# hosts targets that accept it (the deploy-remote.sh allow-list)
# kind quadlet rootless user unit from a .container file
# root-unit systemd system unit symlinked out of scripts/
# vm-quadlet quadlet inside the FrankBook-Pro podman machine
# host a host-level script/timer, no container
# deployed_by this service deploys as part of another deployable's handler
# (the trio/pair shape). It has no dirs/files of its own; the
# owner names the deployable whose handler installs it.
# unit_dir repo dir holding <unit>.container — a scalar, or a per-host map
# when one service carries its unit on two hosts (node-exporter)
# units container names this deployable owns (netns coupling is derived
# from the `Network=` lines in those files, never declared here)
# dirs repo dirs whose every file selects this service
# files exact repo files that select this service (root-unit trios,
# vendored templates, host scripts installed by this handler)
# retires files whose DELETION selects this service (the unit the
# handler must remove before it can claim the ports)
# only Dankserver's media stack still has one)
# health the deployer's readiness URL; `wait` its 2 s poll budget
# scrape Prometheus jobs: {job, target, metrics_path?, labels?}
# dns names this service answers at (rendered into the Pi-hole
# addn-hosts; every vhost resolves at the Dankclaw Caddy edge)
# probe host:port the homepage checker dials (per-host map if on 2 hosts)
# tests the Bats seams a change to this service must re-run
# =============================================================================
hosts:
dankclaw:
address: 192.168.0.143
# Caddy is consolidated on Dankclaw: it fronts every service on every
# host, so every DNS name in the fleet resolves here.
dns_edge: 192.168.0.143
dankserver:
address: 192.168.0.235
frankbook-pro:
address: 192.168.0.112
# Repo files no service owns. Each entry is a fact about the tree, not a
# fallback: the census gate fails on anything unowned that is NOT listed, so
# retirement work shrinks this list and a new stray file cannot slip in.
unowned:
shared/services.yml: the inventory this tool reads
scripts: repo tooling (build, fetch, bench, deploy, test); the deployers'
files are claimed by name in the service entries
autoresearch.sh: bench scratch at the repo root (retirement pending)
echo_prompt.txt: bench fixture at the repo root (retirement pending)
echo_request.json: bench fixture at the repo root (retirement pending)
prose_prompt.txt: bench fixture at the repo root (retirement pending)
prose_request.json: bench fixture at the repo root (retirement pending)
dankclaw/apply_qwen21_patch.py: retired patch script (its target runtime is gone)
dankclaw/comfyui-start.sh: superseded by the comfyui quadlet + Containerfile
dankclaw/udev: host udev rules, applied by manual sudo install + udevadm trigger
tests: the seams themselves
report: frozen point-in-time audit reports
old: retired scripts, reference only
.githooks: the commit hooks that run the seams
services:
# --- Search stack: one netns family, gluetun owns it ---------------------
- name: gluetun
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/searxng/quadlet
units: [gluetun]
tests: [tests/searxng_quadlet.bats]
# The tunnel's WireGuard bind — the homepage card for the tunnel itself.
probe: 127.0.0.1:51820
- name: searxng-valkey
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/searxng/quadlet
units: [searxng-valkey]
# An app-level dependency, not a network one: the deploy handler
# restarts SearXNG after its cache, so the cache's files select both.
dependents: [searxng]
tests: [tests/searxng_quadlet.bats]
probe: 127.0.0.1:6379
- name: searxng
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/searxng/quadlet
units: [searxng]
dirs: [shared/searxng]
tests: [tests/searxng_limiter.bats, tests/searxng_quadlet.bats]
scrape:
- job: services
target: 192.168.0.143:8081
metrics_path: /
dns: [search.franksdank.cloud]
probe: 127.0.0.1:8081
- name: marreta
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/marreta/quadlet
units: [marreta, marreta-browser]
tests: [tests/marreta_quadlet.bats]
dns: [marreta.franksdank.cloud]
probe: 127.0.0.1:8082
# --- DNS ------------------------------------------------------------------
- name: pihole
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/pihole/quadlet
units: [pihole]
dirs: [dankclaw/pihole]
tests: [tests/pihole.bats, tests/docs_completeness.bats]
dns: [pihole.franksdank.cloud]
probe: 192.168.0.143:8083
# --- Chat front end -------------------------------------------------------
- name: open-webui
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/open-webui/quadlet
units: [open-webui]
tests: [tests/open_webui_quadlet.bats, tests/caddyfile.bats]
scrape:
- job: services
target: 192.168.0.143:3005
metrics_path: /
dns: [ai.franksdank.cloud]
probe: 127.0.0.1:3005
# --- Git hosting ----------------------------------------------------------
- name: gitea-postgres
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/gitea/quadlet
units: [gitea-postgres]
deployed_by: gitea-server
tests: [tests/caddyfile.bats, tests/docs_completeness.bats]
probe: 127.0.0.1:5432
- name: gitea-server
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/gitea/quadlet
units: [gitea-server]
tests: [tests/caddyfile.bats, tests/docs_completeness.bats]
scrape:
- job: gitea
target: 192.168.0.143:3000
metrics_path: /metrics
dns: [gitea.franksdank.cloud]
probe: 127.0.0.1:3000
# --- Image generation -----------------------------------------------------
- name: comfyui
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/comfyui/quadlet
units: [comfyui]
dirs: [dankclaw/comfyui]
files: [dankclaw/Containerfile.comfyui]
health: http://127.0.0.1:8188/
wait: 300
tests: [tests/comfyui_rocm.bats, tests/docs_completeness.bats]
scrape:
- job: comfyui
target: comfyui:8188
metrics_path: /
dns: [comfyui.franksdank.cloud]
probe: 127.0.0.1:8188
# --- Requests -------------------------------------------------------------
- name: seerr
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/seerr/quadlet
units: [seerr]
tests: [tests/seerr_quadlet.bats, tests/deployment_flow.bats]
dns: [seerr.franksdank.cloud]
probe: 127.0.0.1:5055
# --- Agent stack: one handler, four containers ----------------------------
- name: hermes
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/hermes/quadlet
units: [hermes, hermes-dashboard, hermes-webui, chrome-devtools-protocol]
dirs: [dankclaw/hermes]
tests: [tests/hermes_webui.bats, tests/docs_completeness.bats]
dns: [hermes.franksdank.cloud]
probe: 127.0.0.1:8787
- name: hermes-dashboard
hosts: [dankclaw]
kind: quadlet
deployed_by: hermes
tests: [tests/hermes_webui.bats, tests/docs_completeness.bats]
dns: [hermesadmin.franksdank.cloud]
probe: 192.168.0.143:9119
- name: hermes-webui
hosts: [dankclaw]
kind: quadlet
deployed_by: hermes
tests: [tests/hermes_webui.bats, tests/docs_completeness.bats]
# --- Observability --------------------------------------------------------
- name: prometheus
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/observability/quadlet
units: [prometheus]
dirs: [shared/prometheus]
tests: [tests/observability_quadlet.bats, tests/caddyfile.bats,
tests/docs_completeness.bats]
scrape:
- job: prometheus
target: localhost:9090
dns: [prometheus.franksdank.cloud]
probe: 127.0.0.1:9090
- name: grafana
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/observability/quadlet
units: [grafana]
dirs: [shared/grafana]
tests: [tests/observability_quadlet.bats, tests/caddyfile.bats,
tests/docs_completeness.bats]
scrape:
- job: grafana
target: grafana:3000
metrics_path: /metrics
dns: [grafana.franksdank.cloud]
probe: 127.0.0.1:3002
- name: loki
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/observability/quadlet
units: [loki]
dirs: [dankclaw/loki, shared/loki]
tests: [tests/observability_quadlet.bats, tests/caddyfile.bats,
tests/docs_completeness.bats]
scrape:
- job: loki
target: loki:3100
metrics_path: /metrics
dns: [loki.franksdank.cloud]
probe: 127.0.0.1:3100
- name: alloy
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/observability/quadlet
units: [alloy]
dirs: [dankclaw/alloy, shared/alloy]
tests: [tests/observability_quadlet.bats, tests/caddyfile.bats,
tests/docs_completeness.bats]
probe: 127.0.0.1:12345
- name: node-exporter
# One service on two hosts: the same exporter, its own unit per host.
hosts: [dankclaw, dankserver]
kind: quadlet
unit_dir:
dankclaw: dankclaw/observability/quadlet
dankserver: dankserver/observability/quadlet
units: [node-exporter]
tests: [tests/observability_quadlet.bats, tests/caddyfile.bats,
tests/docs_completeness.bats]
scrape:
- job: node-dankclaw
target: host.containers.internal:9100
- job: node-dankserver
target: 192.168.0.235:9100
probe: 127.0.0.1:9100
- name: alertmanager
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/observability/quadlet
units: [alertmanager]
# The Alertmanager config ships from Prometheus' directory, and the Signal
# relay is a HOST unit this handler installs: a change to either reaches
# the machine only through an alertmanager deploy.
files:
- shared/prometheus/alertmanager.yml
- scripts/signal-relay.py
- scripts/signal-relay.service
tests: [tests/observability_quadlet.bats, tests/caddyfile.bats,
tests/docs_completeness.bats]
probe: 127.0.0.1:9093
# --- Identity -------------------------------------------------------------
- name: authentik-postgres
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/authentik/quadlet
units: [authentik-postgres]
deployed_by: authentik-server
tests: [tests/authentik.bats]
- name: authentik-server
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/authentik/quadlet
units: [authentik-server]
tests: [tests/authentik.bats]
dns: [auth.franksdank.cloud]
- name: authentik-worker
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/authentik/quadlet
units: [authentik-worker]
deployed_by: authentik-server
tests: [tests/authentik.bats]
- name: authentik-outpost
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/authentik/quadlet
units: [authentik-outpost]
tests: [tests/authentik.bats]
- name: authentik-outpost-ldap
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/authentik/quadlet
units: [authentik-outpost-ldap]
tests: [tests/authentik.bats]
# --- The auth chain's edge, then the edge itself --------------------------
- name: anubis
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/anubis/quadlet
units: [anubis]
dirs: [dankclaw/anubis]
tests: [tests/anubis.bats, tests/caddyfile.bats, tests/homepage_caddy.bats]
- name: caddy
hosts: [dankclaw]
kind: quadlet
unit_dir: dankclaw/caddy/quadlet
units: [caddy]
dirs: [dankclaw/caddy]
files: [dankclaw/Caddyfile]
# Retiring the old split proxy selects Caddy: it must remove the installed
# unit and container before it can claim 80/443.
retires: [dankclaw/gitea/quadlet/gitea-caddy.container]
tests: [tests/caddy_quadlet.bats, tests/caddyfile.bats,
tests/homepage_caddy.bats, tests/docs_completeness.bats]
dns:
- franksdank.cloud
- www.franksdank.cloud
- dankclaw.franksdank.cloud
- dankserver.franksdank.cloud
probe: 127.0.0.1:80
- name: homepage
hosts: [dankclaw]
kind: host
dirs: [shared/homepage]
files:
- scripts/homepage-status.py
- scripts/homepage-status.service
- scripts/homepage-status.timer
tests: [tests/homepage_caddy.bats, tests/homepage_config.bats,
tests/homepage_deploy.bats, tests/homepage_pages.bats,
tests/homepage_checker.bats]
# --- Dankserver media stack ----------------------------------------------
# The romm trio is one handler and one directory: every file in it selects
# all three, exactly as the deployer restarts all three.
- name: romm-mariadb
hosts: [dankserver]
kind: quadlet
unit_dir: dankserver/romm/quadlet
units: [romm-mariadb]
deployed_by: romm
dirs: [dankserver/romm]
tests: [tests/dankserver_quadlet.bats, tests/deployment_flow.bats]
probe: 192.168.0.235:3306
- name: romm-redis
hosts: [dankserver]
kind: quadlet
unit_dir: dankserver/romm/quadlet
units: [romm-redis]
deployed_by: romm
dirs: [dankserver/romm]
tests: [tests/dankserver_quadlet.bats, tests/deployment_flow.bats]
- name: romm
hosts: [dankserver]
kind: quadlet
unit_dir: dankserver/romm/quadlet
units: [romm]
dirs: [dankserver/romm]
tests: [tests/dankserver_quadlet.bats, tests/deployment_flow.bats]
dns: [romm.franksdank.cloud]
probe: 192.168.0.235:8085
- name: copyparty
hosts: [dankserver]
kind: quadlet
unit_dir: dankserver/copyparty/quadlet
units: [copyparty]
tests: [tests/dankserver_quadlet.bats, tests/deployment_flow.bats]
dns: [files.franksdank.cloud]
probe: 192.168.0.235:3923
- name: jellyfin
hosts: [dankserver]
kind: quadlet
unit_dir: dankserver/jellyfin/quadlet
units: [jellyfin]
tests: [tests/dankserver_quadlet.bats, tests/deployment_flow.bats]
dns: [jelly.franksdank.cloud]
probe: 192.168.0.235:8096
- name: calibre-web
hosts: [dankserver]
kind: quadlet
unit_dir: dankserver/calibre-web/quadlet
units: [calibre-web]
tests: [tests/dankserver_quadlet.bats, tests/deployment_flow.bats]
dns: [books.franksdank.cloud]
probe: 192.168.0.235:8083
- name: calibre-server
hosts: [dankserver]
kind: quadlet
unit_dir: dankserver/calibre-server/quadlet
units: [calibre-server]
tests: [tests/dankserver_quadlet.bats, tests/deployment_flow.bats]
- name: plex
hosts: [dankserver]
kind: quadlet
unit_dir: dankserver/plex/quadlet
units: [plex]
tests: [tests/dankserver_quadlet.bats, tests/deployment_flow.bats]
dns: [plex.franksdank.cloud, plex.franksdank.casa]
probe: 192.168.0.235:32400
- name: tautulli
hosts: [dankserver]
kind: quadlet
unit_dir: dankserver/tautulli/quadlet
units: [tautulli]
tests: [tests/dankserver_quadlet.bats, tests/deployment_flow.bats]
probe: 192.168.0.235:8181
# --- FrankBook-Pro: the *arr stack inside the podman machine --------------
# Every container rides gluetun's netns, and that coupling is DERIVED: each
# arr's unit file says `Network=gluetun.container`, so a gluetun unit change
# selects the whole stack with no declaration here.
- name: gluetun
hosts: [frankbook-pro]
kind: vm-quadlet
unit_dir: frankbook-pro/gluetun/quadlet
units: [gluetun]
tests: [tests/frankbook_quadlet.bats, tests/deployment_flow.bats]
- name: prowlarr
hosts: [frankbook-pro]
kind: vm-quadlet
unit_dir: frankbook-pro/prowlarr/quadlet
units: [prowlarr]
tests: [tests/frankbook_quadlet.bats, tests/deployment_flow.bats]
dns: [prowlarr.franksdank.cloud]
probe: 192.168.0.112:9696
- name: qbittorrent
hosts: [frankbook-pro]
kind: vm-quadlet
unit_dir: frankbook-pro/qbittorrent/quadlet
units: [qbittorrent]
tests: [tests/frankbook_quadlet.bats, tests/deployment_flow.bats]
dns: [qbittorrent.franksdank.cloud]
probe: 192.168.0.112:8080
- name: radarr
hosts: [frankbook-pro]
kind: vm-quadlet
unit_dir: frankbook-pro/radarr/quadlet
units: [radarr]
tests: [tests/frankbook_quadlet.bats, tests/deployment_flow.bats]
dns: [radarr.franksdank.cloud]
probe: 192.168.0.112:7878
- name: sonarr
hosts: [frankbook-pro]
kind: vm-quadlet
unit_dir: frankbook-pro/sonarr/quadlet
units: [sonarr]
tests: [tests/frankbook_quadlet.bats, tests/deployment_flow.bats]
probe: 192.168.0.112:8989
- name: lidarr
hosts: [frankbook-pro]
kind: vm-quadlet
unit_dir: frankbook-pro/lidarr/quadlet
units: [lidarr]
tests: [tests/frankbook_quadlet.bats, tests/deployment_flow.bats]
dns: [lidarr.franksdank.cloud]
probe: 192.168.0.112:8686
- name: readarr
hosts: [frankbook-pro]
kind: vm-quadlet
unit_dir: frankbook-pro/readarr/quadlet
units: [readarr]
tests: [tests/frankbook_quadlet.bats, tests/deployment_flow.bats]
dns: [readarr.franksdank.cloud]
probe: 192.168.0.112:8787
# --- The LLM fleet (last: never implied by a generic change) --------------
- name: frank-dragoon
hosts: [dankclaw]
kind: root-unit
files:
- scripts/frank-dragoon.env
- scripts/frank-dragoon.service
- scripts/serve-frank-dragoon.sh
health: http://127.0.0.1:7770/health
wait: 1200
tests: [tests/frank_dragoon_unit.bats, tests/llm_units.bats,
tests/llm_deploy_gates.bats, tests/llm_secrets.bats,
tests/scripts_syntax.bats, tests/docs_completeness.bats]
scrape:
- job: frank-dragoon
target: host.containers.internal:7770
metrics_path: /metrics
labels: {service: frank-dragoon}
probe: 127.0.0.1:7770
- name: frank-zealot
hosts: [dankclaw]
kind: root-unit
files:
- scripts/frank-zealot.env
- scripts/frank-zealot.service
- scripts/serve-frank-zealot.sh
health: http://127.0.0.1:7771/health
wait: 900
tests: [tests/frank_zealot_unit.bats, tests/llm_units.bats,
tests/llm_deploy_gates.bats, tests/llm_secrets.bats,
tests/scripts_syntax.bats]
scrape:
- job: frank-zealot
target: host.containers.internal:7771
metrics_path: /metrics
labels: {service: frank-zealot}
probe: 127.0.0.1:7771
- name: frank-archon
hosts: [dankserver]
kind: root-unit
files:
- scripts/frank-archon.env
- scripts/frank-archon.service
- scripts/serve-frank-archon.sh
# The vendored chat template the engine renders with.
- scripts/qwen38-froggeric-v22.5.jinja
health: http://127.0.0.1:7770/health
wait: 900
tests: [tests/frank_archon_unit.bats, tests/llm_units.bats,
tests/llm_deploy_gates.bats, tests/llm_secrets.bats,
tests/scripts_syntax.bats]
scrape:
- job: frank-archon
target: 192.168.0.235:7770
metrics_path: /metrics
labels: {service: frank-archon}
probe: 192.168.0.235:7770