# Homelab inventory — canonical structured topology.
#
# Single source of truth. hosts/*.yaml is generated from this file by
# mcp/build_host_files.py; do NOT edit those by hand.
#
# 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`).
#
# `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
