Files
prose-contracts/infrastructure-control.prose.md
T
root 14bb145300 feat: expand infrastructure-control pattern — Proxmox 5-node cluster, Docker 3-ecosystem, NFS storage, network services
Full discovery and expansion based on PVE API exploration:

Proxmox (5 nodes):
  - minipve (.12): authentik, gitea, mumuni, syslog-api, jitsi
  - amdpve (.15): abiba, kagentz, tanko, tdunna, baggy, scottdenya
  - storepve (.6): docker-vm, ra-h-os, PBS, media, zulip
  - acerpve (.9): llm-gpu, adguard
  - ocupve (.5): ocu-llm

Docker (3 ecosystems, 22 containers):
  - docker-vm (.7): Firecrawl, SearXNG, Home stack, Audiobookshelf
  - CT 116: LiteLLM stack (6 containers)
  - Netbird (.17): VPN controller + dashboard + crowdsec + traefik

Pattern now includes: access matrix, per-section checks/remediations,
alert routing, escalation chain, quick-start commands, full CT inventory.
2026-06-27 13:11:55 +00:00

334 lines
13 KiB
Markdown

---
kind: pattern
name: infrastructure-control
description: >
Full infrastructure monitoring and control pattern covering the
5-node Proxmox cluster, 3 Docker ecosystems (22 containers),
NFS storage, and network services. Defines monitors, remediations,
and the access matrix for all environments.
---
# Infrastructure Control Pattern
## Topology
```
┌─────────────────────────┐
│ OpenProse Contract │
│ (declares what's true) │
└──────────┬──────────────┘
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Abiba │ │ Tanko │ │ Mumuni │
│ (pi) │ │ (Hermes) │ │ (Hermes) │
│ CT 100 │ │ CT 122 │ │ CT 114 │
└──────┬──────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└──────────────────┼────────────────────┘
┌──────────────────────────────────────┐
│ Proxmox Cluster API │
│ minipve.sysloggh.net:443 │
│ (monitoring@pve!mumuni token) │
└────┬──────┬──────┬──────┬──────┬─────┘
│ │ │ │ │
┌────┘ ┌────┘ ┌────┘ ┌────┘ ┌────┘
▼ ▼ ▼ ▼ ▼
minipve amdpve storepve acerpve ocupve
(.12) (.15) (.6) (.9) (.5)
┌─────────────────────────────────────────────┐
│ Docker Ecosystems │
│ ┌────────────┬────────────┬──────────────┐ │
│ │ docker-vm │ CT 116 │ Netbird │ │
│ │ (.7) │ (.116) │ (72.61.0.17) │ │
│ │ 11 ctrs │ 6 ctrs │ 5 ctrs │ │
│ └────────────┴────────────┴──────────────┘ │
└─────────────────────────────────────────────┘
```
## Section 1: Access Matrix
### API Tokens & Credentials
| Resource | Auth Method | Credential Source | Status |
|----------|------------|-------------------|--------|
| Proxmox Cluster | PVE API Token | `monitoring@pve!mumuni=...` | ✅ |
| Proxmox Root | Password via API ticket | `root@pam:kakashi19` | ✅ |
| docker-vm (.7) | SSH root | SSH key | ✅ |
| CT 116 (syslog-api) | SSH root | SSH key | ✅ |
| Tanko CT (.122) | SSH jerome | SSH key | ✅ |
| Netbird (.17) | SSH root | SSH key | ✅ |
| Gitea | API token | abiba-bot token | ✅ |
| Zulip | Bot API key | per-bot tokens | ✅ |
| RA-H OS | MCP bridge | port 3100 | ✅ |
### Reachability Matrix
| From / To | PVE API | docker-vm (.7) | CT 116 | Tanko (.122) | Netbird (.17) |
|-----------|---------|----------------|--------|-------------|---------------|
| **Abiba** (CT 100) | ✅ :443 | ✅ SSH | ✅ SSH | ✅ SSH | ❌ no Netbird |
| **Tanko** (CT 122) | ❌ | ❌ | ❌ | ✅ | ❌ |
| **docker-vm** (.7) | ❌ | ✅ | ❌ | ❌ | ❌ |
**Conclusion:** Only Abiba has cross-infrastructure access. All monitoring contracts run from Abiba.
## Section 2: Proxmox Cluster — Monitoring
### Nodes (5)
| Node | IP | CPU | RAM | VMs/CTs | Role |
|------|----|-----|-----|---------|------|
| minipve | .12 | 16C | 30GB | authentik, gitea, mumuni, syslog-api, jitsi | Auth, git, messaging |
| amdpve | .15 | 32C | 62GB | abiba, kagentz, tanko, tdunna, baggy, scottdenya | Agents, compute |
| storepve | .6 | 28C | 31GB | docker-vm, ra-h-os, PBS, media, zulip | Docker, storage, chat |
| acerpve | .9 | 28C | 31GB | llm-gpu, adguard | GPU VMs |
| ocupve | .5 | 12C | 14GB | ocu-llm | GPU VMs |
### Checks (every 5 min)
```
## Maintains
- cluster-status: { online_nodes: int, offline_nodes: int, timestamp }
- node-cpu-usage: { node: pct, warnings: [] }
- node-memory-usage: { node: free_pct, warnings: [] }
- node-uptime: { node: seconds, just_rebooted: bool }
## Checks
- For each node in [minipve, amdpve, storepve, acerpve, ocupve]:
- GET /api2/json/nodes/{node}/status → check status == "online"
- GET /api2/json/nodes/{node}/status → cpu < 0.80
- GET /api2/json/nodes/{node}/status → free_mem > 10%
- GET /api2/json/nodes/{node}/status → uptime > 300 (warn if just rebooted)
- For each CT in inventory (19 total):
- GET /api2/json/cluster/resources → filter by type=lxc
- Warn if status != "running"
- Storage pools:
- GET /api2/json/nodes/storepve/storage/mediastore → used < 80%
- GET /api2/json/nodes/storepve/storage/pbs-backup → last backup < 48h
## Remediations
- Node offline → alert via Zulip DM, escalate after 3x
- CT stopped → `pct start <vmid>` via PVE API, verify after 30s
- VM stopped → `qm start <vmid>` via PVE API, verify after 60s
- Storage > 80% → warn via Zulip DM
- Storage > 95% → crit via Zulip + relay to maintainer
```
## Section 3: Docker Ecosystems — Monitoring
### Ecosystem A: docker-vm (192.168.68.7)
11 containers across 4 compose stacks:
| Stack | Path | Containers |
|-------|------|-----------|
| **Firecrawl** | `/opt/search-stack/firecrawl-source/` | api, rabbitmq, postgres, playwright, redis |
| **SearXNG** | `/opt/search-stack/searxng/` | searxng, valkey |
| **Home stack** | `/opt/home_stack/` | jdownloader, bentopdf, pulse |
| **Audiobookshelf** | `/opt/audiobookshelf/` | audiobookshelf |
### Ecosystem B: CT 116 syslog-api (192.168.68.116)
| Container | Image | Port |
|-----------|-------|------|
| harness-litellm | berriai/litellm:1.90.0-rc.1 | :4001 |
| harness-nginx | nginx:alpine | :80 |
| harness-router | inference-harness-router | :9000 |
| harness-postgres | postgres:16-alpine | :5432 |
| harness-redis | redis:7-alpine | :6379 |
| harness-dashboard | inference-harness-dashboard | :3000 |
### Ecosystem C: Netbird (72.61.0.17)
| Container | Image | Role |
|-----------|-------|------|
| netbird-server | netbird | VPN controller |
| netbird-dashboard | netbird UI | Web management |
| netbird-proxy | nginx | TLS termination |
| netbird-crowdsec | crowdsecurity/crowdsec:v1.7.7 | WAF |
| netbird-traefik | traefik | Reverse proxy |
### Checks (every 60s)
```
## Maintains
- docker-health: { ecosystem: string, healthy: int, unhealthy: int, total: int }
- container-status: { name: string, status: string, restarts: int, image: string }
## Checks
For each Docker host:
- docker ps --format "{{.Names}}" → every required container is running
- docker inspect <name> --format "{{.State.Health.Status}}" → "healthy"
- docker inspect <name> --format "{{.RestartCount}}" → < 3/hour
- df -h / | awk '{print $5}' → usage < 80%
For docker-vm specifically:
- mountpoint -q /media/storage → NFS mounted
- mountpoint -q /media/mediastore → NFS mounted
- docker compose ls → expected stacks present
## Remediations
- Container unhealthy → `docker compose up -d <name>`, wait 10s, re-check
- Container missing → `docker compose up -d` in its stack directory
- Docker daemon down → `systemctl restart docker`
- NFS mount lost → `mount -a`, if fails → alert (storepve issue)
- Disk > 80% → alert via Zulip DM
- Disk > 95% → crit + relay to maintainer
```
## Section 4: Storage — Monitoring
### NFS Mounts (docker-vm → storepve)
| Mount | Export | Capacity | Used | Alert |
|-------|--------|----------|------|-------|
| /media/storage | storepve:/media/storage | 3.6TB | 264GB (8%) | None |
| /media/mediastore | storepve:/media/mediastore | 7.3TB | 5.2TB (75%) | ⚠️ warn at 80% |
### Proxmox Storage Backends
| Storage | Type | Content | Active |
|---------|------|---------|--------|
| local | dir | ISO, vztmpl, backup | ✅ |
| local-lvm | lvmthin | images, rootdir | ✅ |
| zfs-vm | nfs | rootdir, images | ✅ |
| mediastore | dir | images, backup, rootdir | ✅ |
| zfs-iso | nfs | vztmpl, iso | ✅ |
| pbs-backup | pbs | backup | ✅ |
### Checks (every 10 min)
```
## Maintains
- storage-usage: { mount: string, used_pct: float, growth_rate: float }
- backup-status: { last_success: timestamp, age_hours: int }
- snapshot-status: { pool: string, newest_age_hours: int }
## Specific Alerts
- mediastore growth rate > 10GB/day → warn (check what's writing)
- mediastore > 90% → crit (only 730GB remaining)
- No PBS backup in 48h → fail
```
## Section 5: Network Services — Monitoring
| Service | Domain | Status |
|---------|--------|--------|
| Proxmox API | minipve.sysloggh.net:443 | ✅ |
| Authentik | auth.sysloggh.net:443 | ✅ |
| Gitea | git.sysloggh.net:443 | ✅ |
| Zulip | chat.sysloggh.net:443 | ✅ |
| LiteLLM | litellm.sysloggh.net:443 | ✅ |
| Pulse | pulse.sysloggh.net:443 | ✅ |
| SearXNG | searxng.sysloggh.net:8888 | ✅ |
| Firecrawl | firecrawl.sysloggh.net:3002 | ✅ |
### Checks (every 2 min)
```
## Maintains
- dns-resolution: { services: [{name, resolves}] }
- ssl-expiry: { services: [{name, days_remaining}] }
- endpoint-reachability: { services: [{name, http_code}] }
```
## Section 6: Alert Routing & Escalation
### Severity Levels
| Severity | Channel | Format | Rate Limit |
|----------|---------|--------|-----------|
| **warn** | Zulip DM to owner | "⚠️ {check}: {detail}" | 1x/check/hour |
| **crit** | Zulip DM + #agent-hub | "🚨 {check}: {detail}" | Immediate |
| **escalated** | Zulip DM + relay to maintainer | "🔥 {check}: {detail}" | Immediate |
### Escalation Chain
```
Container unhealthy (3 consecutive failures)
→ Alert owner
→ 15 min no response → Relay to maintainer
Node offline
→ Alert owner
→ 5 min → Attempt restart via PVE API
→ 2 failures → Escalate
Storage > 95%
→ Alert owner + maintainer relay
→ Immediate action required
SSL cert < 7 days
→ Alert owner daily
→ 3 days out → Escalate
ZFS pool degraded
→ Cannot auto-fix → Escalate immediately
```
## Appendix A: Quick Health Commands
```bash
# Full cluster status
PVE="https://minipve.sysloggh.net"
AUTH="Authorization: PVEAPIToken=monitoring@pve!mumuni=eafd56c5-93d4-4d40-a41d-e688be0987f3"
curl -sfk "$PVE/api2/json/cluster/resources" -H "$AUTH"
# Docker health from Abiba
ssh root@192.168.68.7 "docker ps --format '{{.Names}} {{.Status}}'"
ssh root@192.168.68.116 "docker ps --format '{{.Names}} {{.Status}}'"
# Storage check
ssh root@192.168.68.7 "df -h /media/storage /media/mediastore"
```
## Appendix B: CT Inventory
| CT | Name | Node | IP | Role | Agent |
|----|------|------|----|------|-------|
| 100 | abiba | amdpve | .24 | Pi agent (this host) | ✅ pi |
| 101 | llm-gpu | acerpve | — | GPU VM | ❌ |
| 102 | adguard | acerpve | — | DNS | ❌ |
| 103 | ocu-llm | ocupve | — | GPU VLM | ❌ |
| 104 | authentik | minipve | .11 | OIDC | ❌ |
| 105 | kagentz | amdpve | — | Agent Zero | ✅ |
| 106 | ra-h-os | storepve | .65 | KG bridge | ✅ MCP |
| 107 | pbs | storepve | — | Backups | ❌ |
| 108 | media | storepve | — | Media | ❌ |
| 109 | docker-vm | storepve | .7 | Docker host | ❌ |
| 110 | gitea | minipve | — | Git | ❌ |
| 111 | tdunna | amdpve | — | ? | ❌ |
| 112 | tanko | amdpve | .122 | Hermes agent | ✅ |
| 113 | baggy | amdpve | — | ? | ❌ |
| 114 | mumuni | minipve | — | Hermes agent | ✅ |
| 115 | scottdenya | amdpve | — | ? | ❌ |
| 116 | syslog-api | minipve | .116 | LiteLLM stack | ❌ |
| 117 | zulip | storepve | — | Chat | ❌ |
| 118 | jitsi | minipve | — | Video | ❌ |
## Appendix C: Docker Compose Files Location
| Host | Stack | Compose File |
|------|-------|-------------|
| docker-vm (.7) | Firecrawl | `/opt/search-stack/firecrawl-source/docker-compose.yaml` |
| docker-vm (.7) | SearXNG | `/opt/search-stack/searxng/docker-compose.yml` |
| docker-vm (.7) | Home stack | `/opt/home_stack/docker-compose.yml` |
| docker-vm (.7) | Audiobookshelf | `/opt/audiobookshelf/docker-compose.yml` |
| CT 116 (.116) | LiteLLM | `/root/docker-compose-litellm.yml` |
| Netbird (.17) | Netbird | Docker run (not compose) |