Files
serverAppConfigs/serverAppConfigs-docs/Architecture/Service Inventory.md
T
Frank bdead570e2 feat(inventory): one service inventory drives planner, allow-lists, and seams
rank 3. shared/services.yml is the single source of truth for every
deployable service (hosts, unit dirs, units, path claims, tests, scrape/
dns/probe facts, aliases, stacks); scripts/service-map.py is its only
reader (hosts/services/allows/plan/seams/owner/units/stack/get/render/
check). deploy-plan.py becomes a thin CLI wrapper; both --service
allow-lists (deploy.sh, deploy-remote.sh) and the changed-test mapper are
the inventory. Selection semantics: host scoping (a host's tree selects
only its own services), the quadlet netns/network coupling graph resolved
across directories (gluetun's riders live in their own dirs), stacks, and
canonical restart order.

Against the pinned old-planner fixture: 921 raw deltas reduced to 17, all
intentional (anubis now maps; cross-host paths select nothing; the retired
qwen-sharp template plans nothing and the live froggeric template plans
frank-archon; dankserver/romm/romm.container, missed by the old filename
rule, plans the romm trio). The fixture is regenerated as the new pin.

Integrity gates: schema, unit claims, quadlet-file ownership, path-glob
liveness, Pi-hole hosts / Prometheus jobs / DNS / homepage probes drift.
tests/service_inventory.bats pins all of it plus the plan fixture and the
deploy-plan CLI. Allow-list grep seams in hermes_webui/observability_quadlet
become behavioral allows calls.
2026-10-13 22:35:07 -07:00

5.2 KiB
Raw Blame History

type, title, description, tags, timestamp
type title description tags timestamp
reference Service Inventory shared/services.yml is the one inventory of deployable services; scripts/service-map.py is its only reader. The planner, both deploy allow-lists, the changed-test seam mapper, the Pi-hole hosts render, the Prometheus scrape facts, and the homepage probes all derive from it.
deployment
inventory
planner
quadlet
prometheus
pihole
homepage
2026-10-13T00:00:00-07:00

Service Inventory

Before 2026-10-13 the same facts lived in four hand-maintained lists — the planner's path rules (deploy-plan.py), the --service allow-list in deploy.sh, the near-copy in deploy-remote.sh, and the changed-test mapper (test-changed.sh) — plus per-fact copies in prometheus.yml, addn-hosts, and services.json. They drifted on every swap (the planner still mapped the retired qwen-sharp template to frank-dragoon; the allow-list still listed the *arr stack under Dankserver after it moved to FrankBook-Pro). Now there is one inventory and one reader:

  • shared/services.yml — the data: every deployable service, its hosts, unit dir(s), unit files, path claims, tests, scrape/DNS/probe facts, aliases.
  • scripts/service-map.py — the only reader. CLI subcommands: hosts, services, allows, plan, seams, owner, units, stack, get, render, check.

1. Consumers

Consumer Call
scripts/deploy-plan.py thin wrapper → plan --host TARGET [paths...] (keeps the --repo/--base/--head and --paths CLIs)
scripts/deploy.sh (--service gate) allows --host T --service S
scripts/deploy-remote.sh (service_is_allowed) same
scripts/test-changed.sh seams [paths...] — a service's declared tests are its seams
dankclaw/pihole/addn-hosts render pihole-hosts — the tracked file is a rendered artifact
Prometheus scrape jobs, LAN DNS, homepage probes check compares the tracked configs against the inventory's scrape/dns/probe fields (drift gate)

Selection rules the engine encodes (see tests/fixtures/service-plan-golden.tsv, the pinned characterization of every tracked path × every target):

  • Host scoping — a host's top-level directory can only select that host's services. frankbook-pro/** plans nothing on Dankserver (the old planner had a stale Dankserver *arr list).
  • Quadlet coupling graph — Network=x.container rides x's netns; Network=y.network joins a shared network. Changing the parent (or the network file) selects every rider transitively, in their own directories (dankclaw/searxng/quadlet/gluetun.container → gluetun + searxng + marreta; frankbook-pro/gluetun/quadlet/gluetun.container → the whole arr stack).
  • Stacks — a service's stack members deploy and restart as one handler (romm trio, hermes trio).
  • Retirement — removing a service from services.yml removes it from the planner, both allow-lists, and the seam mapper in one data edit; a pending deploy-state entry for it is auto-triaged (see Deploy State Triage).

2. Integrity gates (check)

python3 scripts/service-map.py check runs, and tests/service_inventory.bats pins:

  • schema — required fields per entry, known keys only;
  • units — every unit file named by an entry exists under its unit_dir on the right host; each unit name is claimed by exactly one service per host;
  • ownership — every tracked quadlet .container/.network file is claimed by exactly one service (trash excluded);
  • paths — every claimed files/dirs/retires glob matches ≥1 tracked file;
  • compose — the container names in each host's compose.yaml have exactly one quadlet owner (the Quadlet Lifecycle two-owners trap);
  • drift — addn-hosts equals its render; the Prometheus job set, the DNS names, and the homepage probe ports equal the inventory's fields.

3. Adding, moving, retiring a service

  • Add: append an entry (name, hosts, kind, unit_dir, units, paths, tests, …), add the quadlet file, run check — plan/allows/seams pick it up with zero script edits.
  • Move between hosts: edit hosts (and per-host unit_dir/probe maps).
  • Retire: delete the entry, move files to .AGENTTRASH/ (see Testing Policy for the seam the mapper applies to trash paths).

Field notes: alias carries deployable names that differ from the service name (calibre-web deploys the unit calibre-server); probe for multi-host services is a {host: port} map; dependents names services whose config embeds a reference to this one (config-only coupling — a .env edit selects both, a unit edit selects the graph closure).

4. Known cutover deltas (2026-10-13)

The 17 deltas against the old planner are all in these classes: anubis paths now select anubis (the old planner had no rule at all); cross-host paths select nothing; scripts/qwen-sharp-v22.4.0.jinja (retired template) plans nothing while scripts/qwen38-froggeric-v22.5.jinja (the live frank-archon template, previously unmapped) plans frank-archon; dankserver/romm/romm.container (top-level quadlet dir the old unit-file rule missed) now plans the romm trio.


Related: Deployment Flow, Testing Policy, Quadlet Lifecycle, Container Network Namespaces, Deploy State Triage