host: vps

services:
  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

  control-plane:
    role: management-and-orchestration
    deployment_model: docker-compose
    exposure: tailscale-internal
    offline_required: false
    depends_on:
      local:
        - node-agent
      external:
        - piha:redis
    ports:
      - name: http
        container_port: 18180
        protocol: tcp
    runtime:
      config_path: /opt/homelab/config/control-plane
      data_path: /opt/homelab/data/control-plane
      logs_path: /opt/homelab/logs/control-plane

  node_exporter:
    role: metrics-exporter
    deployment_model: docker-compose
    exposure: local-only
    offline_required: true
    depends_on:
      local: []
      external: []

  fleet-prometheus:
    role: fleet-liveness-source
    deployment_model: docker-compose
    exposure: tailscale-internal
    offline_required: false
    depends_on:
      local:
        - node_exporter
      external: []
    ports:
      - name: http
        container_port: 9090
        protocol: tcp
    runtime:
      config_path: /opt/homelab/config/fleet-prometheus
      data_path: /opt/homelab/data/fleet-prometheus
      logs_path: /opt/homelab/logs/fleet-prometheus

  gokapi:
    role: public-file-share
    deployment_model: docker-compose
    exposure: public
    offline_required: false
    depends_on:
      local: []
      external: []
    ports:
      - name: http
        container_port: 53842
        protocol: tcp
    runtime:
      config_path: /opt/homelab/config/gokapi
      data_path: /opt/homelab/data/gokapi
      logs_path: /opt/homelab/logs/gokapi

  stability-agent:
    role: node-watchdog          # read-only docker.sock watchdog, emits filesystem events
    # Deploys via its own deploy-local.sh, outside the declarative pipeline
    # (recon B7) — entry added 2026-07 truth cleanup so desired state matches
    # what actually runs here.
    deployment_model: docker-compose
    exposure: local-only
    offline_required: true
    depends_on:
      local: []
      external: []
    runtime:
      data_path: /opt/homelab/state
      logs_path: /opt/homelab/events

  npm:
    role: reverse-proxy          # public ingress on the VPS (separate from npm-piha)
    deployment_model: docker-compose
    exposure: public
    offline_required: false
    depends_on:
      local: []
      external: []
    ports:
      - name: http
        container_port: 80
        protocol: tcp
      - name: https
        container_port: 443
        protocol: tcp
    runtime:
      # Data stays at the pre-GitOps bind mounts (CLAUDE.md data migration rule):
      # /home/dockeruser/docker/npm/{data,letsencrypt}
      config_path: services/npm

  outline:
    # Runs on vps but has NO services/outline dir on master (recon F20.2) — the
    # containers predate GitOps management. Companion containers
    # outline-postgres-1 / outline-redis-1 are visible in world state but are
    # not separate desired-state entries.
    role: knowledge-base
    deployment_model: docker-compose
    exposure: public
    offline_required: false
    depends_on:
      local: []
      external: []

  joplin-server:
    # Keyed joplin-server (not "joplin") to match the compose service name the
    # world state tracks — an aggregate "joplin" entry would sit in
    # missing_service drift forever. No services/joplin dir on master (recon
    # F20.2). Companion joplin-db is tracked in world state, not desired here.
    role: notes-sync
    deployment_model: docker-compose
    exposure: public
    offline_required: false
    depends_on:
      local: []
      external: []

  umami:
    # Web analytics; compose-labelled but no services/umami dir in the repo —
    # pre-GitOps deployment (recon B5). Companion umami-db not desired here.
    role: web-analytics
    deployment_model: docker-compose
    exposure: public
    offline_required: false
    depends_on:
      local: []
      external: []

  # humanai-mailer / humanai-landing: hand-run containers with NO compose
  # labels (recon B5/B7). Documented here as unmanaged so the truth map is
  # complete. monitor: false keeps the supervisor from generating (dead-end
  # redeploy) actions for containers the pipeline cannot manage; `unmanaged`
  # itself is documentation only — no code reads it.
  humanai-mailer:
    unmanaged: true
    monitor: false
    role: mailer
  humanai-landing:
    unmanaged: true
    monitor: false
    role: landing-page

  # ai-cluster (openclaw, codex-worker, planner-worker, service-ops-worker,
  # redis, mosquitto): legacy stack, runs UNMANAGED on vps and is scheduled
  # for retirement — its codex/* bus has been idle since 2026-06-09.
  # Deliberately NO entry here: legacy is not pulled into desired state.
  # See docs/architecture/ai-cluster-LEGACY.md and
  # docs/architecture/RECON-multiagent-2026-07-27.md (C9).
