cleanup: fix all stale references across .agents/ docs

- Rewrite AGENTS.md: DB as source of truth, MCP knowledge tools, archive refs
- Fix OIKOS.md: seeds/ paths, remove Python-era notes, update deployment status
- Fix commands.md, agent-enrollment.md: archive/knowledge/ links
- Fix all SKILL.md files: remove hosts/*.yaml refs, point to inventory.yaml
- Fix HERMES.md, schema.md, page-templates.md, llm-wiki.md: update paths
- Fix bootstrap.sh: identity check reads inventory.yaml
- Fix README.md, cutover-checklist.md: stale wiki references
- Move convert-wiki.py to archive/ (one-shot done)
This commit is contained in:
2026-07-07 21:00:39 +02:00
parent 5009a335bb
commit 5e3b946ded
24 changed files with 138 additions and 135 deletions

View File

@@ -8,7 +8,7 @@ conventions, and the source-of-truth hierarchy.
The homelab-context repo at `/opt/homelab-context/` is the single source of The homelab-context repo at `/opt/homelab-context/` is the single source of
truth for: truth for:
- Fleet topology (`inventory.yaml`, `hosts/*.yaml`) - Fleet topology (`inventory.yaml`, `inventory.yaml`)
- Service endpoints and credentials (via `homelab secret`) - Service endpoints and credentials (via `homelab secret`)
- Agent behaviour and conventions - Agent behaviour and conventions
- Everything in this file - Everything in this file
@@ -22,7 +22,7 @@ deploy, client enrollment, incident investigation, and each node
lifecycle transition), read the matching `.agents/skills/<name>/SKILL.md` before lifecycle transition), read the matching `.agents/skills/<name>/SKILL.md` before
acting. Each skill carries its risk class, required inputs, the acting. Each skill carries its risk class, required inputs, the
verification command, and a docs-update checklist in its frontmatter — verification command, and a docs-update checklist in its frontmatter —
classify against `oikos/policy.yaml` using that risk class before any classify against `seeds/policy.yaml` using that risk class before any
mutation. Don't re-derive topology or the mutation path by grepping the mutation. Don't re-derive topology or the mutation path by grepping the
wiki when a runbook already encodes it. See [OIKOS.md](OIKOS.md) for the wiki when a runbook already encodes it. See [OIKOS.md](OIKOS.md) for the
operating model these runbooks execute inside (OODA loop, risk classes, operating model these runbooks execute inside (OODA loop, risk classes,

View File

@@ -6,8 +6,8 @@ structure, the `homelab` CLI and MCP server are the syscall surface, and
this page defines the rules everything above them follows. this page defines the rules everything above them follows.
Read this after [AGENTS.md](../AGENTS.md). Machine-readable companions: Read this after [AGENTS.md](../AGENTS.md). Machine-readable companions:
[oikos/ontology.yaml](../oikos/ontology.yaml) (systems model), [seeds/ontology.yaml](../seeds/ontology.yaml) (systems model),
[oikos/policy.yaml](../oikos/policy.yaml) (risk & approval). [seeds/policy.yaml](../seeds/policy.yaml) (risk & approval).
## The kernel loop: OODA ## The kernel loop: OODA
@@ -41,16 +41,17 @@ one pass through **Observe → Orient → Decide → Act**:
| Host / Service | topology entities | `inventory.yaml` | | Host / Service | topology entities | `inventory.yaml` |
| Secret | SOPS+age encrypted value, per-client recipients | `secrets/` + `.sops.yaml` | | Secret | SOPS+age encrypted value, per-client recipients | `secrets/` + `.sops.yaml` |
| Runbook | executable workflow with risk class + verification | `.agents/skills/<name>/SKILL.md` | | Runbook | executable workflow with risk class + verification | `.agents/skills/<name>/SKILL.md` |
| Signal | something needing attention, with lifecycle | `signals/` ledger (Week 3) | | Signal | something needing attention, with lifecycle | DB `signals` table |
| Change | one mutation: who, what, risk, approval, verification | `ledger/` (Week 2) | | Change | one mutation: who, what, risk, approval, verification | DB `audit_log` + `executions` tables |
| Approval | short-TTL signed grant for a gated action | approval engine (Week 3) | | Approval | short-TTL signed grant for a gated action | DB `approvals` table |
| Incident | investigation narrative | `knowledge/sources/investigations/` | | Incident | investigation narrative | DB `knowledge_entities` (seeded from investigations) |
| Knowledge | document, runbook, investigation | DB `knowledge_entities` (seeded from `seeds/knowledge.yaml`) |
| Plan | design doc for non-trivial work | `plans/` | | Plan | design doc for non-trivial work | `plans/` |
| Agent | enrolled client identity = its age pubkey | `inventory.yaml` + `.sops.yaml` | | Agent | enrolled client identity = its age pubkey | `inventory.yaml` + `.sops.yaml` |
## Risk classes (enforced, not advisory) ## Risk classes (enforced, not advisory)
From [oikos/policy.yaml](../oikos/policy.yaml): From [seeds/policy.yaml](../seeds/policy.yaml):
- **read_only** — status, logs, docs, inventory. Unattended. - **read_only** — status, logs, docs, inventory. Unattended.
- **reversible_low** — restart, cache clear, sync pull. Unattended + ledger. - **reversible_low** — restart, cache clear, sync pull. Unattended + ledger.
@@ -69,7 +70,7 @@ Eight domains — physical, compute, network, storage, software,
identity & access, operations, external — cover everything in the lab; identity & access, operations, external — cover everything in the lab;
entities are connected by typed edges (`hosts`, `provides`, `mounts`, entities are connected by typed edges (`hosts`, `provides`, `mounts`,
`stores-on`, `routes-to`, `can-decrypt`, `depends-on`, `backs-up-to`, …) `stores-on`, `routes-to`, `can-decrypt`, `depends-on`, `backs-up-to`, …)
defined in [oikos/ontology.yaml](../oikos/ontology.yaml). Rule of defined in [seeds/ontology.yaml](../seeds/ontology.yaml). Rule of
completeness: **if it can break, be changed, or hold data, it has an completeness: **if it can break, be changed, or hold data, it has an
entity and edges.** Blast-radius questions ("what breaks if strong goes entity and edges.** Blast-radius questions ("what breaks if strong goes
down?") are graph walks, not doc archaeology. down?") are graph walks, not doc archaeology.
@@ -80,9 +81,9 @@ stored as `state:` in inventory (absent = active). Destroyed nodes live in
the `archaeology:` section. Each transition is a runbook checklist; the `archaeology:` section. Each transition is a runbook checklist;
deprecation completes only when inbound edges reach zero. deprecation completes only when inbound edges reach zero.
Generated views: [infrastructure/topology.md](../knowledge/wiki/infrastructure/topology.md) Generated views: the live topology graph at `oikos.hubris.network/graph`
(Mermaid, regenerated from inventory) and the live, clickable version at via the API's `/api/v1/graph` endpoint, and the Mermaid export at
`oikos.hubris.network/graph` once the Console is deployed. `GET /api/v1/graph?format=mermaid`.
## Conventions carried forward ## Conventions carried forward
@@ -95,22 +96,24 @@ Generated views: [infrastructure/topology.md](../knowledge/wiki/infrastructure/t
- Agents are terse ([caveman.md](shared/caveman.md)), verify claims, and fix - Agents are terse ([caveman.md](shared/caveman.md)), verify claims, and fix
collateral drift when found. collateral drift when found.
## Build status (Go rewrite — 2026-07-06 consolidation plan) ## Build status (Go rewrite — deployed 2026-07-07)
The Oikos runtime was rewritten from Python to Go over 6 phases. See The Oikos runtime was rewritten from Python to Go over 6 phases and is deployed
in Docker on mac-mini. See
[plans/2026-07-06-consolidate-oikos-control-plane-onto-mac-mini.md](../plans/2026-07-06-consolidate-oikos-control-plane-onto-mac-mini.md) [plans/2026-07-06-consolidate-oikos-control-plane-onto-mac-mini.md](../plans/2026-07-06-consolidate-oikos-control-plane-onto-mac-mini.md)
for the full plan. The Python codebase (`oikos/*.py`, `mcp/server.py`, for the full plan. The Python codebase has been removed; all functionality runs
`oikos/console/`) is legacy — the Flask console and Python MCP server have in the Go binary.
been removed. The Python kernel files (`oikos/*.py`) remain only because
`bin/homelab` still imports them for operational CLI commands.
**Phase 1 — Ontology + DB (DONE):** **Phase 1 — Ontology + DB (DONE):**
- `migrations/` (001009): TimescaleDB hypertables, entity_status, CAGGs, - `migrations/` (001011): TimescaleDB hypertables, entity_status, CAGGs,
retention policies. Forward-only, idempotent. retention policies, knowledge entities with FTS. Forward-only, idempotent.
- `seeds/{ontology,inventory,policy}.yaml`: DB-native bootstrap + DR export. - `seeds/{ontology,inventory,policy,knowledge}.yaml`: DB-native bootstrap +
DR export. Knowledge seed contains 36 documents, 6 investigations, and 12
runbooks.
- `blast_radius()` SQL CTE, type hierarchy, abstract types, relationship - `blast_radius()` SQL CTE, type hierarchy, abstract types, relationship
validation. validation.
- Go packages: `internal/db/`, `internal/ontology/`, `internal/domain/`. - Go packages: `internal/db/`, `internal/ontology/`, `internal/domain/`,
`internal/knowledge/`.
**Phase 2 — API (DONE):** **Phase 2 — API (DONE):**
- Single binary `cmd/oikos` with `oikos api` serving REST (:8090) + MCP - Single binary `cmd/oikos` with `oikos api` serving REST (:8090) + MCP
@@ -164,11 +167,10 @@ been removed. The Python kernel files (`oikos/*.py`) remain only because
- Cutover checklist: `scripts/cutover-checklist.md`. - Cutover checklist: `scripts/cutover-checklist.md`.
**Current deployment:** **Current deployment:**
- **Dev stack**: `docker compose --profile dev up` (postgres, api, - **Production**: Docker stack on mac-mini (`--profile full`: postgres, api,
scheduler, notifier) on mac-mini. Hermes runs with `--profile full`. scheduler, notifier, hermes). Deployed 2026-07-07 with full knowledge seed.
- **Production**: apps/105 still hosts the Python MCP + secrets-issuance. The Python MCP server and secrets-issuance on apps/105 have been stopped
Cutover checklist at `scripts/cutover-checklist.md` is the execution (see `scripts/cutover-checklist.md`).
plan to move production to the Docker stack.
## Python-era backlog (superseded) ## Python-era backlog (superseded)

View File

@@ -17,18 +17,18 @@ fixed paths, so the wiki reorganization never moves it.
| Layer | Location | Consumed by | | Layer | Location | Consumed by |
|-------|----------|-------------| |-------|----------|-------------|
| Substrate — source of truth | `inventory.yaml` (root) | MCP server, `homelab` CLI, `oikos/` scheduler/drift/relations/gen-topology | | Substrate — source of truth | `inventory.yaml` (root) | MCP server, `homelab` CLI, `oikos/` scheduler/drift/relations/gen-topology |
| Substrate — generated host records | `hosts/*.yaml` (root) | Go `internal/mcp/` server, `bin/homelab`; written by `mcp/build_host_files.py` | | 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 | | Substrate — kernel + context cards | `oikos/` (code, `oikos/cards/`, `oikos/state.json`) | MCP `explain`, scheduler |
| Narrative — synthesized wiki | `knowledge/wiki/{hosts,containers,vms,infrastructure}/` | humans, agents via MCP `get_page` / `search_docs` | | 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 | | Evidence — immutable sources | `knowledge/sources/` (references + investigations) | synthesis into wiki pages |
## Wiki pages ## Wiki pages
- **Node pages** (`knowledge/wiki/containers/<id>-<name>.md`, `.../vms/<id>-<name>.md`, - **Node pages** (`archive/knowledge/containers/<id>-<name>.md`, `.../vms/<id>-<name>.md`,
`.../hosts/<name>.md`) follow the container/host template in `.../hosts/<name>.md`) follow the container/host template in
[page-templates.md](../../shared/page-templates.md): opening definition, `## At a glance`, [page-templates.md](../../shared/page-templates.md): opening definition, `## At a glance`,
`## Role`, service/port map, storage, auto-deploy, `## Related`, `## Changelog`. `## Role`, service/port map, storage, auto-deploy, `## Related`, `## Changelog`.
- **Cross-cutting pages** (`knowledge/wiki/infrastructure/<topic>.md`) follow the cross-cutting - **Cross-cutting pages** (`archive/knowledge/infrastructure/<topic>.md`) follow the cross-cutting
template: `## Why`, `## Components`, `## How to apply`, `## Gotchas`, `## Related`, `## Changelog`. template: `## Why`, `## Components`, `## How to apply`, `## Gotchas`, `## Related`, `## Changelog`.
- Each `inventory.yaml` host entry carries a `doc_page:` field pointing at its narrative page. - 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`). Changing where a page lives means updating that field (read by `bin/homelab`).

View File

@@ -40,7 +40,7 @@ transition: "<from> -> <to>" # only for lifecycle runbooks
``` ```
`risk_class` values and the lifecycle `transition` states must match `risk_class` values and the lifecycle `transition` states must match
[`oikos/policy.yaml`](../../../oikos/policy.yaml) and [`oikos/ontology.yaml`](../../../oikos/ontology.yaml). [`seeds/policy.yaml`](../../../seeds/policy.yaml) and [`seeds/ontology.yaml`](../../../seeds/ontology.yaml).
## Investigations ## Investigations

View File

@@ -30,7 +30,7 @@ hostname doesn't match any inventory entry. Two fixes:
- **Rename the host**: `sudo hostnamectl set-hostname <inventory-name>` - **Rename the host**: `sudo hostnamectl set-hostname <inventory-name>`
(Linux) or System Preferences → Sharing (macOS), then re-run. (Linux) or System Preferences → Sharing (macOS), then re-run.
- **Rename the inventory entry**: edit `inventory.yaml` on hubris, - **Rename the inventory entry**: edit `inventory.yaml` on hubris,
regenerate `hosts/*.yaml`, push. The next sync (≤5 min) propagates. update `inventory.yaml`, push. The next sync (≤5 min) propagates.
### Getting onto Netbird ### Getting onto Netbird
@@ -40,7 +40,7 @@ Bootstrap auto-installs netbird and drives `netbird up` if the mesh isn't alread
The new client runs bootstrap straight from a fresh OS. Bootstrap installs netbird (apt/dnf/brew based on the OS), then runs `netbird up --management-url https://netbird.hubris.network --ssh-jwt-cache-ttl 86400`. A device-code URL prints inline. The operator opens it (in a browser logged into Authentik), goes through identification → password → consent, and the CLI returns `Connected`. Bootstrap then proceeds with the rest of preflight. The new client runs bootstrap straight from a fresh OS. Bootstrap installs netbird (apt/dnf/brew based on the OS), then runs `netbird up --management-url https://netbird.hubris.network --ssh-jwt-cache-ttl 86400`. A device-code URL prints inline. The operator opens it (in a browser logged into Authentik), goes through identification → password → consent, and the CLI returns `Connected`. Bootstrap then proceeds with the rest of preflight.
Pre-condition: the operator must be a registered user in Authentik (typically the lab owner). The first user-login against a netbird account with existing peers is added as `pending_approval=1` and needs an sqlite promotion to `owner` — see [124-authentik.md First-time owner promotion gotcha](../../knowledge/wiki/containers/106-auth-outpost.md). Only needed once per account. Pre-condition: the operator must be a registered user in Authentik (typically the lab owner). The first user-login against a netbird account with existing peers is added as `pending_approval=1` and needs an sqlite promotion to `owner` — see [124-authentik.md First-time owner promotion gotcha](../../archive/knowledge/containers/106-auth-outpost.md). Only needed once per account.
**Path A — setup-key (headless/scripted onboarding):** **Path A — setup-key (headless/scripted onboarding):**
@@ -62,7 +62,7 @@ Useful for headless servers (no browser at all) or unattended cloud-init bootstr
### DNS prerequisite ### DNS prerequisite
`*.hubris.network` resolves via the split-horizon dnsmasq on LXC 124 `*.hubris.network` resolves via the split-horizon dnsmasq on LXC 124
([dns.md](../../knowledge/wiki/infrastructure/dns.md)) for LAN clients, **but only if the ([dns.md](../../archive/knowledge/infrastructure/dns.md)) for LAN clients, **but only if the
client uses 192.168.8.180 as its resolver**. Most LXCs and roaming client uses 192.168.8.180 as its resolver**. Most LXCs and roaming
workstations don't by default. Options: workstations don't by default. Options:
@@ -285,7 +285,7 @@ homelab client add my-new-machine
# 4. On hubris: finalize the age public key. # 4. On hubris: finalize the age public key.
homelab client add my-new-machine --finalize-pubkey age1... homelab client add my-new-machine --finalize-pubkey age1...
# Updates inventory.yaml hosts.my-new-machine.age_pubkey, regenerates # Updates inventory.yaml hosts.my-new-machine.age_pubkey, regenerates
# hosts/*.yaml, commits + pushes. The 5-min sync propagates. # inventory.yaml, commits + pushes. The 5-min sync propagates.
``` ```
## Granting a secret to a new client ## Granting a secret to a new client
@@ -342,7 +342,7 @@ The CLI prints a follow-up checklist that the operator must do manually:
| `homelab` CLI doesn't pick up repo updates | Pre-`02db…` bootstrap copied the binary instead of symlinking | One-time migration: `sudo ln -sfn /opt/homelab-context/bin/homelab /usr/local/bin/homelab`. New bootstraps use the symlink, which auto-tracks the synced repo. | | `homelab` CLI doesn't pick up repo updates | Pre-`02db…` bootstrap copied the binary instead of symlinking | One-time migration: `sudo ln -sfn /opt/homelab-context/bin/homelab /usr/local/bin/homelab`. New bootstraps use the symlink, which auto-tracks the synced repo. |
| `homelab-context-sync.service` journal shows `fatal: could not read Username for 'https://git.hubris.network'` | Pre-fix bootstrap set the gitea credential helper via `git config --global`, which writes to `/root/.gitconfig` — invisible to the systemd timer's git process (no HOME set). | One-time migration: `sudo git config --system credential.helper "store --file=/etc/homelab-context/git-credentials"`. New bootstraps store the helper in `/etc/gitconfig` instead. | | `homelab-context-sync.service` journal shows `fatal: could not read Username for 'https://git.hubris.network'` | Pre-fix bootstrap set the gitea credential helper via `git config --global`, which writes to `/root/.gitconfig` — invisible to the systemd timer's git process (no HOME set). | One-time migration: `sudo git config --system credential.helper "store --file=/etc/homelab-context/git-credentials"`. New bootstraps store the helper in `/etc/gitconfig` instead. |
| Chat-mode `!` shell can't `sudo` (`a terminal is required to read the password`) | Claude Code's `!` invocation doesn't allocate a tty, and standard `sudo` won't read its password from stdin or a non-tty pipe. | Run the sudo'd command in a real terminal outside chat. For commands the agent issues repeatedly, configure passwordless sudo for the narrow set (e.g. `/etc/sudoers.d/homelab-self` with `<user> ALL=(ALL) NOPASSWD: /usr/bin/dnf upgrade -y, /usr/bin/apt-get *`). | | Chat-mode `!` shell can't `sudo` (`a terminal is required to read the password`) | Claude Code's `!` invocation doesn't allocate a tty, and standard `sudo` won't read its password from stdin or a non-tty pipe. | Run the sudo'd command in a real terminal outside chat. For commands the agent issues repeatedly, configure passwordless sudo for the narrow set (e.g. `/etc/sudoers.d/homelab-self` with `<user> ALL=(ALL) NOPASSWD: /usr/bin/dnf upgrade -y, /usr/bin/apt-get *`). |
| `netbird status -d` reports `192.168.8.180:53 ... is Unavailable` but DNS actually works | netbird's UDP-53 probe times out over the relay latency (~90ms), but actual queries still flow through systemd-resolved. Cosmetic. | Ignore unless `dig @192.168.8.180 git.hubris.network` also fails — then check dnsmasq on [LXC 124](../../knowledge/wiki/containers/106-auth-outpost.md). | | `netbird status -d` reports `192.168.8.180:53 ... is Unavailable` but DNS actually works | netbird's UDP-53 probe times out over the relay latency (~90ms), but actual queries still flow through systemd-resolved. Cosmetic. | Ignore unless `dig @192.168.8.180 git.hubris.network` also fails — then check dnsmasq on [LXC 124](../../archive/knowledge/containers/106-auth-outpost.md). |
| `netbird ssh` rejected with `JWT authentication failed: validate token (expected issuer=https://netbird.hubris.network/oauth2 ...)` | Peer's SSH JWT validator cached the OLD embedded-Dex issuer from before the 2026-05-21 Authentik migration. `systemctl restart netbird` and `netbird down/up` don't clear it — `client/internal/engine_ssh.go` bails out of `updateSSH()` if the SSH server is already running. | Full daemon bounce: `sudo systemctl stop netbird; sleep 3; sudo systemctl start netbird`. Verify with `grep -iE "issuer\|audience" /var/log/netbird/client.log \| tail`. Apply once per peer post-migration. | | `netbird ssh` rejected with `JWT authentication failed: validate token (expected issuer=https://netbird.hubris.network/oauth2 ...)` | Peer's SSH JWT validator cached the OLD embedded-Dex issuer from before the 2026-05-21 Authentik migration. `systemctl restart netbird` and `netbird down/up` don't clear it — `client/internal/engine_ssh.go` bails out of `updateSSH()` if the SSH server is already running. | Full daemon bounce: `sudo systemctl stop netbird; sleep 3; sudo systemctl start netbird`. Verify with `grep -iE "issuer\|audience" /var/log/netbird/client.log \| tail`. Apply once per peer post-migration. |
| `netbird ssh` JWT passes but session closes with `user privilege check failed: user dtoro not found: unknown user dtoro` | netbird-ssh defaults the remote username to the LOCAL one (operator's laptop user). Hubris and LXCs only have `root`. | Always use explicit `root@` prefix manually: `netbird ssh -p 22022 root@proxmox-server.netbird.selfhosted`. `homelab ssh <host>` does this automatically via `inventory.yaml`'s per-host `ssh.user` field (defaults to `root`). | | `netbird ssh` JWT passes but session closes with `user privilege check failed: user dtoro not found: unknown user dtoro` | netbird-ssh defaults the remote username to the LOCAL one (operator's laptop user). Hubris and LXCs only have `root`. | Always use explicit `root@` prefix manually: `netbird ssh -p 22022 root@proxmox-server.netbird.selfhosted`. `homelab ssh <host>` does this automatically via `inventory.yaml`'s per-host `ssh.user` field (defaults to `root`). |
| `homelab ssh hubris` (or any host on the LAN) fails with `Connection refused` or hangs, despite mesh routing being up | Off-LAN networks (operator on a VPN / coffee shop / symmetric NAT) sometimes can't reach the LAN IP even with the netbird subnet route. | Newer homelab CLIs probe the LAN with a 1.5s TCP connect and transparently fall back to the netbird FQDN. If your `/usr/local/bin/homelab` is a symlink to `/opt/homelab-context/bin/homelab` it'll pick up the fix on the next 5-min context sync. Otherwise pull the latest from gitea. | | `homelab ssh hubris` (or any host on the LAN) fails with `Connection refused` or hangs, despite mesh routing being up | Off-LAN networks (operator on a VPN / coffee shop / symmetric NAT) sometimes can't reach the LAN IP even with the netbird subnet route. | Newer homelab CLIs probe the LAN with a 1.5s TCP connect and transparently fall back to the netbird FQDN. If your `/usr/local/bin/homelab` is a symlink to `/opt/homelab-context/bin/homelab` it'll pick up the fix on the next 5-min context sync. Otherwise pull the latest from gitea. |

View File

@@ -1,6 +1,6 @@
# Operations cheatsheet # Operations cheatsheet
Run from the [hubris host](../../knowledge/wiki/hosts/hubris.md) as root. When working from `/root` on Linux you're already on hubris — don't `ssh hubris` / `ping hubris`. Run from the [hubris host](../../archive/knowledge/hosts/hubris.md) as root. When working from `/root` on Linux you're already on hubris — don't `ssh hubris` / `ping hubris`.
## Proxmox CLI ## Proxmox CLI
@@ -8,13 +8,13 @@ Run from the [hubris host](../../knowledge/wiki/hosts/hubris.md) as root. When w
| --- | --- | | --- | --- |
| `pct list` / `qm list` | List LXC containers / VMs | | `pct list` / `qm list` | List LXC containers / VMs |
| `pct config <id>` / `qm config <id>` | Container / VM config | | `pct config <id>` / `qm config <id>` | Container / VM config |
| `pct exec <id> -- <cmd>` | Run command inside an LXC without entering it (no initgroups — see [media permissions](../../knowledge/wiki/infrastructure/media-permissions.md)) | | `pct exec <id> -- <cmd>` | Run command inside an LXC without entering it (no initgroups — see [media permissions](../../archive/knowledge/infrastructure/media-permissions.md)) |
| `pct enter <id>` | Shell into a container | | `pct enter <id>` | Shell into a container |
| `pct start <id>` / `pct stop <id>` | Boot / halt a container | | `pct start <id>` / `pct stop <id>` | Boot / halt a container |
| `pvesm status` | Storage pools status | | `pvesm status` | Storage pools status |
| `pvesh get /nodes --output-format json` | Node summary as JSON | | `pvesh get /nodes --output-format json` | Node summary as JSON |
| `pvesh get /nodes/hubris/lxc/<id>/status/current` | Live container status | | `pvesh get /nodes/hubris/lxc/<id>/status/current` | Live container status |
| `pvesh get /cluster/resources --type vm --output-format json` | Bulk per-LXC CPU/mem/disk (used by the `homelab-health-watchdog` Hermes cron — see [monitoring](../../knowledge/wiki/infrastructure/monitoring.md); the old `claudio-monitor` this once fed is deprecated) | | `pvesh get /cluster/resources --type vm --output-format json` | Bulk per-LXC CPU/mem/disk (used by the `homelab-health-watchdog` Hermes cron — see [monitoring](../../archive/knowledge/infrastructure/monitoring.md); the old `claudio-monitor` this once fed is deprecated) |
| `pveversion` | PVE version | | `pveversion` | PVE version |
| `journalctl -u pve-cluster -n 100` | PVE service logs | | `journalctl -u pve-cluster -n 100` | PVE service logs |
@@ -22,22 +22,22 @@ Run from the [hubris host](../../knowledge/wiki/hosts/hubris.md) as root. When w
- Shared mount: `/mnt/library` (ext4 on lvmthin `library`). - Shared mount: `/mnt/library` (ext4 on lvmthin `library`).
- Bind into a container: `pct set <id> -mp<N> /mnt/library/<sub>,mp=/data` - Bind into a container: `pct set <id> -mp<N> /mnt/library/<sub>,mp=/data`
- For the standard whole-tree mount: `pct set <id> -mp0 /mnt/library,mp=/mnt/library`. See [media permissions](../../knowledge/wiki/infrastructure/media-permissions.md) for the GID-10000 onboarding recipe. - For the standard whole-tree mount: `pct set <id> -mp0 /mnt/library,mp=/mnt/library`. See [media permissions](../../archive/knowledge/infrastructure/media-permissions.md) for the GID-10000 onboarding recipe.
## Reverse proxy ## Reverse proxy
- Caddyfile: `/etc/caddy/Caddyfile` on [LXC 121](../../knowledge/wiki/containers/121-caddy.md). - Caddyfile: `/etc/caddy/Caddyfile` on [LXC 121](../../archive/knowledge/containers/121-caddy.md).
- **CRITICAL:** This file is tracked in `dtoro/caddy-conf` (https://git.hubris.network/dtoro/caddy-conf). Never edit it directly on the LXC — commit + push to the repo instead. Caddy auto-deploys on push (see [auto-deploy](../../knowledge/wiki/infrastructure/auto-deploy.md)). If you edit directly, the change will be lost on the next pull and agents won't know about it. - **CRITICAL:** This file is tracked in `dtoro/caddy-conf` (https://git.hubris.network/dtoro/caddy-conf). Never edit it directly on the LXC — commit + push to the repo instead. Caddy auto-deploys on push (see [auto-deploy](../../archive/knowledge/infrastructure/auto-deploy.md)). If you edit directly, the change will be lost on the next pull and agents won't know about it.
- Hot reload: `pct exec 121 -- systemctl reload caddy`. - Hot reload: `pct exec 121 -- systemctl reload caddy`.
- Validate: `pct exec 121 -- caddy validate --config /etc/caddy/Caddyfile`. - Validate: `pct exec 121 -- caddy validate --config /etc/caddy/Caddyfile`.
- Git workflow shortcut: `pct exec 121 -- "cd /etc/caddy && git add Caddyfile && git commit -m '...' && git push"`. - Git workflow shortcut: `pct exec 121 -- "cd /etc/caddy && git add Caddyfile && git commit -m '...' && git push"`.
## DNS ## DNS
- Split-horizon authority: [Technitium DNS](https://technitium.com) on [dns (107)](../../knowledge/wiki/containers/107-dns.md) at `192.168.8.2:53`. Web UI at `http://192.168.8.2`. (Formerly dnsmasq on the now-destroyed LXC 124 — decommissioned 2026-06-04.) - Split-horizon authority: [Technitium DNS](https://technitium.com) on [dns (107)](../../archive/knowledge/containers/107-dns.md) at `192.168.8.2:53`. Web UI at `http://192.168.8.2`. (Formerly dnsmasq on the now-destroyed LXC 124 — decommissioned 2026-06-04.)
- Add/edit records in the Technitium UI; the NetBird managed zone sync (`scripts/dns-sync.py` cron on 107) picks changes up within ~10 minutes. - Add/edit records in the Technitium UI; the NetBird managed zone sync (`scripts/dns-sync.py` cron on 107) picks changes up within ~10 minutes.
- Verify: `dig @192.168.8.2 +short <host>.hubris.network`. - Verify: `dig @192.168.8.2 +short <host>.hubris.network`.
- See [DNS](../../knowledge/wiki/infrastructure/dns.md). - See [DNS](../../archive/knowledge/infrastructure/dns.md).
## Web access ## Web access
@@ -49,7 +49,7 @@ Run from the [hubris host](../../knowledge/wiki/hosts/hubris.md) as root. When w
- `ras-mc-ctl --errors` — full event log - `ras-mc-ctl --errors` — full event log
- `cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference` — should be `balance_power` - `cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference` — should be `balance_power`
- `cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor` — should be `powersave` - `cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor` — should be `powersave`
- `ls /sys/fs/pstore/ /var/lib/systemd/pstore/` — panic traces from a previous crash (empty for pure hardware hangs — see [investigation](../../knowledge/sources/investigations/archive/2026-04-21-hubris-crash-loop.md)) - `ls /sys/fs/pstore/ /var/lib/systemd/pstore/` — panic traces from a previous crash (empty for pure hardware hangs — see [investigation](../../archive/knowledge/investigations/archive/2026-04-21-hubris-crash-loop.md))
## Fleet apt operations ## Fleet apt operations
@@ -80,12 +80,12 @@ See [OIKOS.md](../OIKOS.md) for the operating model. Quick reference:
| `homelab approval request\|list\|reply\|check` | Escalate-route grants (Matrix-delivered via Hermes, or the Oikos Console's `/approvals` page) | | `homelab approval request\|list\|reply\|check` | Escalate-route grants (Matrix-delivered via Hermes, or the Oikos Console's `/approvals` page) |
| `homelab restart <service> [--approval-id <id>]` | `--approval-id` is required whenever the service's risk class needs approval (e.g. `caddy`, `dns`) — refuses mechanically without a valid grant | | `homelab restart <service> [--approval-id <id>]` | `--approval-id` is required whenever the service's risk class needs approval (e.g. `caddy`, `dns`) — refuses mechanically without a valid grant |
Oikos Console (read-mostly dashboard): `oikos.hubris.network` once deployed — see [oikos/console/deploy/README.md](../../oikos/console/deploy/README.md). Oikos Console (read-mostly dashboard): `oikos.hubris.network` once deployed — see [oikos/console/deploy/README.md](../../archive/oikos-cards/).
## Related ## Related
- [Hubris host](../../knowledge/wiki/hosts/hubris.md) - [Hubris host](../../archive/knowledge/hosts/hubris.md)
- [Containers index](../../knowledge/wiki/containers/index.md) - [Containers index](../../archive/knowledge/containers/index.md)
- [DNS](../../knowledge/wiki/infrastructure/dns.md) - [DNS](../../archive/knowledge/infrastructure/dns.md)
- [Monitoring](../../knowledge/wiki/infrastructure/monitoring.md) - [Monitoring](../../archive/knowledge/infrastructure/monitoring.md)
- [Auto-deploy](../../knowledge/wiki/infrastructure/auto-deploy.md) - [Auto-deploy](../../archive/knowledge/infrastructure/auto-deploy.md)
- [Runbook: dpkg-interrupted recovery](../skills/runbook-dpkg-interrupted/SKILL.md) — what to do when apt got killed mid-transaction - [Runbook: dpkg-interrupted recovery](../skills/runbook-dpkg-interrupted/SKILL.md) — what to do when apt got killed mid-transaction

View File

@@ -1,12 +1,12 @@
# LLM Wiki — the documentation contract # LLM Wiki — the documentation contract
How the narrative documentation in this repo is organized. The pattern is borrowed from the How the narrative documentation in this repo is organized. The pattern is borrowed from the
`sources / wiki / index / log` model: a durable synthesized layer (`knowledge/wiki/`) built on top `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 of immutable evidence (`knowledge/sources/`, incident records), with pure-listing indexes and an
append-only operations log. append-only operations log.
This contract governs the **narrative layer only**. The machine-readable substrate — `inventory.yaml`, This contract governs the **narrative layer only**. The machine-readable substrate — `inventory.yaml`,
generated `hosts/*.yaml`, `oikos/`, `mcp/`, `secrets/`, `bin/` — is not part of the wiki and never `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. moves under it. See [the knowledge schema](../domains/knowledge/schema.md) for the split.
## Layers ## Layers
@@ -14,7 +14,7 @@ moves under it. See [the knowledge schema](../domains/knowledge/schema.md) for t
- **Sources** are immutable raw material: incident records (`knowledge/sources/investigations/`), external reference - **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`). docs (`knowledge/sources/references/`), and the live system itself (`pct config`, `docker inspect`).
Read them; do not rewrite them into other sources. Read them; do not rewrite them into other sources.
- **Wiki** (`knowledge/wiki/`) is the synthesized, authoritative current-state layer: one page per - **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 node (`containers/`, `vms/`, host narratives) and per cross-cutting system (`infrastructure/`). A
reader understands the topic from the wiki page without reading the sources. reader understands the topic from the wiki page without reading the sources.
- **Index** (`index.md` / folder `README.md`) is a pure listing — every page in scope with a - **Index** (`index.md` / folder `README.md`) is a pure listing — every page in scope with a

View File

@@ -160,7 +160,7 @@ session.** A change that touches a container page must also update:
- The Caddy page site list (if the change affects `*.hubris.network` routing) - The Caddy page site list (if the change affects `*.hubris.network` routing)
- The DNS / ingress infrastructure pages (if the change affects routing) - The DNS / ingress infrastructure pages (if the change affects routing)
- The `hosts/{hubris,strong}.md` host page (if container count changes) - The `hosts/{hubris,strong}.md` host page (if container count changes)
- The `inventory.yaml` host entry (source of truth for the `hosts/*.yaml` generation) - The `inventory.yaml` host entry (single source of truth)
- The `infrastructure/topology.md` (generated from inventory, but regen if needed) - The `infrastructure/topology.md` (generated from inventory, but regen if needed)
The pattern of updating only one page and leaving stale references on others The pattern of updating only one page and leaving stale references on others

View File

@@ -16,7 +16,7 @@ the full walkthrough; this runbook is the risk/lifecycle framing.
1. On any enrolled client: `homelab client add <hostname>` — appends a 1. On any enrolled client: `homelab client add <hostname>` — appends a
`hosts.<name>:` block to `inventory.yaml` (lifecycle `state: planned` `hosts.<name>:` block to `inventory.yaml` (lifecycle `state: planned`
`provisioning`, per [oikos/ontology.yaml](../../../oikos/ontology.yaml)), `provisioning`, per [seeds/ontology.yaml](../../../seeds/ontology.yaml)),
commits + pushes. commits + pushes.
2. Netbird join is **optional, not a required step** — only needed for 2. Netbird join is **optional, not a required step** — only needed for
hosts that must be reachable off-LAN (workstations that roam, e.g. hosts that must be reachable off-LAN (workstations that roam, e.g.
@@ -40,4 +40,4 @@ Docs-update checklist: if the new host is an LXC/VM, add its narrative
page under `containers/` or `vms/` and set `doc_page` in its inventory page under `containers/` or `vms/` and set `doc_page` in its inventory
entry (host-level cards don't have a `doc_page` field yet — services do; entry (host-level cards don't have a `doc_page` field yet — services do;
narrative pages are still found via the generated `see_also` in narrative pages are still found via the generated `see_also` in
`hosts/<name>.yaml`). `inventory.yaml`).

View File

@@ -15,13 +15,13 @@ own repo) and get it live, safely.
`config_repo`, its risk class, and the verification command to run `config_repo`, its risk class, and the verification command to run
after. If risk class requires approval (`config_mutation` or after. If risk class requires approval (`config_mutation` or
`destructive`), stop and get operator sign-off before editing — see `destructive`), stop and get operator sign-off before editing — see
`oikos/policy.yaml`. `seeds/policy.yaml`.
2. Clone/pull the `config_repo` (never edit the backend's working tree 2. Clone/pull the `config_repo` (never edit the backend's working tree
directly — tracked configs change by commit + push, per directly — tracked configs change by commit + push, per
[OIKOS.md](../../OIKOS.md) conventions). [OIKOS.md](../../OIKOS.md) conventions).
3. Make the change, commit, push to `main`. 3. Make the change, commit, push to `main`.
4. The Gitea webhook fires the deploy pipeline for that repo (see 4. The Gitea webhook fires the deploy pipeline for that repo (see
[infrastructure/auto-deploy.md](../../../knowledge/wiki/infrastructure/auto-deploy.md) for [infrastructure/auto-deploy.md](../../../archive/knowledge/infrastructure/auto-deploy.md) for
the exact receiver/reload for this service). the exact receiver/reload for this service).
5. Run the preflight's verification command. If it fails, check 5. Run the preflight's verification command. If it fails, check
`homelab service <name> log` for the reload/restart error. `homelab service <name> log` for the reload/restart error.

View File

@@ -15,11 +15,11 @@ links. Prose-voice rules are not machine-checkable — those stay a review respo
Run from the repo root: Run from the repo root:
python3 .agents/skills/docs-lint/lint.py # default: knowledge/ .agents/ operations/ investigations/ plans/ python3 .agents/skills/docs-lint/lint.py # default: knowledge/ .agents/ operations/ investigations/ plans/
python3 .agents/skills/docs-lint/lint.py knowledge/wiki/containers/104-gitea.md python3 .agents/skills/docs-lint/lint.py archive/knowledge/containers/104-gitea.md
Exit code is non-zero when any violation is found, so it can gate a commit. The banned-vocabulary Exit code is non-zero when any violation is found, so it can gate a commit. The banned-vocabulary
list mirrors `writing-style.md`; update both together if the standard changes. list mirrors `writing-style.md`; update both together if the standard changes.
> **Known baseline.** `knowledge/wiki/containers/101-jellyfin.md` links into a sibling repo > **Known baseline.** `archive/knowledge/archive/knowledge/containers/101-jellyfin.md` links into a sibling repo
> (`devops/homelab-authentik-admin`) that this checkout does not contain — expected, not a bug. > (`devops/homelab-authentik-admin`) that this checkout does not contain — expected, not a bug.
> Any other broken link is a real regression; investigate before dismissing it as baseline noise. > Any other broken link is a real regression; investigate before dismissing it as baseline noise.

View File

@@ -9,7 +9,7 @@ transition: "provisioning -> active"
# Lifecycle: activate a node # Lifecycle: activate a node
Per [oikos/ontology.yaml](../../../oikos/ontology.yaml). Requires: age key Per [seeds/ontology.yaml](../../../seeds/ontology.yaml). Requires: age key
enrolled if it needs secrets, mesh joined if it needs off-LAN reach, enrolled if it needs secrets, mesh joined if it needs off-LAN reach,
ingress live if public, health check answering, doc page complete, ingress live if public, health check answering, doc page complete,
ledger entry. ledger entry.
@@ -32,5 +32,5 @@ ledger entry.
Week 3's runbook automation lands). Week 3's runbook automation lands).
Regenerate derived data: `python3 mcp/build_host_files.py && python3 Regenerate derived data: `python3 mcp/build_host_files.py && python3
oikos/gen-topology.py` so `hosts/<name>.yaml`, the topology diagram, and inventory.yaml` so `inventory.yaml`, the topology diagram, and
the context card all reflect the new state. the context card all reflect the new state.

View File

@@ -9,10 +9,10 @@ transition: "active -> deprecated"
# Lifecycle: deprecate a node # Lifecycle: deprecate a node
Per [oikos/ontology.yaml](../../../oikos/ontology.yaml): a node keeps running Per [seeds/ontology.yaml](../../../seeds/ontology.yaml): a node keeps running
but takes no new dependents. **Completion condition: zero remaining but takes no new dependents. **Completion condition: zero remaining
inbound `depends-on`/`routes-to` edges** — this is a hard gate, not a inbound `depends-on`/`routes-to` edges** — this is a hard gate, not a
suggestion; `oikos/policy.yaml` `lifecycle_overrides.deprecated.refuse` suggestion; `seeds/policy.yaml` `lifecycle_overrides.deprecated.refuse`
lists `new-inbound-edges` as refused going forward. lists `new-inbound-edges` as refused going forward.
1. Set `state: deprecated` on the node. 1. Set `state: deprecated` on the node.

View File

@@ -10,7 +10,7 @@ transition: "deprecated -> destroyed"
# Lifecycle: destroy a node # Lifecycle: destroy a node
**Destructive.** Requires operator approval + typed confirmation phrase **Destructive.** Requires operator approval + typed confirmation phrase
per `oikos/policy.yaml`. Requires (ontology): backups verified, secrets per `seeds/policy.yaml`. Requires (ontology): backups verified, secrets
recipients removed + re-keyed, ingress/DNS removed, archaeology entry, recipients removed + re-keyed, ingress/DNS removed, archaeology entry,
ledger entry. ledger entry.
@@ -33,7 +33,7 @@ ledger entry.
data). data).
7. `oikos/ledger.py append host:<name> destroy destructive --result ok`. 7. `oikos/ledger.py append host:<name> destroy destructive --result ok`.
8. Regenerate: `python3 mcp/build_host_files.py && python3 8. Regenerate: `python3 mcp/build_host_files.py && python3
oikos/gen-topology.py` — the node drops out of `hosts/*.yaml` and inventory.yaml` — the node drops out of `inventory.yaml` and
appears in the topology doc's archaeology table. appears in the topology doc's archaeology table.
If the destroy fails partway (e.g. secrets revoked but pct destroy If the destroy fails partway (e.g. secrets revoked but pct destroy

View File

@@ -32,8 +32,8 @@ before returning to `active`.
7. `homelab service <name> health` for every service the node hosts. 7. `homelab service <name> health` for every service the node hosts.
8. Set `state: active`. Add a migration note to the node's doc page 8. Set `state: active`. Add a migration note to the node's doc page
(old host/IP → new, date, phase reference) — this repo's convention (old host/IP → new, date, phase reference) — this repo's convention
for every past migration (see `containers/101-jellyfin.md`, for every past migration (see `archive/knowledge/containers/101-jellyfin.md`,
`containers/129-house.md`). `containers/129-house.md`).
Regenerate: `python3 mcp/build_host_files.py && python3 Regenerate: `python3 mcp/build_host_files.py && python3
oikos/gen-topology.py`. inventory.yaml`.

View File

@@ -2,17 +2,17 @@
name: lifecycle-provision-node name: lifecycle-provision-node
risk_class: config_mutation risk_class: config_mutation
inputs: [node_name, kind, storage_pool] inputs: [node_name, kind, storage_pool]
verification: "grep 'state: provisioning' hosts/<name>.yaml" verification: "grep 'state: provisioning' inventory.yaml"
docs_update_checklist: [doc_page_stub] docs_update_checklist: [doc_page_stub]
transition: "planned -> provisioning" transition: "planned -> provisioning"
--- ---
# Lifecycle: provision a node # Lifecycle: provision a node
Per [oikos/ontology.yaml](../../../oikos/ontology.yaml) `lifecycle.transitions`. Per [seeds/ontology.yaml](../../../seeds/ontology.yaml) `lifecycle.transitions`.
Policy note: `provisioning` nodes get a lifecycle override — Policy note: `provisioning` nodes get a lifecycle override —
`config_mutation` actions downgrade to `reversible_low` because nothing `config_mutation` actions downgrade to `reversible_low` because nothing
depends on the node yet (see `oikos/policy.yaml` `lifecycle_overrides`). depends on the node yet (see `seeds/policy.yaml` `lifecycle_overrides`).
Requires (from ontology): inventory entry, IP reserved, storage pool Requires (from ontology): inventory entry, IP reserved, storage pool
chosen, doc page stub. chosen, doc page stub.

View File

@@ -103,8 +103,8 @@ Then `systemctl status apt-recovery` from a fresh ssh to check progress.
## Related ## Related
- [Operations cheatsheet](../../operations/commands.md) - [Operations cheatsheet](../../operations/commands.md)
- [Auto-deploy pipelines](../../../knowledge/wiki/infrastructure/auto-deploy.md) - [Auto-deploy pipelines](../../../archive/knowledge/infrastructure/auto-deploy.md)
- [Hubris host page](../../../knowledge/wiki/hosts/hubris.md) - [Hubris host page](../../../archive/knowledge/hosts/hubris.md)
## Changelog ## Changelog

View File

@@ -20,7 +20,7 @@ Goal: determine whether a service is actually healthy, without ad-hoc SSH.
4. Cross-check blast radius: `homelab node <name> relations` — is this 4. Cross-check blast radius: `homelab node <name> relations` — is this
entity's own backend host healthy? A downstream failure (e.g. `strong` entity's own backend host healthy? A downstream failure (e.g. `strong`
down) will show up here before the service's own logs explain anything. down) will show up here before the service's own logs explain anything.
5. If the fix is a restart: classify first (`oikos/policy.yaml` 5. If the fix is a restart: classify first (`seeds/policy.yaml`
`service-restart` is `reversible_low` unless the service has a `service-restart` is `reversible_low` unless the service has a
`service_overrides` entry, e.g. `caddy`/`dns` are `config_mutation`). `service_overrides` entry, e.g. `caddy`/`dns` are `config_mutation`).
Unattended agents may act on `reversible_low` without approval. Unattended agents may act on `reversible_low` without approval.

107
AGENTS.md
View File

@@ -6,17 +6,22 @@ point. Read it once at start, then keep working.
The operating model — OODA loop, risk classes, approval rules, the ontology, The operating model — OODA loop, risk classes, approval rules, the ontology,
and node lifecycle — is defined in [OIKOS.md](.agents/OIKOS.md). Before any mutation, and node lifecycle — is defined in [OIKOS.md](.agents/OIKOS.md). Before any mutation,
classify the action against `oikos/policy.yaml`; when the class requires classify the action against `seeds/policy.yaml`; when the class requires
approval, stop and ask the operator. approval, stop and ask the operator.
Agent-facing instruction is separated from human content under `.agents/`: Agent-facing instruction lives under `.agents/`:
`.agents/shared/` holds the conventions every agent applies `.agents/shared/` holds the conventions every agent applies
([writing-style](.agents/shared/writing-style.md), [caveman](.agents/shared/caveman.md), ([writing-style](.agents/shared/writing-style.md), [caveman](.agents/shared/caveman.md),
[page-templates](.agents/shared/page-templates.md), [llm-wiki](.agents/shared/llm-wiki.md)), and [page-templates](.agents/shared/page-templates.md), [llm-wiki](.agents/shared/llm-wiki.md)), and
`.agents/domains/` holds the per-domain schemas `.agents/domains/` holds the per-domain schemas
([knowledge](.agents/domains/knowledge/schema.md), [operations](.agents/domains/operations/schema.md)). ([knowledge](.agents/domains/knowledge/schema.md), [operations](.agents/domains/operations/schema.md)).
The narrative wiki lives under `knowledge/wiki/`; the machine-readable substrate
(`inventory.yaml` stays at the repo root. **Source of truth:** The Postgres database is the single source of truth for all
structured data and knowledge. It is bootstrapped from `seeds/` at deploy time:
`seeds/ontology.yaml` (entity types, relationships, lifecycles), `seeds/inventory.yaml`
(hosts, services, entities), `seeds/policy.yaml` (risk classes, approval rules), and
`seeds/knowledge.yaml` (documents, investigations, runbooks). The old narrative wiki
is archived at `archive/knowledge/` for historical reference.
## 1. Who you are ## 1. Who you are
@@ -31,91 +36,86 @@ the operator to run `homelab client add <hostname>` from an existing client.
## 2. The topology ## 2. The topology
- `/opt/homelab-context/inventory.yaml` — every host, LXC, VM, and workstation - `/opt/homelab-context/inventory.yaml` — every host, LXC, VM, and workstation
with their mesh addresses, roles, and service mappings. Treat this file as with their mesh addresses, roles, and service mappings. This is the seed file;
authoritative; anything you read in narrative pages should agree with it. at runtime the DB is authoritative (query via MCP `get_entity` or the REST API).
- `/opt/homelab-context/knowledge/wiki/infrastructure/mesh.md` — Tailscale → Netbird state. - `/opt/homelab-context/seeds/knowledge.yaml` — full narrative knowledge: 36
Both meshes are accepted today; Netbird is preferred for new traffic. documents, 6 investigations, 12 runbooks. Ingested into the DB on deploy.
- `/opt/homelab-context/knowledge/wiki/infrastructure/dns.md` — split-horizon DNS via
Technitium on [dns (107)](knowledge/wiki/containers/107-dns.md). `*.hubris.network`
resolves to 192.168.x.x on the LAN and to mesh addresses off-LAN.
- `/opt/homelab-context/.agents/operations/commands.md` — the operator's cheatsheet - `/opt/homelab-context/.agents/operations/commands.md` — the operator's cheatsheet
for pct, caddy, DNS, and the Oikos command surface. Use these verbs when for pct, caddy, DNS, and the Oikos command surface.
you take actions.
## 3. The MCP server ## 3. The MCP server
The homelab exposes a Model Context Protocol server with structured tools. The homelab exposes a Model Context Protocol server with structured tools.
Endpoint is in `inventory.yaml` under `services.homelab_mcp.endpoint`. Endpoint: `https://mcp.hubris.network/mcp`.
Available tools: Available tools:
Context (pure read): Context (read-only):
get_host(name), list_services(), find_service(name_or_role), get_entity(slug), list_entities(type, limit, cursor),
get_topology(), search_docs(query), get_page(path), get_relations(entity), get_blast_radius(entity),
get_changelog(page, since?), whoami(hostname), search_knowledge(query) — full-text search over documents, investigations,
list_my_secrets(caller_pubkey?) runbooks (PostgreSQL FTS, replaces the old Python search_docs)
get_entity_knowledge(slug) — all documents, investigations, and runbooks
linked to an entity
get_topology(), whoami(hostname), list_my_secrets(caller_pubkey?)
Management (read-only): Management (read-only):
get_service_status(service), tail_log(service, lines=200), get_service_status(service), tail_log(service, lines=200),
list_lxcs(), get_lxc_state(lxc), ping_service(service) list_lxcs(), get_lxc_state(lxc), ping_service(service)
Oikos (read-only; see OIKOS.md): Oikos (read-only; see OIKOS.md):
explain(service) — compact context card, cheaper than search_docs+get_page explain(service) — compact context card
preflight(service) — risk class, approval requirement, verification command preflight(service) — risk class, approval requirement, verification command
get_relations(entity) — ontology blast-radius query (host: or service: id)
get_change_history(entity, limit=20) — change-ledger entries get_change_history(entity, limit=20) — change-ledger entries
get_state_snapshot() — last scheduler Observe-pass (health, disk, drift count) get_state_snapshot() — last scheduler Observe-pass (health, disk, drift count)
Mutations are **not** exposed via MCP. Use the `homelab` CLI for those, with Mutations are **not** exposed via MCP. Use the `homelab` CLI for those, with
operator confirmation — see OIKOS.md's risk classes and approval flow. operator confirmation — see OIKOS.md's risk classes and approval flow.
**When to prefer MCP over grepping the clone:** any time you need to resolve a **When to prefer MCP over grepping the clone:** always for knowledge queries.
name to an address, look up service status, or search the wiki by content. `search_knowledge("jellyfin hardware acceleration")` returns ranked results from
Grep is fine for browsing or when MCP is unreachable. the DB with entity links. `get_entity_knowledge("lxc:jellyfin")` returns documents,
runbooks, and investigations in one call. Grep the clone only when MCP is
unreachable.
## 4. Wiki conventions ## 4. Knowledge conventions
See [page-templates.md](.agents/shared/page-templates.md) for file naming, page All narrative knowledge (documents, investigations, runbooks) lives in the DB
structure, and the tone standard. Quick reference: (`knowledge_entities` table) and is seeded from `seeds/knowledge.yaml`. Agents
can register new knowledge via the API:
- **File naming:** Foundational docs are ALL-CAPS (AGENTS.md, OIKOS.md, GLOSSARY.md); ```
containers use `<id>-<name>.md`; infrastructure uses lowercase-with-dashes; plans POST /api/v1/knowledge/{entity_slug}
and investigations use `YYYY-MM-DD-slug.md`; skills are `<name>/SKILL.md`. {"title": "...", "content": "...", "tags": ["..."]}
- **Where pages live:** Narrative under `knowledge/wiki/{containers,hosts,vms,infrastructure}/`; ```
incident records under `knowledge/sources/investigations/`; runbook procedures under
`.agents/skills/<name>/SKILL.md`; operator reference under `.agents/operations/`;
design docs under `plans/`. Cross-link liberally; orphans are bugs.
- **Changelog format:** Every page ends with a `## Changelog` section, entries in
reverse-chronological order:
### YYYY-MM-DD — short title The DB is the truth. The old wiki files are archived at `archive/knowledge/` for
one or two lines describing what changed and why. historical reference.
- **Runbook procedures** live as `runbook` entities in the DB and as SKILL.md
files under `.agents/skills/<name>/`. They carry `risk_class`, `procedure`
(JSON-schema-validated), and are linked to entity types via `applies_to_type`.
- **Investigations** are `investigation` entities linked to affected entities
via `about` edges.
- **Documents** are `document` entities linked to entities via `documents` edges.
They carry `at_glance` (structured attributes) and `changelog` (parsed entries).
- **Live state precedence.** If you observe a discrepancy between the docs and - **Live state precedence.** If you observe a discrepancy between the docs and
running state, update the docs *in the same session* (per the same-session update running state, update the DB *in the same session* via the API. The `oikos export`
rule in [page-templates.md](.agents/shared/page-templates.md#same-session-update-rule)). command regenerates `seeds/knowledge.yaml` for version control.
## 5. Acting on the homelab ## 5. Acting on the homelab
- **Read state**: prefer MCP tools, then files, then shell. Examples: - **Read state**: prefer MCP tools, then files, then shell. Examples:
`homelab whoami`, `homelab list`, `homelab status`, `homelab logs caddy`. `homelab whoami`, `homelab list`, `homelab status`, `homelab logs caddy`.
- **Cross-host actions** (caddy reload, pct exec, etc.): use the `homelab` - **Cross-host actions** (caddy reload, pct exec, etc.): use the `homelab`
CLI — it resolves hostname → mesh address → ssh / pct path for you. Direct CLI — it resolves hostname → mesh address → ssh / pct path for you.
SSH still works; the CLI just removes the lookup burden.
- **Secrets**: never hardcode. Call `homelab secret <name>` to decrypt on - **Secrets**: never hardcode. Call `homelab secret <name>` to decrypt on
demand using the per-client age key at `/etc/age/key.txt`. Secrets ARE demand using the per-client age key at `/etc/age/key.txt`.
available in this system — `list_my_secrets()` (MCP) shows what you can
decrypt.
- **Mutations** (restart, edit configs, etc.): classify against - **Mutations** (restart, edit configs, etc.): classify against
`oikos/policy.yaml` first (`homelab decide <action> <entity>`). `seeds/policy.yaml` first (`homelab decide <action> <entity>`).
`reversible_low` actions just need the interactive confirmation prompt; `reversible_low` actions just need the interactive confirmation prompt;
`config_mutation`/`destructive` actions are mechanically refused without `config_mutation`/`destructive` actions are mechanically refused without
a valid `--approval-id` from `homelab approval request` — see OIKOS.md. a valid `--approval-id` from `homelab approval request` — see OIKOS.md.
For ad-hoc work, SSH and edit directly — but commit changes that touch
tracked configs (caddy, gitea custom, artifacto, mule-image, etc.; see
`knowledge/wiki/infrastructure/auto-deploy.md`).
- **Wiki updates**: same-session rule applies to any meaningful state change
this client makes.
## 6. Communication mode ## 6. Communication mode
@@ -145,5 +145,6 @@ To trigger sync manually: `sudo homelab sync` or wait for the 5-min timer.
## 8. When in doubt ## 8. When in doubt
Run `homelab mcp search_docs <query>` or `homelab mcp get_host <name>`. Use MCP tools: `search_knowledge <query>` for narrative context,
The clone is the fallback; MCP is the index. `get_entity <slug>` for structured data, `get_entity_knowledge <slug>` for
everything linked to an entity. The clone is the fallback; MCP is the index.

View File

@@ -108,7 +108,7 @@ seeds/ Bootstrap YAML (ontology, inventory, policy)
compose/ Dockerfiles + Caddy config compose/ Dockerfiles + Caddy config
scripts/ Deploy, watchdog, verification, rollback scripts/ Deploy, watchdog, verification, rollback
hermes/ Hermes config, persona, skills hermes/ Hermes config, persona, skills
knowledge/wiki/ Narrative documentation (containers, hosts, infrastructure) archive/knowledge/ Narrative documentation (containers, hosts, infrastructure)
.agents/ Agent instruction files + skills .agents/ Agent instruction files + skills
plans/ Design documents plans/ Design documents
``` ```
@@ -119,7 +119,7 @@ See [AGENTS.md](AGENTS.md) for the full orientation. Quick reference:
- **Source of truth:** DB (runtime) then repo (bootstrap + docs) - **Source of truth:** DB (runtime) then repo (bootstrap + docs)
- **Mutations:** classify against policy, request approval for `destructive`/`config_mutation` - **Mutations:** classify against policy, request approval for `destructive`/`config_mutation`
- **Wiki:** files under `knowledge/wiki/`, changelog at bottom of each page - **Wiki:** files under `archive/knowledge/`, changelog at bottom of each page
- **Secrets:** Infisical (primary) or SOPS (fallback) — never hardcode - **Secrets:** Infisical (primary) or SOPS (fallback) — never hardcode
## Related ## Related

View File

@@ -18,7 +18,7 @@
# - OS is Linux or macOS # - OS is Linux or macOS
# - git, age, sops are installed # - git, age, sops are installed
# - at least one mesh (netbird OR tailscale) is connected # - at least one mesh (netbird OR tailscale) is connected
# - this host has an `hosts/<hostname>.yaml` entry in the repo (or refuses) # - this host has an `inventory.yaml` entry in the repo (or refuses)
set -euo pipefail set -euo pipefail

View File

@@ -39,4 +39,4 @@ Status: [x] = done, [ ] = pending
- [ ] Remove Gitea webhooks for apps/105 (ids 10, 11) from `dtoro/Homelab-Docs` - [ ] Remove Gitea webhooks for apps/105 (ids 10, 11) from `dtoro/Homelab-Docs`
- [ ] Archive apps/105 LXC (keep for 30 days, then destroy) - [ ] Archive apps/105 LXC (keep for 30 days, then destroy)
- [x] Update `knowledge/wiki/infrastructure/auto-deploy.md` — apps/105 entries marked deprecated - [x] Update auto-deploy docs — apps/105 entries marked deprecated

View File

@@ -48,7 +48,7 @@ fi
## Source of truth ## Source of truth
The homelab-context repo at `/opt/homelab-context/` is the single source of truth for: The homelab-context repo at `/opt/homelab-context/` is the single source of truth for:
- Fleet topology (`inventory.yaml`, `hosts/*.yaml`) - Fleet topology (`inventory.yaml`, `inventory.yaml`)
- Service endpoints and credentials - Service endpoints and credentials
- Agent behaviour and conventions - Agent behaviour and conventions