Phase 1: cross-client homelab context + MCP scaffolding

Add the foundation for distributing homelab context to every client
(LXCs, VMs, workstations including republic-laptop, mac-mini, ludo-mini)
with a single source of truth, structured query layer (MCP), and per-client
age-key issuance for secrets:

- inventory.yaml — canonical topology (hosts, services, mesh addresses)
- hosts/*.yaml — per-host identity files generated from inventory by
  mcp/build_host_files.py; do not edit by hand
- AGENTS.md — orientation doc symlinked to /root/AGENTS.md on every client
- bootstrap.sh — one-shot enroll (Linux + macOS), clones repo, fetches age
  key from issuance, installs sync timer/launchd job, drops the homelab CLI
- bin/homelab — single-binary Python CLI: whoami, list, ssh, pct, logs,
  restart, open, status, secret, sync, mcp, client add/remove, nuke
- mcp/server.py — FastMCP server: context tools + read-only management
  tools (no mutations exposed); shell-outs use mcp-reader restricted ssh key
- mcp/deploy/ — claudio-monitor-style gitea webhook deploy scaffold for the
  MCP service on LXC 105 (ports 9810 mcp, 9811 webhook)
- secrets-issuance/ — per-client age key auto-provisioning over the mesh;
  source-IP gated against inventory, with denylist for revoked clients
  (ports 9820 issue, 9821 webhook)
- secrets/, .sops.yaml — SOPS recipient scaffolding; the operator fills in
  age public keys after Phase 3a generates them
- scripts/sync/ — systemd timer (Linux) + launchd plist (macOS) pulling
  /opt/homelab-context every 5 min

Mesh: both Netbird (preferred, 100.122.0.0/16) and Tailscale accepted
during the in-flight migration; no client is gated on completing the move.

Plan reference: /root/.claude/plans/lets-make-a-plan-fluttering-trinket.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-05-20 15:47:48 +02:00
parent 8f598a0e7a
commit 3c25f936d3
44 changed files with 3180 additions and 0 deletions

50
hosts/apps.yaml Normal file
View File

@@ -0,0 +1,50 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: apps
kind: lxc
os: linux
role: docker-apps
host: hubris
pve_id: 105
lan_ip: 192.168.8.205
mesh:
netbird:
fqdn: apps.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library
public_hosts:
- artifacto.hubris.network
runs:
- artifacto
- plantuml
- homelab-mcp
- secrets-issuance
- artifacto
- homelab_mcp
- secrets_issuance
services_hosted:
- name: artifacto
backend: apps
url: https://artifacto.hubris.network
- name: homelab_mcp
backend: apps
port: 9810
netbird_host: apps.netbird.selfhosted
endpoint: http://apps.netbird.selfhosted:9810/sse
note: MCP server. Read-only context + management. Mesh-only.
- name: secrets_issuance
backend: apps
port: 9820
netbird_host: apps.netbird.selfhosted
endpoint: http://apps.netbird.selfhosted:9820/issue
note: Issues per-client age private keys. Mesh-only.
see_also:
- containers/105-apps.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

34
hosts/arriman.yaml Normal file
View File

@@ -0,0 +1,34 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: arriman
kind: lxc
os: linux
role: arr-stack
host: hubris
pve_id: 122
lan_ip: 192.168.8.132
mesh:
netbird:
fqdn: arriman.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library
public_hosts:
- jellyseerr.hubris.network
- qbit.hubris.network
- sab.hubris.network
runs:
- arr_stack
services_hosted:
- name: arr_stack
backend: arriman
note: jellyseerr / qbit / sab on docker compose
see_also:
- containers/122-arriman.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

35
hosts/authentik.yaml Normal file
View File

@@ -0,0 +1,35 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: authentik
kind: lxc
os: linux
role: idp
host: hubris
pve_id: 124
lan_ip: 192.168.8.180
mesh:
netbird:
fqdn: authentik.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
public_host: auth.hubris.network
runs:
- authentik
- dnsmasq
services_hosted:
- name: authentik
url: https://auth.hubris.network
backend: authentik
- name: dnsmasq
backend: authentik
note: split-horizon DNS, /etc/dnsmasq.d/hubris-split.conf
notes:
- 'Also hosts split-horizon dnsmasq: /etc/dnsmasq.d/hubris-split.conf'
see_also:
- containers/124-authentik.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

35
hosts/caddy.yaml Normal file
View File

@@ -0,0 +1,35 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: caddy
kind: lxc
os: linux
role: reverse-proxy
host: hubris
pve_id: 121
lan_ip: 192.168.8.175
mesh:
netbird:
fqdn: caddy.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
peers:
- authentik
- gitea
runs:
- caddy
services_hosted:
- name: caddy
backend: caddy
role: reverse-proxy
note: terminates all *.hubris.network
notes:
- Terminates all *.hubris.network
- /etc/caddy is a git checkout of dtoro/caddy-conf
see_also:
- containers/121-caddy.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

24
hosts/claudio-bot.yaml Normal file
View File

@@ -0,0 +1,24 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: claudio-bot
kind: lxc
os: linux
role: matrix-agent
host: hubris
pve_id: 123
lan_ip: 192.168.8.230
mesh:
netbird:
fqdn: claudio-bot.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
notes:
- Reads /opt/homelab-context/ on startup
see_also:
- containers/123-claudio-bot.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

29
hosts/elementsynapse.yaml Normal file
View File

@@ -0,0 +1,29 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: elementsynapse
kind: lxc
os: linux
role: matrix-server
host: hubris
pve_id: 118
lan_ip: 192.168.8.239
mesh:
netbird:
fqdn: elementsynapse.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
public_host: matrix.hubris.network
runs:
- matrix
services_hosted:
- name: matrix
url: https://matrix.hubris.network
backend: elementsynapse
see_also:
- containers/118-elementsynapse.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

34
hosts/gitea.yaml Normal file
View File

@@ -0,0 +1,34 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: gitea
kind: lxc
os: linux
role: git-server
host: hubris
pve_id: 104
lan_ip: 192.168.8.121
mesh:
netbird:
fqdn: gitea.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library
public_host: git.hubris.network
runs:
- gitea
services_hosted:
- name: gitea
url: https://git.hubris.network
backend: gitea
backend_url: http://192.168.8.121:3000
notes:
- Bare repos live at /mnt/library/repos/dtoro/*.git
see_also:
- containers/104-gitea.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

26
hosts/haos.yaml Normal file
View File

@@ -0,0 +1,26 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: haos
kind: vm
os: linux
role: home-automation
host: hubris
pve_id: 108
mesh:
netbird:
fqdn: haos.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
runs:
- haos
services_hosted:
- name: haos
backend: haos
see_also:
- vms/108-haos.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

33
hosts/hubris.yaml Normal file
View File

@@ -0,0 +1,33 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: 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
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library
ssh:
port: 22
netbird_port: 22022
runs:
- proxmox_ui
services_hosted:
- name: proxmox_ui
url: https://proxmox.hubris.network
backend: hubris
port: 8006
see_also:
- hosts/hubris.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

31
hosts/jellyfin.yaml Normal file
View File

@@ -0,0 +1,31 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: jellyfin
kind: lxc
os: linux
role: media-server
host: hubris
pve_id: 101
lan_ip: 192.168.8.206
mesh:
netbird:
fqdn: jellyfin.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library
public_host: media.hubris.network
runs:
- jellyfin
services_hosted:
- name: jellyfin
url: https://media.hubris.network
backend: jellyfin
see_also:
- containers/101-jellyfin.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

17
hosts/ludo-mini.yaml Normal file
View File

@@ -0,0 +1,17 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: ludo-mini
kind: workstation
os: linux
role: dev
mesh:
netbird:
fqdn: ludo-mini.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

19
hosts/mac-mini.yaml Normal file
View File

@@ -0,0 +1,19 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: mac-mini
kind: workstation
os: macos
role: dev
mesh:
netbird:
fqdn: mac-mini.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
notes:
- Only macOS in the fleet. Bootstrap uses launchd.
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

31
hosts/mule-images.yaml Normal file
View File

@@ -0,0 +1,31 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: mule-images
kind: lxc
os: linux
role: photo-management
host: hubris
pve_id: 120
lan_ip: 192.168.8.136
mesh:
netbird:
fqdn: mule-images.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library
public_host: photos.hubris.network
runs:
- photos
services_hosted:
- name: photos
url: https://photos.hubris.network
backend: mule-images
see_also:
- containers/120-mule-images.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

View File

@@ -0,0 +1,29 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: mule-photos-new
kind: lxc
os: linux
role: photo-management
host: hubris
pve_id: 127
lan_ip: 192.168.8.181
mesh:
netbird:
fqdn: mule-photos-new.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
public_host: photos-new.hubris.network
runs:
- photos_new
services_hosted:
- name: photos_new
url: https://photos-new.hubris.network
backend: mule-photos-new
see_also:
- containers/127-mule-photos-new.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

31
hosts/nextcloud.yaml Normal file
View File

@@ -0,0 +1,31 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: nextcloud
kind: lxc
os: linux
role: file-sync
host: hubris
pve_id: 114
lan_ip: 192.168.8.224
mesh:
netbird:
fqdn: nextcloud.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library
public_host: cloud.hubris.network
runs:
- nextcloud
services_hosted:
- name: nextcloud
url: https://cloud.hubris.network
backend: nextcloud
see_also:
- containers/114-nextcloud.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

22
hosts/nfs-export.yaml Normal file
View File

@@ -0,0 +1,22 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: nfs-export
kind: lxc
os: linux
role: storage-export
host: hubris
pve_id: 102
lan_ip: 192.168.8.200
mesh:
netbird:
fqdn: nfs-export.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
see_also:
- containers/102-nfs-export.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

31
hosts/paperless.yaml Normal file
View File

@@ -0,0 +1,31 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: paperless
kind: lxc
os: linux
role: document-archive
host: hubris
pve_id: 103
lan_ip: 192.168.8.130
mesh:
netbird:
fqdn: paperless.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library
public_host: paperless.hubris.network
runs:
- paperless
services_hosted:
- name: paperless
url: https://paperless.hubris.network
backend: paperless
see_also:
- containers/103-paperless.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

31
hosts/plato.yaml Normal file
View File

@@ -0,0 +1,31 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: plato
kind: lxc
os: linux
role: app
host: hubris
pve_id: 126
lan_ip: 192.168.8.190
mesh:
netbird:
fqdn: plato.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library/documents/plato
public_host: plato.hubris.network
runs:
- plato
services_hosted:
- name: plato
url: https://plato.hubris.network
backend: plato
see_also:
- containers/126-plato.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

View File

@@ -0,0 +1,17 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: republic-laptop
kind: workstation
os: linux
role: primary-dev
mesh:
netbird:
fqdn: republic-laptop.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

24
hosts/sophia.yaml Normal file
View File

@@ -0,0 +1,24 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: sophia
kind: lxc
os: linux
role: workshop
host: hubris
pve_id: 119
lan_ip: 192.168.8.157
mesh:
netbird:
fqdn: sophia.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
mounts:
- /mnt/library
see_also:
- containers/119-sophia.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue

28
hosts/zimaos.yaml Normal file
View File

@@ -0,0 +1,28 @@
# Generated by mcp/build_host_files.py from inventory.yaml.
# Do NOT edit by hand — your changes will be overwritten.
# Source of truth: ../inventory.yaml
name: zimaos
kind: vm
os: linux
role: nas-frontend-eval
host: hubris
pve_id: 100
mesh:
netbird:
fqdn: zimaos.netbird.selfhosted
mesh_globals:
primary: netbird
accepted:
- netbird
- tailscale
public_host: zimaos.hubris.network
runs:
- zimaos
services_hosted:
- name: zimaos
url: https://zimaos.hubris.network
backend: zimaos
see_also:
- vms/100-zimaos.md
mcp_endpoint: http://apps.netbird.selfhosted:9810/sse
secrets_issuance_endpoint: http://apps.netbird.selfhosted:9820/issue