host: piha

services:
  ha-diag-agent:
    role: ha-diagnostic-agent
    deployment_model: docker-compose
    exposure: local-only
    offline_required: false
    depends_on:
      local: []
      external: [homeassistant]
    config:
      target_url: http://localhost:8123
      location_tag: "ken"
      events_dir: /opt/homelab/events/piha
    runtime:
      config_path: /opt/homelab/config/ha-diag-agent
      data_path: /var/lib/ha-diag-agent

  node-agent:
    role: node-stability-monitor
    deployment_model: docker-compose
    exposure: local-only
    offline_required: true
    depends_on:
      local: []
      external: []
    runtime:
      config_path: /opt/homelab/config/node-agent
      data_path: /opt/homelab/state
      logs_path: /opt/homelab/events

  brain-watchdog:
    role: control-plane-watchdog
    deployment_model: docker-compose
    exposure: private
    offline_required: false
    depends_on:
      local: []
      external: [control-plane]
    runtime:
      config_path: /opt/homelab/config/brain-watchdog

  vikunja:
    role: task-management
    deployment_model: docker-compose
    exposure: private          # public via npm reverse proxy at vikunja.okit.pl
    offline_required: false
    depends_on:
      local: []
      external: [forgejo]      # OIDC identity provider
    ports:
      - name: http
        container_port: 3456
        protocol: tcp
    runtime:
      # secrets (.env) + OIDC config (config.yml) live alongside the compose file
      config_path: services/vikunja
      # data is in Docker named volumes: vikunja_vikunja_db, vikunja_vikunja_files

  llm-gateway:
    role: llm-router             # FastAPI proxy -> Ollama @ SOLARIA :11434
    deployment_model: docker-compose
    exposure: private            # Tailscale-only bind (TAILSCALE_BIND_IP); no public ingress
    offline_required: false
    depends_on:
      local: []
      external: [ollama]         # SOLARIA may be powered down -> routes 502, health stays ok
    ports:
      - name: http
        container_port: 8080
        protocol: tcp
    runtime:
      # .env (TAILSCALE_BIND_IP) lives alongside the compose file; stateless, no data path
      config_path: services/llm-gateway

  node_exporter:
    role: metrics-exporter
    deployment_model: docker-compose
    exposure: local-only
    offline_required: true
    depends_on:
      local: []
      external: []
    runtime:
      # textfile collector reads /opt/homelab/state/node-exporter (module 5 phase 3 step 5,
      # kb/phases/kb-m5-faza3.md §7.2 — kb-ingest.prom) via the existing /:/host:ro
      # mount, see hosts/piha/runtime/node_exporter/docker-compose.override.yml.
      data_path: /opt/homelab/state/node-exporter

  kb-postgres:
    role: kb-database            # KB spine: Postgres 16 + pgvector (always-on)
    deployment_model: docker-compose
    exposure: local-only         # Tailscale-accessible; no public ingress
    offline_required: false
    depends_on:
      local: []
      external: []
    ports:
      - name: postgres
        host_port: 5433
        protocol: tcp
    runtime:
      config_path: /opt/homelab/config/kb-postgres
      # data is in Docker named volume kb_postgres_data — must land on the NVMe
      # (Docker data-root on /home); see hosts/piha/runtime/kb-postgres override.

  ollama-piha:
    role: embed-fallback         # module 5 phase 4 (plan §2 D2/§5): local CPU bge-m3 for kb-query while SOLARIA sleeps
    deployment_model: docker-compose
    exposure: private            # 127.0.0.1 + LAN bind (LAN_BIND_IP) only; no public/Tailscale exposure
    offline_required: false
    depends_on:
      local: []
      external: []
    ports:
      - name: http
        container_port: 11434
        host_port: 11434
        protocol: tcp
    runtime:
      # .env (LAN_BIND_IP) lives alongside the compose file; models live in the
      # Docker named volume ollama_piha_models (NVMe data-root, like kb-postgres).
      # Deploy note: `docker exec ollama-piha ollama pull bge-m3` is a manual
      # post-deploy step — the image ships no models.
      config_path: services/ollama-piha

  kb-query:
    role: kb-search-api          # module 5 phase 4: FastAPI wrapper over kb_retrieval cascade/flat query
    deployment_model: docker-compose
    exposure: private            # LAN bind (LAN_BIND_IP); npm@PIHA vhost + OIDC is a later step
    offline_required: false
    depends_on:
      # ollama-piha is a soft local dependency: the embed fallback while SOLARIA
      # sleeps — kb-query starts and serves /healthz without it, /search degrades
      # to 503 only when BOTH embed backends are unreachable.
      local: [kb-postgres, ollama-piha]
      external: [ollama]         # primary embed backend @ SOLARIA; may be offline -> fallback to ollama-piha
    ports:
      - name: http
        container_port: 8080
        host_port: 8230
        protocol: tcp
    runtime:
      # .env (KB_DSN, LAN_BIND_IP) lives alongside the compose file; stateless, no data path
      config_path: services/kb-query

  narty27:
    role: static-html-host       # single self-contained viz.html (narty 2027 / Saalbach KB export)
    deployment_model: docker-compose
    exposure: private            # LAN only; no npm vhost, no public ingress
    offline_required: false
    depends_on:
      local: []
      external: []
    ports:
      - name: http
        container_port: 80
        host_port: 8240
        protocol: tcp
    runtime:
      # No config and no secrets. Content is PERSONAL and deliberately outside
      # the repo — it lives only in the Docker named volume
      # narty27_narty27_content, refreshed from SOLARIA (kb/runbooks/narty27-deploy.md).
      # No backup job, no /opt/homelab/data bind.
      config_path: services/narty27

  # --- Known unmanaged containers on piha (recon B5/B6, 2026-07-27) ----------
  # ~28 running containers have no entry above and are deliberately NOT being
  # added piecemeal — bringing them under desired state is a later stage
  # (recon open question 5). The set: agent-system ×4 (webui,
  # runtime-materializer, telegram-bot, redis — these STAY, decision
  # 2026-07-28), stability-agent, paperless ×3, immich ×4, forgejo ×3,
  # zigbee2mqtt, audiobookshelf, grafana, wikijs ×2, code-server, portainer,
  # prometheus, homepage, actual-budget, mqtt-exporter, owntracks ×3,
  # vaultwarden, pihole-exporter (unlabelled), fail2ban-exporter,
  # nginxproxymanager — plus the HOST systemd mosquitto (OS package, no repo
  # definition at all, recon C8). When adding any of these later, match the
  # entry key to the world-state service key (supervisor does exact
  # <host>/<name> matching) or it will drift as missing_service.
