think
16px
820px

Server ↔ App/Repo Distribution & Networking Map — AHU AI Platform

Date: 2026-07-09
What this is: two pictures — where each app/repo lands on which server and what's allowed to talk to what, first for the near-term MVP target, then for the fuller future state with real public traffic. Consolidates the server delivery audit, migration audit, and the Keep deployment into one reference.
Current vs. target: the 6 new hosts are hardened (firewall/Docker/monitoring) but no engine is deployed on them yet — everything below except the firewall rules themselves is still on the GPU box ai-ahu today. Both sections below show where things are going, not what's live now.

Near-term MVP target

Server inventory

Host IP Zone What lands here
ahu-ctrl-01 192.168.82.120 Control plane Gateway (ahu-gpu-manager) + Redis + Controller (not built yet)
ahu-gov-01 192.168.82.121 Governance Observatory + dashboard (ahu-ai-observatory, ahu-observatory-dashboard) + TimescaleDB + Keep (moves here with Observatory) + Prometheus/Grafana (planned)
ahu-int-01 192.168.82.122 Internal engines Internal chatbot + RAG (ahu-ai-chatbot internal, ai-ahu-rag) + OCR (ahu-ocr-akta-notaris, tidyup branch only)
ahu-dmz-01 192.168.82.123 DMZ Public chatbot only (ahu-ai-chatbot public)
ahu-dwh-01 / ahu-dwh-store-01 192.168.82.124 / .125 DWH Idle — engine not built
ai-ahu (unchanged) 192.168.83.20 GPU zone vLLM, TEI embeddings, PaddleOCR, Azure DI, doc-classifier, node-agent (planned) — stays put, never migrates
103.30.246.154 (not platform-owned) Efran's private box, currently borrowed for staging edge nginx — not part of production

Network diagram

