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.
5.2 KiB
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. |
|
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.containerrides x's netns;Network=y.networkjoins 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
stackmembers deploy and restart as one handler (romm trio, hermes trio). - Retirement — removing a service from
services.ymlremoves 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_diron the right host; each unit name is claimed by exactly one service per host; - ownership — every tracked quadlet
.container/.networkfile is claimed by exactly one service (trash excluded); - paths — every claimed
files/dirs/retiresglob matches ≥1 tracked file; - compose — the container names in each host's
compose.yamlhave exactly one quadlet owner (the Quadlet Lifecycle two-owners trap); - drift —
addn-hostsequals 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/seamspick it up with zero script edits. - Move between hosts: edit
hosts(and per-hostunit_dir/probemaps). - 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