docs(R5): rewrite knowledge schema + llm-wiki for DB-native model; deprecate root inventory.yaml

Rewrote .agents/domains/knowledge/schema.md and .agents/shared/llm-wiki.md
which described the deleted Python substrate (bin/homelab, oikos/cards/,
oikos/ledger.py, root inventory.yaml, knowledge/sources/, get_page/
search_docs MCP tools). Now reflect ADR 0003: Postgres DB is the single
source of truth for structured data and narrative knowledge; seeds/*.yaml
are bootstrap+DR manifests (content-hashed via seed_versions); archive/
knowledge/ is the frozen legacy wiki; MCP search_knowledge/get_entity_
knowledge replace get_page/search_docs.

Swept substrate refs in .agents/shared/{writing-style,page-templates}.md
and .agents/domains/operations/schema.md: bare inventory.yaml ->
seeds/inventory.yaml; knowledge/sources/ -> archive/knowledge/sources/
(historical); get_changelog/oikos/ledger.py -> DB audit trail / structured
document changelog field; HERMES -> Nomos.

Root inventory.yaml (618-line Python-era file superseded 2026-07-07 by
seeds/inventory.yaml) replaced with a deprecation stub pointing to the seed
and DB. Kept as a stub rather than deleted because AGENTS.md §1/§2 still
point clients at /opt/homelab-context/inventory.yaml; full on-client path
reconciliation deferred to R13.

Flagged export gap: oikos export regenerates seeds/{ontology,inventory,
policy}.yaml but NOT seeds/knowledge.yaml — API-added knowledge lives only
in the DB until hand-edited into the seed.

VERSION 0.7.7 -> 0.7.8. Plan R5 marked done.
This commit is contained in:
2026-07-17 22:36:41 +02:00
parent d2950dd09d
commit a2410cf9c2
8 changed files with 175 additions and 702 deletions

View File

@@ -1,48 +1,93 @@
# Knowledge domain — schema
The knowledge domain is the durable, authoritative current-state documentation of the homelab: one
page per node and per cross-cutting system, synthesized from live state and evidence. It answers
"what exists and how does it work right now."
The knowledge domain is the durable, authoritative current-state documentation of the homelab:
narrative for every node and cross-cutting system, synthesized from live state and evidence. It
answers "what exists and how does it work right now."
It follows the [LLM Wiki layer model](../../shared/llm-wiki.md) and the
[writing-style](../../shared/writing-style.md) and [page-templates](../../shared/page-templates.md)
rules.
## The narrative / substrate split
## Source of truth — the database
The knowledge wiki is **narrative**. It sits alongside a **machine-readable substrate** that it
describes but never contains. The split is load-bearing: several programs read the substrate at
fixed paths, so the wiki reorganization never moves it.
Per ADR 0003, the Postgres database is the single source of truth for all structured data **and**
narrative knowledge. The narrative/substrate split of the Python era is gone: the DB holds both the
structured graph (entities, relationships, status, metrics) and the narrative layer (documents,
investigations, runbooks) in the `knowledge_entities` table.
| Layer | Location | Consumed by |
|-------|----------|-------------|
| Substrate — source of truth | `inventory.yaml` (root) | MCP server, `homelab` CLI, `oikos/` scheduler/drift/relations/gen-topology |
| Substrate — generated host records | `inventory.yaml` (root) | Go `internal/mcp/` server, `bin/homelab`; the single source of truth |
| Substrate — kernel + context cards | `oikos/` (code, `oikos/cards/`, `oikos/state.json`) | MCP `explain`, scheduler |
| Narrative — synthesized wiki | `archive/knowledge/{hosts,containers,vms,infrastructure}/` | humans, agents via MCP `get_page` / `search_docs` |
| Evidence — immutable sources | `knowledge/sources/` (references + investigations) | synthesis into wiki pages |
| Concern | Where it lives | How it gets there |
|---------|----------------|-------------------|
| Knowledge content — documents, investigations, runbooks | `knowledge_entities` table (rows linked to `entities` via `documents` / `about` edges) | Seeded from `seeds/knowledge.yaml` at deploy; mutated at runtime via the API |
| Seed manifest (bootstrap + DR) | `seeds/knowledge.yaml` | Hand-edited or regenerated; ingested idempotently (content-hashed via `seed_versions`) |
| Structured graph — hosts, services, entity types, relationships | `entities`, `relationships`, `entity_types` tables | Seeded from `seeds/{ontology,inventory}.yaml`; mutated via API/MCP |
| Archived narrative wiki (read-only history) | `archive/knowledge/` | Frozen 2026-07-07 when the DB became source of truth |
## Wiki pages
### Seed ingest
- **Node pages** (`archive/knowledge/containers/<id>-<name>.md`, `.../vms/<id>-<name>.md`,
`.../hosts/<name>.md`) follow the container/host template in
[page-templates.md](../../shared/page-templates.md): opening definition, `## At a glance`,
`## Role`, service/port map, storage, auto-deploy, `## Related`, `## Changelog`.
- **Cross-cutting pages** (`archive/knowledge/infrastructure/<topic>.md`) follow the cross-cutting
template: `## Why`, `## Components`, `## How to apply`, `## Gotchas`, `## Related`, `## Changelog`.
- Each `inventory.yaml` host entry carries a `doc_page:` field pointing at its narrative page.
Changing where a page lives means updating that field (read by `bin/homelab`).
`seeds/knowledge.yaml` has three top-level lists — `documents`, `investigations`, `runbooks` — each
entry carrying `slug`, `title`, `content` (markdown), and tags. `internal/knowledge/seed.go`
ingests each entry by:
1. `getOrCreateEntity` — ensures the slug exists in `entities` (type `document` / `investigation` /
`runbook`).
2. `upsertKnowledgeEntity` — writes the markdown body into `knowledge_entities`, keyed by
`content_hash` so re-ingest is a no-op when nothing changed.
3. `createEdge` — links the knowledge entity to its subject(s) via `documents` (for `document`) or
`about` (for `investigation`) edges. Runbooks bind to an `entity_type` via `applies_to_type`
rather than to a single entity.
### Runtime mutation
Agents register or update knowledge through the API, not by editing the seed:
- `POST /api/v1/knowledge/{entity_slug}` — upsert a document/investigation on an entity
(`upsert_knowledge` MCP tool).
- `update_entity_attributes` — merge a discovered fact (IP, version, port) into an entity.
- `create_relationship` — record a discovered edge (`depends-on`, `hosts`, `routes-to`).
> **Export gap.** `oikos export` regenerates `seeds/{ontology,inventory,policy}.yaml` from the DB
> for version control, but **not** `seeds/knowledge.yaml`. Knowledge added via the API today lives
> only in the DB until someone hand-edits the seed. Tracked as a follow-up.
## Knowledge kinds
- **Documents** (`document` entities, linked via `documents` edges) — node and cross-cutting
narrative pages. Carry `at_glance` (structured attributes) and a parsed `changelog`. Follow the
container / cross-cutting templates in [page-templates.md](../../shared/page-templates.md).
- **Investigations** (`investigation` entities, linked via `about` edges) — incident evidence,
written once at incident time. Sections: `## Summary`, `## Timeline`, `## Root cause`,
`## Mitigations applied`, `## Open questions`.
- **Runbooks** (`runbook` entities, bound by `applies_to_type`) — repeatable procedures. Carry
`risk_class` and a JSON-schema-validated `procedure`. **Runbooks also live as `SKILL.md` files
under `.agents/skills/<name>/`** — the DB row is the policy/lifecycle framing, the SKILL.md is
the executable procedure the agent loads. See
[the operations schema](../operations/schema.md).
## The two logs
- The per-page **`## Changelog`** records infrastructure changes and is machine-parsed
(`get_changelog`, the Oikos ledger). Keep the `### YYYY-MM-DD — title` shape.
- **`knowledge/log.md`** is append-only and records *documentation-maintenance* operations only
(restructures, source ingests, lint sweeps): `## [YYYY-MM-DD] <op> | <summary>`. It never
duplicates the Oikos change ledger (`oikos/ledger.py`).
- The per-document **`## Changelog`** records infrastructure changes to that node. Keep the
`### YYYY-MM-DD — title` shape so the parsed `changelog` field stays structured.
- **`archive/knowledge/log.md`** is the append-only record of *documentation-maintenance*
operations on the legacy wiki (restructures, source ingests, lint sweeps):
`## [YYYY-MM-DD] <op> | <summary>`. It is frozen with the rest of `archive/knowledge/`; new
doc-maintenance operations are recorded in the DB audit trail instead.
## Querying knowledge
Use MCP, not grep:
- `search_knowledge(query)` — ILIKE search over documents, investigations, and runbooks in
`knowledge_entities`.
- `get_entity_knowledge(entity_slug)` — every document, investigation, and runbook linked to one
entity, in one call.
- `get_entity(slug)` / `get_relations(entity)` — the structured graph around an entity.
Grep the clone only when MCP is unreachable, and prefer `archive/knowledge/` for historical
narrative (it is not updated when the DB changes).
## Same-session update rule
A change to a node updates every page that references it in the same session — the node page, the
section `README.md` table, the root `README.md`, the Caddy/DNS/ingress pages, the host page, and
`inventory.yaml`. See [page-templates.md](../../shared/page-templates.md#same-session-update-rule).
A change to a node updates the DB in the same session — the entity's attributes, the relationships
that reference it, and any document whose `at_glance` or changelog should reflect the new state. See
[page-templates.md](../../shared/page-templates.md#same-session-update-rule) for the legacy wiki
equivalent (now scoped to `archive/knowledge/` history).

View File

@@ -6,9 +6,10 @@ follows [writing-style](../../shared/writing-style.md); runbooks and plans use t
exception.
Where each kind lives: runbooks are skills under [`.agents/skills/`](../../skills/); operator
reference (command cheatsheet, enrollment, Hermes agent) lives in
[`.agents/operations/`](../../operations/); investigations are sources under
`knowledge/sources/investigations/`; plans stay in the repo-root `plans/` folder (below).
reference (command cheatsheet, enrollment, Nomos agent) lives in
[`.agents/operations/`](../../operations/); investigations are `investigation` entities in the DB
(historically `archive/knowledge/sources/investigations/`); plans stay in the repo-root `plans/`
folder (below).
## Plans always live in `plans/`
@@ -21,7 +22,7 @@ message.** An agent drafting a plan:
3. On completion, moves it to `plans/done/` and updates the index status.
This is the single source for homelab design intent; keeping it in-repo means the plan is
versioned, reviewable, and reachable by MCP `get_page`/`search_docs` like any other doc.
versioned, reviewable, and reachable by MCP `search_knowledge` like any other doc.
## Runbooks
@@ -44,12 +45,14 @@ transition: "<from> -> <to>" # only for lifecycle runbooks
## Investigations
Incident records live in `knowledge/sources/investigations/YYYY-MM-DD-slug.md` and are **evidence sources** — written
once at incident time, then linked from the changelogs of the nodes they implicate. Sections:
`## Summary`, `## Timeline`, `## Root cause`, `## Mitigations applied`, `## Open questions`. Resolved
incidents move to `knowledge/sources/investigations/archive/`.
Incident records are `investigation` entities in the DB, linked to the entities they implicate via
`about` edges. They are **evidence sources** — written once at incident time, then back-linked from
the changelogs of the nodes they implicate. Sections: `## Summary`, `## Timeline`, `## Root cause`,
`## Mitigations applied`, `## Open questions`. The legacy file-based investigations live at
`archive/knowledge/sources/investigations/` (frozen 2026-07-07); new investigations go in the DB.
## The operations log
`plans/log.md` and `knowledge/log.md` are append-only records of documentation operations on
those areas (`## [YYYY-MM-DD] <op> | <summary>`), distinct from the Oikos change ledger.
`plans/log.md` is the append-only record of documentation operations on plans
(`## [YYYY-MM-DD] <op> | <summary>`), distinct from the DB audit trail. The legacy
`archive/knowledge/log.md` is frozen with the rest of the archived wiki.

View File

@@ -1,40 +1,49 @@
# LLM Wiki — the documentation contract
How the narrative documentation in this repo is organized. The pattern is borrowed from the
`sources / wiki / index / log` model: a durable synthesized layer (`archive/knowledge/`) built on top
of immutable evidence (`knowledge/sources/`, incident records), with pure-listing indexes and an
append-only operations log.
How documentation in this repo is organized. The pattern is the `sources / wiki / index / log`
model: a durable synthesized layer built on top of immutable evidence, with pure-listing indexes and
an append-only operations log.
This contract governs the **narrative layer only**. The machine-readable substrate — `inventory.yaml`,
`secrets/`, `scripts/`, `bin/` — is not part of the wiki and never
moves under it. See [the knowledge schema](../domains/knowledge/schema.md) for the split.
This contract governs the **narrative layer only**. The machine-readable source of truth — the
Postgres database, bootstrapped from `seeds/` — is not part of the wiki and never moves under it.
See [the knowledge schema](../domains/knowledge/schema.md) for the split, and ADR 0003 for the
DB-native model.
## Layers
- **Sources** are immutable raw material: incident records (`knowledge/sources/investigations/`), external reference
docs (`knowledge/sources/references/`), and the live system itself (`pct config`, `docker inspect`).
Read them; do not rewrite them into other sources.
- **Wiki** (`archive/knowledge/`) is the synthesized, authoritative current-state layer: one page per
node (`containers/`, `vms/`, host narratives) and per cross-cutting system (`infrastructure/`). A
reader understands the topic from the wiki page without reading the sources.
- **Source of truth** is the Postgres database. Structured data (entities, relationships, status,
metrics) and narrative knowledge (documents, investigations, runbooks) both live there, in the
`entities` / `relationships` / `knowledge_entities` tables. It is bootstrapped at deploy time from
`seeds/{ontology,inventory,policy,knowledge}.yaml` (idempotent, content-hashed via
`seed_versions`) and mutated at runtime via the API/MCP. `oikos export` regenerates
`seeds/{ontology,inventory,policy}.yaml` for version control.
- **Sources** are immutable raw material: incident records (now `investigation` entities in the DB,
historically `archive/knowledge/sources/investigations/`), external reference docs, and the live
system itself (`pct config`, `docker inspect`). Read them; do not rewrite them into other sources.
- **Wiki** — the synthesized, authoritative current-state layer. Today this is the set of
`document` entities in the DB (one per node and per cross-cutting system), queried via MCP
`search_knowledge` / `get_entity_knowledge`. The legacy file-based wiki is frozen at
`archive/knowledge/{hosts,containers,vms,infrastructure}/` for historical reference only.
- **Index** (`index.md` / folder `README.md`) is a pure listing — every page in scope with a
one-line summary, and nothing else. Anything the section wants to say up front goes into a page
the index lists, not into the index.
- **Log** (`log.md`) is append-only, recording *doc-maintenance operations* (restructures, source
ingests, lint sweeps) in single-line format: `## [YYYY-MM-DD] <op> | <summary>`.
- **Log** is append-only, recording *doc-maintenance operations* (restructures, source ingests,
lint sweeps) in single-line format: `## [YYYY-MM-DD] <op> | <summary>`. The active log is the DB
audit trail; `archive/knowledge/log.md` is the frozen legacy equivalent.
## Two logs, kept distinct
- **`## Changelog`** on each node/topic page records *infrastructure* changes to that node. It is
machine-parsed (`get_changelog`, the Oikos ledger) — keep the `### YYYY-MM-DD — title` shape.
- **`log.md`** per area records *documentation* operations only. It never duplicates the Oikos
change ledger (`oikos/ledger.py`), which stays authoritative for infra changes with
who/what/risk/approval/verification.
- **`## Changelog`** on each node/topic document records *infrastructure* changes to that node. It
is stored as a structured field on the `document` entity — keep the `### YYYY-MM-DD — title`
shape so it parses cleanly.
- **Doc-maintenance logs** record *documentation* operations only. They never duplicate the
infrastructure changelog, which stays authoritative for infra changes with
who/what/risk/approval/verification (now the DB audit trail, formerly `oikos/ledger.py`).
## Rules
- Wiki pages stay short and focused. A page past ~300 lines splits.
- Pages stay flat under `wiki/<section>/` until there are enough to warrant a sub-group.
- Pages stay flat under their section until there are enough to warrant a sub-group.
- Every page follows [writing-style.md](writing-style.md).
- Plans and design docs always live in the repo `plans/` folder (`plans/YYYY-MM-DD-slug.md`),
listed in `plans/index.md`, moved to `plans/done/` on completion — never a scratch path or a chat

View File

@@ -14,7 +14,7 @@ in [writing-style.md](writing-style.md); the layer model (sources / wiki / index
**Content / narrative pages:** lowercase-with-dashes, date-prefixed as needed
- **Container pages:** `<id>-<name>.md` (e.g. `101-jellyfin.md`, `132-rclone.md`). The `<id>` is the LXC/VM ordinal from `inventory.yaml`.
- **Container pages:** `<id>-<name>.md` (e.g. `101-jellyfin.md`, `132-rclone.md`). The `<id>` is the LXC/VM ordinal from the entity's attributes in the DB (seeded via `seeds/inventory.yaml`).
- **Infrastructure / cross-cutting pages:** `<topic>.md` (e.g. `dns.md`, `auto-deploy.md`, `mesh.md`). Describes a system, not a specific node.
- **Plans / investigations:** `YYYY-MM-DD-<slug>.md` (e.g. `2026-07-05-oikos-prometheus-lxc.md`). Date-sorted; slug is lowercase.
- **Section indices:** `README.md` (lowercase, conventional). Prefer in folders; `index.md` only if both intro prose and listing coexist.
@@ -118,7 +118,7 @@ What it looks like after.
Changelog entries to write, index status to update.
```
### Investigation (`knowledge/sources/investigations/YYYY-MM-DD-slug.md`)
### Investigation (`investigation` entity in the DB; historically `archive/knowledge/sources/investigations/YYYY-MM-DD-slug.md`)
```markdown
# YYYY-MM-DD — <title>
@@ -152,16 +152,18 @@ Changelog entries to write, index status to update.
## Same-session update rule
When you make a change to a node — migrate an LXC, update an IP, change a
mount, deploy a new service — **update every relevant doc page in the same
session.** A change that touches a container page must also update:
mount, deploy a new service — **update the DB and every relevant doc page in
the same session.** A change that touches a container must also update:
- The `containers/index.md` table (IPs, host, mounts, status)
- The `entities` / `relationships` rows for the node (via the API/MCP) —
this is the source of truth
- The `document` entity's `at_glance` and `## Changelog` for the container
- The `containers/index.md` table in the archived wiki (IPs, host, mounts,
status) — historical reference, update for consistency where still consulted
- The `README.md` table (if the change affects listed columns)
- The Caddy page site list (if the change affects `*.hubris.network` routing)
- The DNS / ingress infrastructure pages (if the change affects routing)
- The `hosts/{hubris,strong}.md` host page (if container count changes)
- The `inventory.yaml` host entry (single source of truth)
- The `infrastructure/topology.md` (generated from inventory, but regen if needed)
The pattern of updating only one page and leaving stale references on others
is a bug. If you're doing a multi-step migration, document the intermediate

View File

@@ -36,7 +36,7 @@ Every doc-level page follows the same shape so a reader scans it in one pass.
1. **One H1 = the page title.** Node pages use `# <id> — \`<name>\``; topic pages use `# <Topic>`.
2. **Opening definition.** First paragraph, 13 sentences, says what the thing is. No motivation, no marketing, no setup.
3. **Body sections** in the natural order for the topic. Reuse the section templates in [page-templates.md](page-templates.md).
4. **`## Changelog`** at the bottom of every node/topic page — reverse-chronological, append-only. This section is machine-parsed (Go MCP `get_changelog` in `internal/mcp/server.go`); keep the `### YYYY-MM-DD — title` shape.
4. **`## Changelog`** at the bottom of every node/topic page — reverse-chronological, append-only. This section is stored as a structured field on the `document` entity in the DB; keep the `### YYYY-MM-DD — title` shape so it parses cleanly.
5. **Related links** only at the bottom, only when a reference cannot be woven inline.
## Section indexes (folder READMEs)
@@ -53,12 +53,12 @@ duplicated prose, no narrative between the intro and the table.
- Prefer **tables** for enumerable items with internal structure (service/port maps, field lists, status grids). Reserve bullets for short non-structured lists.
- Use the **bold-leading-phrase pattern** for structured points: `**Read-only by construction.** The MCP server never mutates state.` — a bold noun phrase, a period, then the explanation.
- When enumerating across services or nodes, give each its own `###` sub-section or a table row, not one run-on paragraph.
- Use backticks for code, paths, hostnames, and file names (`inventory.yaml`, `192.168.8.77`, `pct config`); italics for first-mention terminology.
- Use backticks for code, paths, hostnames, and file names (`seeds/inventory.yaml`, `192.168.8.77`, `pct config`); italics for first-mention terminology.
- Use `>` blockquotes for caveats and gaps that interrupt the main flow: `> **Outstanding gap.** DNS-vs-inventory drift check not yet wired.` One thought per blockquote.
## Diagrams
- Mermaid is the default for topology and flow diagrams. `infrastructure/topology.md` is generated by `oikos/gen-topology.py` — do not hand-edit it. (Go DB-native topology generation planned.)
- Mermaid is the default for topology and flow diagrams. `infrastructure/topology.md` in the archived wiki was generated by the retired `oikos/gen-topology.py`; the DB-native equivalent is a future task — do not hand-edit the archived file expecting it to regenerate.
- ASCII box diagrams are fine for small shape diagrams; keep them to one screen.
## Sourcing and cross-references

View File

@@ -1 +1 @@
0.7.7
0.7.8

View File

@@ -1,618 +1,28 @@
# Homelab inventory — canonical structured topology.
# DEPRECATED — 2026-07-17 (R5). Do not edit. Do not consume.
#
# Single source of truth. hosts/*.yaml is generated from this file by
# mcp/build_host_files.py; do NOT edit those by hand.
# This root-level inventory.yaml is the Python-era topology file. It was
# superseded on 2026-07-07 by the DB-native model (ADR 0003):
#
# Conventions:
# - hostname keys MUST match the actual `hostname` of the machine (on
# macOS: `scutil --get LocalHostName` if set).
# - `os:` one of: linux, macos
# - `kind:` one of: proxmox-host, lxc, vm, workstation, external
# ("external" is reserved for hosts the homelab CLI manages via ssh but
# that aren't homelab clients themselves — e.g. the IONOS netbird VPS
# with no /etc/age/key.txt and no /opt/homelab-context clone.)
# - `mesh:` lists addresses the host is reachable at. Both `netbird` and
# `tailscale` are accepted during the migration (see infrastructure/mesh.md).
# Prefer netbird FQDNs over raw IPs.
# - `age_pubkey:` provisioned by secrets-issuance on first bootstrap and
# committed back via `homelab client add --finalize-pubkey <key>`.
# - When a service moves hosts, update only the `services:` section here;
# never duplicate addresses elsewhere.
# - `ssh.user:` per-host login user. Default is `root` if omitted (matches
# every LXC + the PVE host). Set explicitly for workstations whose login
# user differs from `root`. Used by the `homelab` CLI to build
# `user@host` and to inform anyone running raw `netbird ssh` (which
# defaults to the LOCAL username — the gotcha that creates "user not
# found" errors when ssh'ing INTO machines that only have `root`).
# - The Postgres database is the single source of truth for all structured
# data. Query it via MCP `get_entity` / `list_entities` or the REST API.
# - `seeds/inventory.yaml` is the bootstrap + DR seed manifest, ingested
# idempotently into the DB at deploy time (content-hashed via
# `seed_versions`). `oikos export` regenerates it from the DB for VC.
# - `archive/knowledge/` holds the frozen legacy narrative wiki.
#
# `homelab client add/remove` does surgical line-edits — comments survive.
# Avoid round-tripping the file through yaml.safe_dump (it strips comments).
mesh:
primary: netbird
accepted:
- netbird
- tailscale
netbird_subnet: 100.122.0.0/16
netbird_domain: netbird.selfhosted
# Service contract (Oikos, 2026-07-05): each service should carry
# backend host/container that runs it (required)
# url public URL if ingress-exposed
# doc_page owning wiki page
# config_repo tracked config repo, if any (mutations go commit+push)
# health health-check URL if it differs from `url`
# risk_notes what an agent must know before touching it
# See oikos/ontology.yaml + oikos/policy.yaml.
services:
proxmox_ui:
url: https://proxmox.hubris.network
backend: hubris
port: 8006
doc_page: knowledge/wiki/hosts/hubris.md
risk_notes: hypervisor UI — changes here affect every guest on the node
gitea:
url: https://git.hubris.network
backend: gitea
backend_url: http://192.168.8.121:3000
doc_page: knowledge/wiki/containers/104-gitea.md
config_repo: dtoro/gitea-customizations
risk_notes: hosts all config repos + deploy webhooks; outage blocks auto-deploy and sync
caddy:
backend: caddy
role: reverse-proxy
note: terminates all *.hubris.network
doc_page: knowledge/wiki/containers/121-caddy.md
config_repo: dtoro/caddy-conf
risk_notes: wide blast radius — every *.hubris.network route rides on it (see oikos/policy.yaml service_overrides)
authentik:
url: https://auth.hubris.network
backend: netbird-vps
doc_page: knowledge/wiki/containers/106-auth-outpost.md
note: >-
core runs on the VPS since 2026-05-31; LAN forward-auth outpost is
auth-outpost (LXC 106) at 192.168.8.6:9000. Previous backend value
"authentik" referenced the retired embedded-outpost host (LXC 124).
risk_notes: SSO provider — outage locks login to OIDC/forward-auth services
dns:
backend: dns
note: Technitium DNS, split-horizon zone
doc_page: knowledge/wiki/containers/107-dns.md
risk_notes: LAN-wide resolver — misconfig breaks name resolution for every client
jellyfin:
url: https://media.hubris.network
backend: jellyfin
doc_page: knowledge/wiki/containers/101-jellyfin.md
risk_notes: native Authentik OIDC via SSO-Auth plugin, no Caddy forward-auth gate; VAAPI transcode depends on GPU passthrough on strong
nextcloud:
url: https://cloud.hubris.network
backend: nextcloud
doc_page: knowledge/wiki/containers/114-nextcloud.md
paperless:
url: https://paperless.hubris.network
backend: paperless
doc_page: knowledge/wiki/containers/103-paperless.md
risk_notes: document archive — treat data as irreplaceable; DB operations are destructive-class
matrix:
url: https://matrix.hubris.network
backend: elementsynapse
doc_page: knowledge/wiki/containers/118-elementsynapse.md
risk_notes: alert/approval channel for Oikos — outage silences agent escalation
photos:
url: https://photos.hubris.network
backend: mule-images
doc_page: knowledge/wiki/containers/120-mule-images.md
config_repo: dtoro/mule-image
arr_stack:
backend: arriman
note: jellyseerr / qbit / sab on docker compose
doc_page: knowledge/wiki/containers/122-arriman.md
artifacto:
backend: apps
url: https://artifacto.hubris.network
doc_page: knowledge/wiki/containers/105-apps.md
config_repo: dtoro/Artifacto
trmnl:
backend: trmnl
url: https://trmnl.hubris.network
note: self-hosted middleware for TRMNL e-ink plugins (polled by TRMNL cloud)
doc_page: knowledge/wiki/containers/128-trmnl.md
config_repo: dtoro/terminalito
zimaos:
url: https://zimaos.hubris.network
backend: zimaos
doc_page: knowledge/wiki/vms/100-zimaos.md
haos:
backend: haos
doc_page: knowledge/wiki/vms/108-haos.md
teddycloud:
url: https://teddy.hubris.network
backend: teddycloud
doc_page: knowledge/wiki/containers/131-teddycloud.md
note: self-hosted TeddyCloud (Toniebox cloud reimplementation), docker compose
risk_notes: no Caddy forward-auth gate (unlike sab.hubris.network on the same Caddyfile) —
reachable to anyone on the LAN/mesh who can resolve teddy.hubris.network; undocumented
in inventory.yaml until 2026-07-06 (drift-caught)
homelab_mcp:
backend: apps
port: 9810
systemd_unit: homelab-mcp
public_host: mcp.hubris.network
endpoint: https://mcp.hubris.network/mcp
doc_page: knowledge/wiki/infrastructure/homelab-context.md
config_repo: dtoro/oikos
note: MCP server. Read-only context + management. Reachable on the LAN via Caddy
and from off-LAN via Netbird (192.168.8.0/24 is a network resource routed through
hubris).
risk_notes: agents' primary read surface — outage degrades every agent to grepping the clone
secrets_issuance:
backend: apps
port: 9820
systemd_unit: secrets-issuance
public_host: secrets.hubris.network
endpoint: https://secrets.hubris.network/issue
doc_page: .agents/operations/agent-enrollment.md
config_repo: dtoro/oikos
note: Issues per-client age private keys. Gated at source-IP layer (mesh + LAN
subnets in MESH_SUBNETS).
risk_notes: identity issuance — any change is security-sensitive; key operations are destructive-class
hosts:
hubris:
kind: proxmox-host
os: linux
role: hypervisor
lan_ip: 192.168.8.77
mesh:
netbird:
ip: 100.122.38.109
fqdn: proxmox-server.netbird.selfhosted
ssh:
port: 22
netbird_port: 22022
user: root
mounts:
- /mnt/library
age_pubkey: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
trmnl:
kind: lxc
pve_id: 128
host: hubris
os: linux
role: trmnl-middleware
lan_ip: 192.168.8.211
public_host: trmnl.hubris.network
# not yet mesh/SOPS-enrolled — see containers/128-trmnl.md
house:
kind: lxc
pve_id: 129
host: strong
os: linux
role: family-planner
lan_ip: 192.168.8.244
public_host: house.hubris.network
notes:
- Docker host for Yuvomi (family planner). Created 2026-06-26.
- Migrated from hubris to strong 2026-07-05 (Phase 1 of strong migration plan).
- Runs Yuvomi container + WebDAV doc bridge to paperless
- 192.168.8.212 was the hubris IP before migration (briefly picked up by teddycloud via
DHCP; teddycloud has since been given a static IP, see hosts.teddycloud)
age_pubkey: age1s07zs83ehtlg8jtwvr75ltc3c4cdlemfwjuxrwjtwkqxkl9tpggsyrzn2h
jellyfin:
kind: lxc
pve_id: 101
host: strong
os: linux
role: media-server
lan_ip: 192.168.8.246
public_host: media.hubris.network
mesh:
tailscale:
fqdn: jellyfin
mounts:
- /mnt/media_local
notes:
- Jellyfin 10.11.11 with VAAPI hardware acceleration (Radeon 680M iGPU on strong)
- 4 cores / 8 GiB RAM / 1 GiB swap
- SSO-Auth plugin v4.0.0.4 with Authentik OIDC (no Caddy forward-auth gate)
- GPU passed via dev0+dev1: /dev/dri/renderD128 + card0
- Migrated from hubris to strong 2026-07-05 (Phase 2). Library on ludo-lvm.
age_pubkey: ''
nfs-export:
kind: lxc
pve_id: 102
host: hubris
os: linux
role: storage-export
lan_ip: 192.168.8.200
paperless:
kind: lxc
pve_id: 103
host: hubris
os: linux
role: document-archive
lan_ip: 192.168.8.130
public_host: paperless.hubris.network
mesh:
tailscale:
fqdn: paperless
mounts:
- /mnt/library
age_pubkey: ''
gitea:
kind: lxc
pve_id: 104
host: hubris
os: linux
role: git-server
lan_ip: 192.168.8.121
public_host: git.hubris.network
backend_port: 3000
mesh:
tailscale:
fqdn: gitea
mounts:
- /mnt/library
notes:
- Bare repos live at /mnt/library/repos/dtoro/*.git
age_pubkey: ''
apps:
kind: lxc
pve_id: 105
host: hubris
os: linux
role: docker-apps
lan_ip: 192.168.8.205
public_hosts:
- artifacto.hubris.network
mesh:
tailscale:
ip: 100.121.171.122
fqdn: apps
mounts:
- /mnt/library
runs:
- artifacto
- plantuml
- homelab-mcp
- secrets-issuance
# booklore removed 2026-06-29 → migrated to grimmory (LXC 130)
age_pubkey: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
auth-outpost:
kind: lxc
pve_id: 106
host: hubris
os: linux
role: authentik-gateway
lan_ip: 192.168.8.6
notes:
- Runs Authentik outpost (reverse-proxy/SSO enforcement) for protected services
dns:
kind: lxc
pve_id: 107
host: hubris
os: linux
role: dns-server
lan_ip: 192.168.8.2
notes:
- Technitium DNS, split-horizon zone for *.hubris.network
- Primary DNS for 192.168.8.0/24 LAN (inventory.services.dns references this)
nextcloud:
kind: lxc
pve_id: 114
host: hubris
os: linux
role: file-sync
lan_ip: 192.168.8.224
public_host: cloud.hubris.network
mesh:
tailscale:
fqdn: nextcloud
mounts:
- /mnt/library
age_pubkey: ''
elementsynapse:
kind: lxc
pve_id: 118
host: strong
os: linux
role: matrix-server
lan_ip: 192.168.8.242
public_host: matrix.hubris.network
mesh:
tailscale: {}
notes:
- Migrated from hubris to strong 2026-07-05 (Phase 1 of strong migration plan).
sophia:
kind: lxc
pve_id: 119
host: hubris
os: linux
role: workshop
lan_ip: 192.168.8.109
mesh:
tailscale:
fqdn: sophia
mounts:
- /mnt/library
age_pubkey: ''
mule-images:
kind: lxc
pve_id: 120
host: hubris
os: linux
role: photo-management
lan_ip: 192.168.8.136
public_host: photos.hubris.network
mesh:
tailscale:
fqdn: muleimage
mounts:
- /mnt/library
age_pubkey: ''
caddy:
kind: lxc
pve_id: 121
host: hubris
os: linux
role: reverse-proxy
lan_ip: 192.168.8.175
notes:
- Terminates all *.hubris.network
- /etc/caddy is a git checkout of dtoro/caddy-conf
peers:
- authentik
- gitea
arriman:
kind: lxc
pve_id: 122
host: strong
os: linux
role: arr-stack
lan_ip: 192.168.8.245
public_hosts:
- jellyseerr.hubris.network
- qbit.hubris.network
- sab.hubris.network
mesh:
tailscale:
fqdn: arr
mounts:
- /mnt/media_local
notes:
- Migrated from hubris to strong 2026-07-05 (Phase 2). Library on ludo-lvm.
- Contains homarr, radarr, sonarr, lidarr, sabnzbd, qbittorrent, bazarr, flaresolverr, prowlarr, jellyseerr
- qBittorrent auth subnet whitelist expanded to 192.168.8.0/24 (for seanime + Caddy access)
age_pubkey: ''
grimmory:
kind: lxc
pve_id: 130
host: strong
os: linux
role: book-library
lan_ip: 192.168.8.247
public_host: books.hubris.network
mounts:
- /mnt/media_local
notes:
- Docker host for Grimmory (community fork of Booklore). Created 2026-06-29.
- Migrated from hubris to strong 2026-07-05 (Phase 2d). Books on ludo-lvm.
age_pubkey: age1uellsemnjrzgfg9fxw4jefpy05laxzggwnwhh6ny3wl7alyp6v8q0muxet
teddycloud:
kind: lxc
pve_id: 131
host: hubris
os: linux
role: teddycloud
lan_ip: 192.168.8.150
public_host: teddy.hubris.network
mounts:
- /mnt/library
state: active
notes:
- Docker host for TeddyCloud (ghcr.io/toniebox-reverse-engineering/teddycloud), a
self-hosted reimplementation of the Toniebox cloud backend. Debian 12 (bookworm).
- 1 core / 1 GiB RAM / 512 MiB swap / 16 GiB rootfs (local-lvm).
- Predates the client-enrollment convention — undocumented in inventory.yaml until
2026-07-06, when Oikos's drift detector (oikos/drift.py) caught pve_id 131 live on
hubris (`pct list`) with no inventory entry. Static IP assigned 2026-07-05 during the
strong migration (was picking up 192.168.8.243 via DHCP before that — see
hosts/strong.md's 2026-07-05 changelog).
- No age_pubkey / homelab-context enrollment — not a homelab CLI client, just a
docker-compose app container. Not a required follow-up unless it needs secrets.
seanime:
kind: lxc
pve_id: 133
host: strong
os: linux
role: anime-media-server
lan_ip: 192.168.8.248
public_host: seanime.hubris.network
mounts:
- /mnt/media_local/anime
notes:
- Seanime anime media server for online streaming + local library scanning
- Created 2026-07-05. Binary at /opt/seanime/bin/seanime, systemd service.
- Connected to qBittorrent on arriman (192.168.8.245:8080)
- 8 online streaming extensions installed (HiAnime, AniWatch, KickAssAnime, etc.)
- /anime mounted from strong ludo-lvm (/mnt/media_local/anime)
- Caddy: https://seanime.hubris.network → 192.168.8.248:43211
- qBittorrent auth subnet whitelist expanded to 192.168.8.0/24 for seanime access
romm:
kind: lxc
pve_id: 134
host: strong
os: linux
role: rom-manager
lan_ip: 192.168.8.249
public_host: roms.hubris.network
mounts:
- /mnt/media_local
notes:
- Docker host for RomM (romm.app) self-hosted ROM manager. Created 2026-07-05.
- MariaDB sidecar at /opt/romm/docker-compose.yml.
- ROMs on ludo-lvm media volume at /mnt/media_local/roms.
- 1 core / 2 GiB RAM / 16 GiB rootfs (ludo-lvm).
zimaos:
kind: vm
pve_id: 100
host: hubris
os: linux
role: nas-frontend-eval
lan_ip: 192.168.8.195
public_host: zimaos.hubris.network
haos:
kind: vm
pve_id: 108
host: hubris
os: linux
role: home-automation
lan_ip: 192.168.8.101
mesh:
tailscale:
fqdn: homeassistant
republic-laptop:
kind: workstation
os: linux
role: primary-dev
mesh:
netbird:
fqdn: republic-laptop.netbird.selfhosted
ssh:
user: dtoro
mac-mini:
kind: workstation
os: macos
role: dev
lan_ip: 192.168.178.182
mesh:
netbird:
fqdn: mac-mini-234-17.netbird.selfhosted
ssh:
user: dtoro
notes:
- Only macOS in the fleet. Bootstrap uses launchd.
age_pubkey: age169104ee1a9e1577d493820830560197f0adf56bf8f1c369d57c152c03f9437ae
strong:
kind: proxmox-host
os: linux
role: hypervisor
lan_ip: 192.168.178.181
ssh:
user: root
notes:
- Reformatted from Linux workstation ("ludo-mini" in this wiki, still
the machine's nickname) to Proxmox VE 9.2.3 on 2026-07-01. Renamed
the inventory/wiki identity from ludo-mini to strong on the same day
so it matches the OS/cluster hostname everywhere (bootstrap looks up
hosts/$(hostname).yaml, so a mismatch would break enrollment).
- Joined hubris's "Homelab" cluster same day. 2-node, no QDevice
tiebreaker yet — see hosts/hubris.md quorum note.
- Netbird not yet installed (fresh OS wiped prior enrollment); reachable
today only via the household LAN / existing Fritz static route to
192.168.8.0/24. Re-enroll in mesh as a follow-up if off-LAN access
to this host itself (not just its future guests) is needed.
- First step of the planned library-SSD migration — see
.nomos/plans/2026-06-03_110000-library-ssd-migration-to-ludo-mini.md
(filename kept as-is, it's a historical planning doc). Only Phase 1
(Proxmox install + cluster join) is done; no physical
drive move, service migration, or GPU passthrough has happened yet.
age_pubkey: age1rtwvdct6avjkr3cyxv3vue3vqx4d524fjfr3vk7xrnvyrylnry5sm54sn4
netbird-vps:
kind: external
os: linux
role: netbird-mgmt
mesh:
netbird:
ip: 100.122.165.149
fqdn: netbird-ionos.netbird.selfhosted
ssh:
user: root
notes:
- Public IONOS VPS — hosts the vanilla netbird mgmt+signal+relay+dashboard
stack + host coturn (see infrastructure/vps-hardening.md +
infrastructure/mesh.md changelog 2026-05-21).
- NOT a homelab client. No /etc/age/key.txt, no /opt/homelab-context
clone. Managed via ssh from hubris; sshd is locked to hubris's pubkey.
- Public IPv4 82.165.190.79. Auto-patching via unattended-upgrades.
- Configs rendered by `homelab render-vps-configs` from
vps/turnserver.conf.tmpl + vps/management.json.tmpl, with secrets
decrypted from secrets/turn-shared-secret.yaml +
secrets/netbird-authentik-oidc.yaml on hubris.
rclone:
kind: lxc
os: linux
role: backup
mesh:
netbird:
fqdn: rclone.netbird.selfhosted
age_pubkey: age1pwtdws2thdh7vzp2dzttl3zxgcs2tgpcsjsqgw3q04nyml4kvuqq467u4x
# Destroyed nodes (lifecycle state: destroyed — see oikos/ontology.yaml).
# Kept so agents can answer "what happened to X?" from structured data and
# so drift detectors can flag anything still referencing them.
# Full narrative table: containers/index.md "Recently destroyed".
archaeology:
claudio-bot:
kind: lxc
pve_id: 123
destroyed: 2026-06-04
reason: replaced by Nomos Agent on mac-mini; monitoring moved to homelab-health-watchdog cron
plato:
kind: lxc
pve_id: 126
destroyed: 2026-06-28
reason: notes workspace decommissioned; data retained at /mnt/library/documents/plato
mule-photos-new:
kind: lxc
pve_id: 127
destroyed: 2026-05-22
reason: PhotoPrism test stack promoted to LXC 120 (Mulimage 2.0 merge)
heaper:
kind: lxc
pve_id: 116
destroyed: 2026-05-14
reason: decommissioned; data retained at /mnt/library/heaper
syncthing:
kind: lxc
pve_id: 109
destroyed: 2026-05-14
reason: decommissioned; library subtree was empty
seafile:
kind: lxc
pve_id: 125
destroyed: 2026-05-13
reason: Seafile Pro evaluation rejected; files.hubris.network removed from caddy + dns
arr-yunohost:
kind: lxc
pve_id: 100
destroyed: 2026-04-28
reason: migrated to docker stack on arriman (LXC 122)
flaresolverr:
kind: lxc
pve_id: 106
destroyed: 2026-04-28
reason: folded into the arriman docker compose
marimo:
kind: lxc
pve_id: 107
destroyed: 2026-04-28
reason: decommissioned
photoprism:
kind: lxc
pve_id: 110
destroyed: 2026-04-28
reason: replaced by mule-images (LXC 120)
karakeep:
kind: lxc
pve_id: 111
destroyed: 2026-04-28
reason: decommissioned
immich:
kind: lxc
pve_id: 112
destroyed: 2026-04-28
reason: replaced by mule-images (LXC 120)
reticulum:
kind: lxc
pve_id: 115
destroyed: 2026-04-28
reason: decommissioned
# This file is kept only because AGENTS.md §1/§2 still point clients at
# `/opt/homelab-context/inventory.yaml` (the on-client clone path). Reconcile
# that path to `seeds/inventory.yaml` and delete this stub — tracked as R13
# in plans/2026-07-17-codebase-review-and-cleanup.md.
#
# The live topology is in the DB. The seed is `seeds/inventory.yaml`. This
# file is not read by any Go code, script, or test in the repo.
---
deprecated: true
superseded_by: seeds/inventory.yaml
source_of_truth: postgres (see ADR 0003)
see_also:
- seeds/inventory.yaml
- seeds/ontology.yaml
- seeds/policy.yaml
- docs/adr/0003-db-native-ontology-yaml-seeds.md

View File

@@ -322,14 +322,17 @@ Remaining brittle numbers (left as-is, intrinsic to evidence trail):
evidence trail. Recommend adding a "Last verified: YYYY-MM-DD" header to that
file and a scheduled re-verification (see §F).
### D.3 Substrate docs describing deleted Python architecture — NOT fixed
### D.3 Substrate docs describing deleted Python architecture — fixed in R5
`.agents/domains/knowledge/schema.md` and `.agents/shared/llm-wiki.md`
describe `bin/homelab`, `oikos/cards/`, `oikos/ledger.py`, root
`inventory.yaml`, `knowledge/sources/`, `get_page`/`search_docs` MCP tools —
none of which exist. They contradict AGENTS.md §"Source of truth" and ADR
0003. **These need a full rewrite** (deferred — substantial; tracked as
recommendation R5).
`.agents/domains/knowledge/schema.md` and `.agents/shared/llm-wiki.md` previously described
`bin/homelab`, `oikos/cards/`, `oikos/ledger.py`, root `inventory.yaml`, `knowledge/sources/`,
`get_page`/`search_docs` MCP tools — none of which exist. **Rewritten** for the DB-native model
(ADR 0003): DB is the single source of truth for both structured data and narrative knowledge;
`seeds/*.yaml` are the bootstrap+DR manifests; `archive/knowledge/` is the frozen legacy wiki;
MCP `search_knowledge`/`get_entity_knowledge` replace `get_page`/`search_docs`. Substrate refs in
`.agents/shared/{writing-style,page-templates}.md` and
`.agents/domains/operations/schema.md` swept clean. Root `inventory.yaml` marked deprecated
(stub points to `seeds/inventory.yaml` + DB; full on-client path reconciliation deferred to R13).
### D.4 ADR format
@@ -362,13 +365,14 @@ itself use `/opt/homelab/`. **Pick one and use consistently** (recommend
`/opt/homelab/` per `CLIENTS.md:70-71`). Deferred — touches many lines and
the actual deployed path needs confirming against an enrolled client.
### D.8 Legacy root `inventory.yaml`
### D.8 Legacy root `inventory.yaml` — deprecated in R5
20387-byte Python-era file still committed; superseded by
`seeds/inventory.yaml` on 2026-07-07. Multiple `.agents/` docs still treat bare
`inventory.yaml` as the kernel source of truth. **Delete or mark explicitly
deprecated** (deferred — touches `.agents/shared/*` and `.agents/domains/*`
which need the substrate rewrite in R5 anyway).
20387-byte Python-era file superseded by `seeds/inventory.yaml` on 2026-07-07. **Replaced with a
deprecation stub** pointing to `seeds/inventory.yaml` and the DB (ADR 0003). Kept as a stub rather
than deleted because AGENTS.md §1/§2 still point clients at `/opt/homelab-context/inventory.yaml`
(the on-client clone path); full path reconciliation is R13. `.agents/shared/*` and
`.agents/domains/*` references to bare `inventory.yaml` swept to `seeds/inventory.yaml` or
qualified as `archive/knowledge/` history.
## E. Build & tooling
@@ -401,7 +405,7 @@ behavior; tracked as R6.
| R2 | Delete dead web: 21-file tool-renderer registry, 5 dead components, 2 dead store exports, 2 dead npm deps | S | Low | ✅ done (c3973e7+1) |
| R3 | Decide sqlc vs raw SQL: delete 17 dead queries OR migrate inline SQL to use them | M | Medium | ✅ done (hybrid: 8 deleted, 9 migrated) |
| R4 | Split `phase3.go` (2627 lines) into per-resource files; refactor `newServer` (708 lines) to a tool registry | M | Medium |
| R5 | Rewrite `.agents/domains/knowledge/schema.md` + `.agents/shared/llm-wiki.md` for the DB-native model; delete/deprecate root `inventory.yaml` | M | Low |
| R5 | Rewrite `.agents/domains/knowledge/schema.md` + `.agents/shared/llm-wiki.md` for the DB-native model; delete/deprecate root `inventory.yaml` | M | Low | ✅ done |
| R6 | Inject desktop `version` from `VERSION` via ldflags; fix `Makefile` `BINARY` colliding with `oikos/` dir | S | Low |
| R7 | Add tests for `learning` (80% gate), `actuator`, `scheduler`, `domain`, `notifier`, `knowledge` | L | Low |
| R8 | Add `eslint`+`prettier`+`vitest` to `web/`; wire `svelte-check`+`tsc` into CI; add `web/` CI job | M | Low |