flowchart TB internet([Internet users]):::actor admin([Admins / Developers
via VPN·bastion]):::actor subgraph edge["Edge — OPEN ITEM, not provisioned"] lb["LB / WAF (TBD)"] end subgraph dmz["DMZ — ahu-dmz-01 (.123)"] pubweb["public-web + public-agent
ahu-ai-chatbot"] end subgraph ctrl["Control plane — ahu-ctrl-01 (.120)"] gw["Gateway :8200
ahu-gpu-manager"] redis[("Redis :6379")] ctrlapi["Controller :8210
(not built)"] end subgraph intz["Internal engines — ahu-int-01 (.122)"] intweb["internal-web + internal-agent
ahu-ai-chatbot"] rag["RAG · ai-ahu-rag"] ocr["OCR backend
ahu-ocr-akta-notaris (tidyup)"] end subgraph govz["Governance — ahu-gov-01 (.121)"] obs["Observatory :8300
ahu-ai-observatory"] dash["Dashboard :8320"] tsdb[("TimescaleDB")] keep["Keep :8330-8332
(incident escalation)"] prom["Prometheus + Grafana
(planned)"] end subgraph dwhz["DWH — idle (engine not built)"] dwh["ahu-dwh-01 (.124)"] dwhstore["ahu-dwh-store-01 (.125)"] end subgraph gpuz["GPU zone (unchanged) — ai-ahu · 192.168.83.20"] vllm["vLLM · TEI embeddings"] paddleocr["PaddleOCR · Azure DI"] clf["doc-classifier"] nodeagent["node-agent
(planned)"] end internet -.->|"no LB/WAF yet"| lb lb ==> pubweb admin ==>|SSH, all 6 hosts| ctrl admin -.-> intz admin -.-> dmz admin -.-> govz admin -.-> dwhz admin -->|dashboard :8320 · obs API :8300| dash pubweb ==>|"ONLY egress path allowed
gateway-only, no exceptions"| gw intweb --> gw ocr --> gw rag -.->|"/search, proxied through gateway
no direct DMZ→Internal call"| gw gw --> vllm gw --> paddleocr gw --> clf ctrlapi -.->|planned| nodeagent obs ==>|consumes audit stream| redis keep <-.->|EWS webhook| obs prom -.->|scrape :9100| ctrl prom -.-> intz prom -.-> dmz prom -.-> dwhz dmz -. "BLOCKED by firewall" .-x dwhz dmz -. "BLOCKED by firewall" .-x govz dmz -. "BLOCKED — internet egress" .-x internet classDef actor fill:#eaf0fb,stroke:#334455 classDef blocked stroke:#c0392b,stroke-dasharray: 3 3

Solid/thick ==> = the primary intended traffic path · dotted -.-> = secondary/planned/admin-only path · -.-x = explicitly blocked by firewall (shown to make the isolation visible, not because traffic flows there).

Networking rules, in plain terms

  • SSH: every host accepts port 22 only from the admin/VPN source range — no direct internet SSH anywhere, no exceptions.
  • DMZ (ahu-dmz-01) egress is locked to exactly three things: the Gateway (ahu-ctrl-01:8200), two named internal DNS resolvers, and NTP. Everything else — including the open internet, and including every other production host — is default-deny. The /search RAG call used to be a fourth, disallowed path; it's now proxied through the Gateway instead, so the DMZ's egress list stays exactly three items.
  • Cross-zone calls all flow through the Gateway — internal chatbot, OCR, and (now) RAG search all reach the GPU zone's models and each other's model-adjacent calls via ahu-ctrl-01:8200, never directly.
  • Governance pulls, it doesn't get pushed to: ahu-gov-01's Observatory consumes the audit stream from ahu-ctrl-01's Redis (port 6379, that specific path is an explicit firewall allow), and — once built — Prometheus on ahu-gov-01 reaches out to scrape :9100 on the other 5 hosts (that scrape-permission firewall rule is already in place).
  • DWH is the most isolated zone on purpose: nothing reaches it from DMZ or the internet, ever — currently nothing reaches it at all, since the engine isn't built and the hosts sit idle.
  • The LB/WAF is a real gap, not a design choice. No host among the 6 fronts the DMZ publicly yet; this blocks the public chatbot's production cutover specifically until it's resolved.

Repo → host, condensed

Repo Lands on
ahu-gpu-manager ahu-ctrl-01
ahu-ai-observatory + ahu-observatory-dashboard ahu-gov-01
Keep (keephq, not a repo in this workspace) ahu-gov-01 (moves with Observatory)
ahu-ai-chatbot — internal apps ahu-int-01
ai-ahu-rag ahu-int-01
ahu-ocr-akta-notaris (tidyup branch only — legacy never ships) ahu-int-01
ahu-ai-chatbot — public apps ahu-dmz-01
ahu-doc-classifier stays on ai-ahu (GPU-resident, never migrates)

Future state — real public traffic, everything currently open resolved

The section above is the near-term MVP target: single instance per zone, LB/WAF still an open gap, no engine live yet. This section is the picture once the platform is actually taking public traffic and the open items from the audits get resolved — the HA tier from the topology doc, plus the backup target, secrets vault, and full monitoring stack landing. Two pieces (DWH, the B200 cluster) are genuinely further out — not scheduled, shown here only so the target shape is visible, not because they're in progress.

flowchart TB pubusers([Public users]):::actor staffusers([AHU staff, via VPN]):::actor notaryusers([Notary / public users
RBAC-gated]):::actor ops([Ops / auditors]):::actor subgraph edge["Edge — LB / WAF (resolved)"] lb["Public LB/WAF
rate limiting, TLS"] end subgraph dmzha["DMZ — HA pair"] dmz1["ahu-dmz-01"] dmz2["ahu-dmz-02"] end subgraph ctrlha["Control plane — HA"] gw["Gateway ×2
active-active, internal VIP"] ctl["Controller
active-standby"] redis[("Redis — Sentinel/managed")] end subgraph intha["Internal engines — HA pair"] int1["ahu-int-01
chatbot + RAG + OCR"] int2["ahu-int-02"] end subgraph govha["Governance"] govapp["Observatory + dashboard
+ Keep + Prometheus/Grafana"] tsdbp[("TimescaleDB — primary")] tsdbs[("TimescaleDB — standby")] end subgraph backupz["Backup target (resolved)
dedicated host"] backup[("Object storage / PITR")] end subgraph gpuz["GPU zone — current H100s"] vllm["vLLM 35B-A3B · TEI
PaddleOCR · classifier"] end subgraph b200["B200 cluster — PHASE 2
separate procurement, not started"] synth["397B synthesis model"] end subgraph dwhz["DWH — PHASE 2
engine not built"] dwhcompute["Warehouse + ETL
+ Domain API"] dwhstore2[("Metastore +
lakehouse storage")] end vault[("Secrets vault
SOPS+age / Infisical")]:::vault powerbi(["Power BI
exec reporting"]):::actor pubusers --> lb lb ==> dmzha staffusers --> intha notaryusers --> intha ops --> govapp govapp -.-> powerbi dmzha ==>|"gateway-only egress
still the only path out"| ctrlha intha --> ctrlha ctrlha --> gpuz ctrlha -.->|"synthesis calls,
once available"| b200 ctl -.-> gpuz govapp -->|consumes audit stream| redis tsdbp -.->|replication| tsdbs tsdbp ==>|PITR backup| backup int1 -.->|pg_dump backup| backup intha -.->|"Domain API,
once DWH exists"| dwhcompute dwhcompute --> dwhstore2 vault -.- dmzha vault -.- ctrlha vault -.- intha vault -.- govha classDef actor fill:#eaf0fb,stroke:#334455 classDef vault fill:#fef6e0,stroke:#a67c00

What changed vs. the near-term diagram:
- Real public users flow in through a live LB/WAF — resolves the open item from §"Networking rules" above; DMZ egress stays exactly as locked-down (gateway-only), it's just fronted properly now instead of having no entry point at all.
- HA pairs everywhere except governance — matches the topology doc's documented HA tier: DMZ ×2 and internal-engines ×2 behind their respective paths, Gateway active-active behind an internal VIP, Controller active-standby. Governance stays single-app-host by design (per the original spec, the Observatory ingester can't safely run 2 replicas yet — a known backlog item, not an oversight) but its database gets a standby.
- Backup target is a real, separate host, receiving TimescaleDB's continuous PITR stream and periodic dumps from the internal-engines Postgres/pgvector — not co-located with anything it protects, per the earlier decision.
- Secrets vault is cross-cutting — every zone's compose deployment pulls from it instead of plaintext .env files.
- Monitoring is complete: Prometheus/Grafana/Keep on governance, feeding Power BI for exec-facing reporting.
- DWH and the B200 cluster are drawn but explicitly marked Phase 2 — RAG's "live data sync from DWH" and the 397B synthesis tier's on-prem home both depend on infrastructure that doesn't exist yet and isn't scheduled. Shown for shape, not as a near-term claim.