Files

11 KiB
Raw Permalink Blame History

name, description, tags
name description tags
server-architecture-plan ITPP infrastructure architecture — server roles, service placement, and the migration plan from Hetzron to netcup. Approved July 2026.
infrastructure
architecture
netcup
migration
planning
RBAC

Server Architecture Plan

Approved Architecture (July 2026) — Rebalanced Jul 12

Base Server Standard

All new netcup servers are RS 4000 G12 — 12 dedicated EPYC 9645 cores, 32 GB DDR5 ECC RAM, 1 TB NVMe, ~$44/mo. Exception: Core (RS 2000) keeps Hermes + LiteLLM + Ollama fallback. core-bu (Hetzner, was app1-bu) is warm standby, pending CPX11→CPX31 upgrade.

Server Roles — Post-Rebalance

Box Model Cost IP Role
Core RS 2000 ~$24/mo 152.53.192.33 Hermes + MCPs only. LiteLLM, Caddy, Portal, Super Search, Ollama moved to app1/decommissioned. See audit Jul 13.
app1 RS 4000 ~$44/mo 152.53.36.131 Service hub — Caddy (all domains), Ops Portal, OSINT API, Super Search MCP, SearXNG, Grafana, cron, n8n + Postgres, Docker services
app2 RS 4000 ~$44/mo 152.53.39.202 Infrastructure — UNMS/UISP, UniFi Controller, Hudu (target), Traccar (target)
app3 RS 4000 ~$44/mo 152.53.241.111 Web apps — WordPress/Apex from wphost02/SiteGround
core-bu (was app1-bu) CPX11→CPX31 ~$22/mo 5.161.114.8 Warm standby, needs upgrade for DR capacity
~$174/mo

Hetzner Cleanup Plan (Jul 15, 2026 — mostly complete)

Deleted today (Jul 15):

  • unifi (178.156.131.57) — Old UniFi controller decommissioned, backups in S3, server deleted
  • hudu (178.156.130.130) — Full data migration to app2 verified (135 assets, 125 passwords, 18 layouts restored), server deleted
  • unms (5.161.225.131) — UNMS running on app2, full backup to S3 (994MB + 1,107 configs), server deleted

Previously deleted:

  • fleettracker (178.156.149.32) — Deleted Jul 14

Remaining Hetzner servers (4):

  • core-bu (5.161.114.8) — Warm standby, keep
  • ai.itpropartner.com (178.156.167.181) — LiteLLM, pending migration to app1
  • wphost02 (5.161.62.38) — WordPress, pending migration to app3
  • app1.itpropartner.com (87.99.144.163) — Old app1, deprecated

Why RS 4000 as standard

  • 12 dedicated EPYC cores — no shared vCPU contention
  • 32 GB ECC RAM — headroom for Java apps (UniFi, Traccar) + 20+ Docker containers
  • 1 TB NVMe — Ollama models alone need 50+ GB, plus UNMS/UISP data
  • Fault isolation — an outage on app2 doesn't touch Core or app1

Service Placement

Core (RS 2000) — Existing, paid

  • Hermes Agent (systemd, port 8081)
  • Caddy reverse proxy (systemd, ports 80/443)
  • Ops Portal backend (FastAPI, systemd, port 8090)
  • Prometheus + Grafana (Docker)
  • Uptime-Kuma (Docker, /root/docker/uptime-kuma/, port 3001) — migrated from docker server Jul 13 with 9 monitors + Telegram/Discord/Slack notifications
  • FT360 MCP server (FastMCP, systemd, port 8903, /root/docker/ft360-mcp/) — 3 tools: get_devices, get_positions, get_stats. Queries Traccar H2 on app2 via SSH+SCP pattern
  • Vaultwarden, DocuSeal, SearXNG, Twenty CRM (Docker under /root/docker/*/)
  • Tailscale (systemd)
  • Super Search — FastMCP server at http://127.0.0.1:8899/mcp (systemd: super-search.service). Multi-provider web search: SearXNG → Exa → OpenCorporates → CourtListener → Firecrawl, plus Trafilatura extraction + premium Exa endpoint. All 3 exposed as Hermes MCP tools: web_search, web_extract, web_search_premium. v1.2.0: token-bucket rate limiting, TTL caching, exponential backoff per provider. Deployed at /root/docker/super-search/server.py via venv.

app1 (RS 4000) — 152.53.36.131 — Currently Live

  • Ollama (ollama/ollama:latest) — running
  • Open WebUI (ghcr.io/open-webui/open-webui:latest) — running on :3000
  • n8n (n8nio/n8n:latest) — running on :5678, proxied at n8n.itpropartner.com
  • n8n-postgres (postgres:16-alpine) — running
  • LiteLLM — NOT yet deployed (target for admin-ai migration)
  • Caddy 2.6.2 — active on 80/443, proxying n8n and Open WebUI
  • 1TB disk — 3% used (943 GB free), 32 GB RAM, 12C EPYC
  • All deployments per docker-service-deployment standard at /root/docker/*/

See references/admin-ai-migration-plan.md for the detailed cutover plan (Phase 03).

See references/core-rebalance-plan.md (created Jul 12, 2026) for the full service rebalance — moving Caddy, Ops Portal, Super Search, SearXNG, Grafana, cron from Core → app1; LiteLLM from Hetzner old-ai → Core; Ollama fallback from app1 → Core. 6-phase executable plan with DNS cutover and rollback.

app2 (RS 4000) — New

  • UNMS/UISP full stack (10 Docker containers: nginx, API, device-ws, PostgreSQL, SiriDB, RabbitMQ, Fluentd, Netflow)
  • UCRM (customer management CRM)
  • UniFi Controller (native Java app + MongoDB)
  • Hudu (full migration complete Jul 15 — 21 companies, 135 assets, 125 passwords, 18 custom layouts all verified on app2. Old server 178.156.130.130 deleted. Daily backup cron active: 3 AM ET → S3, 30-day retention)
  • Traccar (Docker at /root/docker/traccar/ — port 8082 web dashboard + 5000-5150 TCP/UDP for GPS device protocols). Cloudflare DNS: fleettracker360.com, gps.fleettracker360.com, www.fleettracker360.com → 152.53.39.202

app3 (RS 4000) — New

  • WordPress/Apex Track Experience (from wphost02 via RunCloud or standalone Nginx)
  • Other WordPress sites (from wphost02 / SiteGround)
  • Web apps requiring PHP/Nginx stack

app1-bu (Hetzner CPX11) — Stays

  • Warm standby for Core
  • Hermes gateway dormant
  • StrongSwan + L2TP
  • Watchdog checks Core every 5 min, 2 min confirmation window

Migration Status

Ready to migrate

  • ai.itpropartner.com (LiteLLM, Open WebUI, Ollama, Qdrant) → app1
    • LiteLLM uses PostgreSQL DB for model configs — dump/restore
    • Open WebUI data in SQLite at /app/backend/data/webui.db (~121 MB, 1.9 GB total)
    • Ollama models at /root/.ollama (need to copy or re-pull)
    • CRITICAL: admin-ai.itpropartner.com must have zero downtime. Set up new proxy first, test, switch DNS, decommission old.
  • n8n (current Hetzner app1) → app1
    • Docker compose at /opt/n8n/ — migrate volumes
    • Benefits from direct Ollama access on same box

Approved for migration (pending execution)

  • UNMS/UISP → app2 (Docker stack ready, DNS pending SiteGround update)
  • UniFi Controller → app2 (deployed Jul 12 at 152.53.39.202:8443, backup .unf file import needed)
  • Traccar GPS → app2 (ready for fresh Docker deploy, HERE Maps API for traffic-aware ETA)
  • Hudu → app2 (🔴 PARTIAL — DNS cut over, companies migrated, but assets/passwords tables empty. Old server still up with all data. Full DB re-dump/restore needed. See docker-service-deployment skill for verification queries.)
  • WordPress → app3

Stays at Hetzner

  • app1-bu (warm standby)
  • tony-vps (Anthony's Hermes)
  • Container host "docker" (178.156.168.35) — NPM, RustDesk, StrongSwan, RunCloud stack remain. Uptime-Kuma migrated to Core Jul 13. Server decommissioning deferred until RunCloud web apps migrate to app3 and RustDesk/StrongSwan decisions are made.

API Access Status (Jul 13, 2026)

  • Hetzner API token expired — server deletion/provisioning requires manual Hetzner Cloud Console access until token is rotated
  • SiteGround DNS — no API; all DNS changes for itpropartner.com and related domains must be manual through SiteGround web panel

Estimated Monthly Cost Breakdown

Server Provider Model Cost
Core netcup RS 2000 ~$24
app1 netcup RS 4000 ~$44
app2 netcup RS 4000 ~$44
app3 netcup RS 4000 ~$44
app1-bu Hetzner CPX11 ~$8
Total ~$164/mo
Current Hetzner total Hetzner 10 VPS ~$150/mo (inflated)

RBAC Model (for ops portal)

Role Scope Who
Admin Full access — servers, customers, billing, provisioning, DNS, audit logs Germaine
Tech/Support Tickets, server status, network health. No billing, no provisioning. Future hire
Sales/Onboarding Customer activations, provisioning status, order forms Future hire
Viewer Read-only, limited scope Anita, auditors

Roles built into ops portal from day one. Not retrofitted.

Docker Standard Reference

All services at /root/docker// with:

  • One docker-compose.yml, one .env (chmod 600)
  • Healthchecks on every service
  • Resource limits per container
  • Named volumes (not bind mounts for app data)
  • Internal-only networks for service-to-service traffic
  • Logging with rotation (max-size 10m, max-file 3)
  • Automated S3 backup of volumes

Pitfalls

  • admin-ai goes down = Sho'Nuff loses model access. Fallback chain: deepseek-v4-pro (admin-ai) → deepseek/deepseek-chat (OpenRouter) → cheap API fallbacks. No Ollama. admin-ai lives on app1 for fault isolation — Core and admin-ai should not share the same box.
  • Do NOT put all AI containers in one compose file. Each service gets its own /root/docker// — LiteLLM, Open WebUI are separate.
  • UniFi on app2 needs MongoDB. Must migrate the native MongoDB database along with the controller. Cannot just copy files.
  • Traccar device ports: 5000-5150 TCP+UDP — Ensure firewall allows this range. GPS devices connect directly (non-proxied DNS at gps.fleettracker360.com). Web dashboard on port 8082.
  • UNMS uses fixed version tags (3.0.140, 4.5.31). Do not use :latest for Ubiquiti images — they break on minor releases.
  • admin-ai placement decided Jul 13: app1, not Core. LiteLLM migration target is app1 (152.53.36.131). Core keeps Hermes + MCPs only. See architecture audit at /root/.hermes/references/architecture-audit-2026-07-13.md.
  • Ollama removed Jul 13. Too weak for real work, wasted RAM on Core. Replacement: cheap API fallbacks (deepseek-v4-flash, qwen3-flash via OpenRouter). Remove ollama.service from Core, delete llama3.3:70b (42GB disk waste).
  • Caddy + existing containers port conflict (app2) — When Caddy shares a box with services like UNMS nginx (port 443), Caddy's auto port 80 binding also conflicts. Fix: use auto_https disable_redirects + explicit domain:443 blocks in Caddyfile. Change existing containers to only bind their required ports.
  • Hudu migration: verify ALL tables, not just companies (Jul 15, 2026). Initial pg_dump/restore only copied the companies table — assets and asset_passwords were empty on the target. Post-migration verification must query every major table (companies, assets, asset_passwords) and compare row counts between old and new before declaring done. Use curl --resolve to hit both old and new IPs through the domain for side-by-side API comparison. See docker-service-deployment skill for the full pattern.
    {
        default_bind 152.53.192.33
    }
    
    This tells Caddy to bind only the public IP. Tailscale keeps its Tailnet IP. Both coexist without conflict. Verify with caddy validate --config /etc/caddy/Caddyfile then systemctl start caddy.