Files
jarvis/public_html/admin/downloads/INFRASTRUCTURE-REFERENCE.md
T
Claude f7309a15fc Rotate agent registration key; remove key literals from public scripts/UI
- install.sh, install-agent.sh: require JARVIS_REG_KEY env var or interactive prompt instead of baked-in key (matches install-mac.sh/install-windows.ps1 behavior)

- netscan.php: reuse AGENT_REGISTRATION_KEY constant instead of a duplicate literal

- agent.php + api.php: add session-authed "regkey" action so the admin install modal fetches the current key at runtime

- jarvis-agents.js: fetch reg key via /api/agent/regkey instead of hardcoding it; pass JARVIS_REG_KEY in the Linux install one-liner

- INFRASTRUCTURE-REFERENCE.md: scrub old key literal (rotated; real value lives only in api/config.php on VM211)

Key rotated on the box + rolled out to all 11 agents (verified all re-register online). New value is in the gitignored api/config.php only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:22:00 -05:00

73 KiB
Raw Blame History

INFRASTRUCTURE REFERENCE — COMPLETE SYSTEM MAP

Last Updated: 2026-07-07 (WAN IP change + FortiGate VIP update; JARVIS auto-deploy pipeline fully restored; admin panel fixes — Works tab, Site Health, Email Intelligence/Outbox; backup systems audited and fixed to include actual data, not just DB/config; new Blair HQ Backup Downloads feature)
Owner: Myron Blair — myronblair@outlook.com


TABLE OF CONTENTS

  1. Network Overview
  2. Cloud Servers
  3. On-Premise — Proxmox Hypervisors
  4. On-Premise — Virtual Machines
  5. NAS Storage
  6. Websites (all on DO)
  7. JARVIS AI System
  8. Phone System (FusionPBX)
  9. Networking & VPN
  10. Backup Systems
  11. SSH Quick Reference
  12. Critical Credentials Master List
  13. Git & Repository Management
  14. Network Equipment & Client Device Inventory

1. NETWORK OVERVIEW

INTERNET
    │
    ▼
[Cloudflare CDN] ──────────────────────────────────────────────────────────────
    │ (proxied DNS for public sites)
    │
    ├─► [DigitalOcean 165.22.1.228] — CyberPanel/OLS — All websites (7 sites)
    │
    └─► [FusionPBX 134.209.72.226] — FreeSWITCH PBX (SSH via DO relay)

HOME NETWORK (FortiGate router at 10.48.200.1)
    WAN: 97.245.77.197 (dynamic, DDNS: orbisne.fortiddns.com — changed 2026-07-07,
         previously 97.154.207.5, and 97.154.109.245 before that. All 35 FortiGate
         VIPs' extip updated to match on each change — see Section 9 for the full list.)
    │
    ├─► PVE1 Proxmox    10.48.200.90  (primary hypervisor)
    │     ├── VM 101    10.48.200.97  Home Assistant
    │     ├── VM 112    10.48.200.33  Jellyfin
    │     ├── VM 103    10.48.200.35  MediaStack (Sonarr/Radarr/qBT/Prowlarr)
    │     ├── VM 118    10.48.200.18  Homebridge
    │     ├── VM 120    10.48.200.110 NovaCPX hosting panel
    │     ├── VM 106    10.48.200.210  Ollama (local LLM + vision) — llama3.1:8b, llava:7b
    │     └── CT110     10.48.200.19/.67  WireGuard exit container (disabled at boot 2026-07-06, legacy/unused)
    │
    ├─► PVE2 Proxmox    10.48.200.91  (secondary hypervisor)
    │     └── VM 302    10.48.200.99  NetworkBackup
    │
    ├─► Synology NAS    10.48.200.249 — Media & backup storage
    ├─► Yealink T48S    10.48.200.2   — Ext 1000 (Myron Blair, Desk)
    ├─► Yealink T48S    10.48.200.43  — Ext 1001 (Tommy Ivy, Desk)
    ├─► Yealink AX86R   10.48.200.65  — Ext 1002 (Myron Blair, WiFi Work)
    ├─► Yealink T57W    10.48.200.3   — External SIP (United Mirror & Glass)
    ├─► Yealink T57W    10.48.200.83  — Ext 1003 (Kitchen)
    └─► Yealink T57W    10.48.200.85  — Ext 1004 (Master Bedroom)

FortiGate Port Forwards (VIP list confirmed 2026-07-07 via `show firewall vip` — this
replaces the earlier partial/approximate list; all extip values = current WAN IP above):
  homeassistant-8123              → 10.48.200.97:8123  (Home Assistant)
  proxmox1-8006                   → 10.48.200.90:8006  (PVE1 web UI)
  proxmox2-8007to8006             → 10.48.200.91:8006  (PVE2 web UI, ext 8007)
  proxmoxbku-8007to8008           → 10.48.200.92:8007  (ext 8008)
  reolink-9000/1935/554/8000      → 10.48.200.95 (4 VIPs, one per port)
  proxmox-ssh-claude-connection   → 10.48.200.29:22
  jarvis-8443                     → 10.48.200.211:80   (JARVIS — working forward, use this for external HTTP to JARVIS)
  jarvis-1972                     → 10.48.200.211:80   (legacy port, confirmed working again 2026-07-07 after the WAN IP/VIP update — earlier notes calling this "dead" were from before that fix)
  tunnel-51820 / Wireguard-CT110  → 10.48.200.67:51821 (UDP, WireGuard — 2 VIPs, same target)
  dns-53                          → 10.48.200.90:53
  bbs-23                          → 10.48.200.112:23   (SynchroNet BBS)
  NOVACPX-* (18 VIPs)             → 10.48.200.110 or .200 — HTTP/HTTPS/user/reseller/admin/webmail/SSH/FTP/SMTP/POP3/IMAP/DNS ports for NovaCPX hosting panel
  NPM-HTTP / NPM-HTTPS            → 10.48.200.200:80/443 (Nginx Proxy Manager, VM 105)
No VIP maps external port 80 directly to JARVIS — use jarvis-8443 (or the working
jarvis-1972) for external HTTP access instead.

2. CLOUD SERVERS

2A. DigitalOcean — Main Server

Field Value
IP 165.22.1.228
OS Ubuntu 22.04 LTS
Panel CyberPanel (OpenLiteSpeed)
SSH ssh root@165.22.1.228 — password: Gonewalk1974!@#
Purpose All public websites (7 sites) — webhook deploy for websites

Key Paths:

  • All sites: /home/<domain>/public_html/

  • Deploy log: per-site (website deploys only)

  • Watchdog log: /usr/local/lsws/logs/watchdog.log

  • Infra repo: /opt/infra

Services running:

  • OpenLiteSpeed web server (lsws) — serves all 7 sites
  • MySQL 8 — all site databases on localhost
  • Redis — session/cache
  • PHP 8.5 (lsphp85) — runtime for all sites
  • Cron jobs: website deploy runner (every 1 min), watchdog (every 5 min)

CyberPanel Web UI: https://165.22.1.228:8090
Login: myron / Joker1974!!!

phpMyAdmin: https://165.22.1.228/phpmyadmin
Login: myron / Joker1974!!!


2B. FusionPBX / FreeSWITCH — PBX Server

Field Value
IP 134.209.72.226
OS Debian (DigitalOcean droplet)
SSH Direct via Tailscale: ssh root@100.74.46.120 — password: Joker1974!@#
Direct SSH Only from: 107.178.2.130 / 97.154.109.245
Purpose VoIP phone system — handles all inbound/outbound calls

Web UI: https://fusion.orbishosting.com
Login: admin / fY7XP5swgtpbzrYLhkeVYkA4744

Database: PostgreSQL
User: fusionpbx / Password: pSJaF9mUJqPr4Sj5mwJyRqvCCpc / Host: 127.0.0.1

SIP Trunk: SignalWire
DID: +1 (817) 764-5007
Gateway: signalwire on external profile (port 5080, UDP)

How calls flow:

Caller → SignalWire SIP → FusionPBX:5080 → IVR (ext 900) → Ring extensions
Outbound: Phone → FusionPBX:5080 → SignalWire → PSTN

SSH Relay Command:

sshpass -p 'Gonewalk1974!@#' ssh -o StrictHostKeyChecking=no root@165.22.1.228 \
  'sshpass -p "Joker1974!@#" ssh -o StrictHostKeyChecking=no root@134.209.72.226 "COMMAND"'

3. ON-PREMISE — PROXMOX HYPERVISORS

PVE1 — Primary Hypervisor

Field Value
Local IP 10.48.200.90
External orbisne.fortiddns.com (FortiGate DDNS — auto-updates on WAN IP change)
OS Proxmox VE 8.x
SSH ssh root@orbisne.fortiddns.com OR ssh root@10.48.200.90 — password: Joker1974!!!
Web UI https://orbisne.fortiddns.com:8006root / Joker1974!!!
Purpose Runs VMs 101, 112, 113, 118, 120, 210, CT110

Useful commands:

qm list                          # list all VMs
qm start/stop/restart <VMID>     # control VMs
qm guest exec <VMID> -- bash -c "cmd"  # run command inside VM (requires QEMU agent)

JARVIS API Token: root@pam!jarvis=c45b5feb-f9a9-445d-a626-14fbb959f78b


PVE2 — Secondary Hypervisor

Field Value
Local IP 10.48.200.91
OS Proxmox VE 8.x
SSH ssh root@10.48.200.91 — password: Joker1974!!!
Web UI https://10.48.200.91:8006root / Joker1974!!!
Purpose Runs VM 302 (NetworkBackup); part of shared Proxmox cluster with PVE1

4. ON-PREMISE — VIRTUAL MACHINES

VM 100 — SynchroNet (PVE1)

Field Value
IP 10.48.200.112
OS Windows
Purpose SynchroNet BBS (bulletin board system)
Note VM is named "SynchroNet-50" in Proxmox but its real IP is .112, not .50.50 is an unrelated Raspberry Pi 5 hobby device (see Section 14). Confirmed 2026-07-06.

VM 105 — Nginx Proxy Manager (PVE1)

Field Value
IP 10.48.200.200
Purpose Reverse proxy management (NPM)
Note Confirmed 2026-07-06; VM name NPM-200 matches its IP correctly (unlike SynchroNet/Ollama above).

VM 101 — Home Assistant (PVE1)

Field Value
IP 10.48.200.97
OS Ubuntu + Home Assistant OS/Supervised
Web UI http://orbisne.fortiddns.com:8123myron / [HA password]
SSH Via HA web terminal only (Settings → Add-ons → Advanced SSH & Web Terminal)
Purpose Smart home automation — 212 entities (lights, switches, scenes, sensors)
JARVIS Agent ID: homeassistant_ha — pushes entity states to JARVIS every 10s

JARVIS ↔ HA Integration:

  • HA custom component at /config/custom_components/jarvis_agent/
  • Pushes all entity state changes to JARVIS /api/agent/ha_state (debounced 2s)
  • JARVIS admin toggles → queued in agent_commands table → HA executes natively
  • HA Long-lived Token (Jarvis2): eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzNmI0N2I1Njk5ZGQ0MTQ2ODMwZWFmYjZiYTQ1MjJkMSIsImlhdCI6MTc4MDIwMzU5NCwiZXhwIjoyMDk1NTYzNTk0fQ.sYRok-jRDlA4lFgWxLQELcEjkJNGQdprk6ZziLwLtXE

VM 112 — Jellyfin Media Server (PVE1)

Field Value
IP 10.48.200.33
OS Ubuntu 24.04.4 LTS (verified 2026-07-06; previously logged as 22.04 — reinstalled/upgraded at some point)
SSH ssh root@10.48.200.33 — password: Joker1974!!! (enabled 2026-06-14)
Web UI http://10.48.200.33:8096 (Jellyfin 10.11.11)
Purpose Media streaming server — Movies and TV shows
JARVIS Agent jarvis-agent.service running (verified 2026-07-06)
Remote access Tailscale installed, node jellyfin-112 at 100.81.145.48 — used for off-LAN streaming access

Media Libraries:

  • Movies: /mnt/mediastack/movies — NFS from MediaStack (10.48.200.35:/media/movies)
  • TV: /mnt/mediastack/tv — NFS from MediaStack (10.48.200.35:/media/tv)

NFS chain: Jellyfin → MediaStack → Synology NAS (/volume1/video/movies and /volume1/video/tv)

Admin token: 635142c218f2457e813a2de8dec75b05 (regenerated 2026-07-06 — previous token 7c0ccf78... had gone stale/invalid. The account's real username is admin, not myron — confirmed via the Users table in /var/lib/jellyfin/data/jellyfin.db; it's hidden from the public login list, which is why /Users/Public returns empty.)

If library scan needed:

curl -X POST "http://10.48.200.33:8096/Library/Refresh" \
  -H "X-Emby-Token: 635142c218f2457e813a2de8dec75b05"

If NFS stale after MediaStack changes:

umount -l /mnt/mediastack/movies && umount -l /mnt/mediastack/tv
mount /mnt/mediastack/movies && mount /mnt/mediastack/tv

VM 103 — MediaStack (PVE1)

Field Value
IP 10.48.200.35
OS Ubuntu 24.04.4 LTS
SSH Via PVE1: ssh -i /root/.ssh/id_rsa root@10.48.200.35 (no direct access from DO)
Purpose Automated media download pipeline + NFS server to Jellyfin
JARVIS Agent ID: MediaStack_2c00b1b8
Not Docker Despite the name, all services below run bare-metal via systemd, not docker-compose

Services:

Service Port Login API Key
qBittorrent :8080 admin / Joker1974!!!
Sonarr :8989 admin / Joker1974!!! b43e04350a594846b4ee95261c29e9e0
Radarr :7878 admin / Joker1974!!! 53c4268360444feeae5f98c0cc24e0e3
Prowlarr :9696 admin / Joker1974!!! 9d0ce6c5660743b5bf1c7951efc62252

All services run as root — required by Synology NFS ACL (only root can write).

VPN: NordVPN — nordlynx WireGuard interface — exit IP rotates (US Dallas servers), not a fixed IP (previous "181.214.226.188" was just a snapshot, not stable). All download/general traffic exits via NordVPN with LAN traffic exempted (Kill Switch + Firewall + LAN Discovery all enabled as of 2026-07-06 — see Section 9 for the full incident/fix history). If downloads stall, check nordvpn status first, then ip rule show for rules 32764/32765/table 205.

Media Flow:

IPTorrents (Prowlarr) → Sonarr/Radarr search → qBittorrent download
→ /mnt/nas/video/downloads (NAS)
→ Sonarr/Radarr import → /mnt/nas/video/tv or /mnt/nas/video/movies (NAS)
→ NFS → Jellyfin /mnt/mediastack/movies or /mnt/mediastack/tv

Indexer: IPTorrents via Prowlarr cookie auth
Cookie: uid=2237410; pass=JzLP2niTWxBJAZIU3yvtLbJzD55kdLeB
(Expires — if search fails, log into iptorrents.com, copy uid+pass cookies)

If Radarr/Sonarr shows "0 active indexers":

systemctl stop radarr
sqlite3 /var/lib/radarr/radarr.db "DELETE FROM IndexerStatus WHERE ProviderId=1;"
systemctl start radarr

SSH from DO:

sshpass -p 'Joker1974!!!' ssh -o StrictHostKeyChecking=no root@10.48.200.90 \
  'ssh -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa root@10.48.200.35 "COMMAND"'

VM 118 — Homebridge (PVE1)

Field Value
IP 10.48.200.18
OS Linux
SSH ssh myron@10.48.200.18 — password: Joker1974!
Purpose Apple HomeKit bridge — exposes non-HomeKit devices to Apple Home app
JARVIS Agent ID: homebridge_b57cbaea

VM 120 — NovaCPX Hosting Panel (PVE1)

Field Value
IP 10.48.200.110
OS Ubuntu 24.04 LTS
SSH ssh root@10.48.200.110 — password: Joker1974!!! (direct, no PVE hop)
Purpose Custom web hosting control panel (cPanel alternative), v1.0.27
JARVIS Agent ID: novacpx_e3b07264

Ports:

Port Panel
:8880 User panel
:8881 Reseller panel
:8882 Admin panel
:8883 Roundcube webmail

Admin: https://10.48.200.110:8882admin / Admin2026!
phpMyAdmin: http://10.48.200.110/phpmyadmin

File Paths:

  • Web root: /srv/novacpx/public/
  • DB (SQLite): /var/lib/novacpx/panel.db
  • Config: /etc/novacpx/config.ini
  • Git repo: /opt/novacpx-src/
  • GitHub: myronblair/novacpx (auto-deploy on push to main)

VM 106 — Ollama Local LLM + Vision (PVE1)

Field Value
IP 10.48.200.210
OS Ubuntu (cloud image)
SSH ssh root@10.48.200.210 via PVE1 hop — password: Joker1974!!! (also reachable as ssh myron@10.48.200.210 — password Joker1974!, then sudo). VM's name is Ollama-95 but its real IP is .210, not .95 — a prior version of this doc had a stray SSH line pointed at .95 (nothing listens there); confirmed 2026-07-06 that .210 is correct.
Purpose Local AI inference — chat (llama3.1:8b) + vision (llava:7b)
API http://10.48.200.210:11434 (Ollama REST API)
JARVIS Agent ID: ollama-ai_ubuntu
Models llama3.1:8b (chat/Tier 1), llava:7b (vision cascade)

JARVIS uses this as Tier 1 AI — if Ollama is down, falls back to Groq (cloud). Vision cascade: Arc Reactor calls Claude first; if Claude credits depleted, falls back to llava:7b via Ollama. Vision is enabled via: /etc/systemd/system/jarvis-arc.service.d/vision.confOLLAMA_VISION_MODEL=llava:7b


VM 302 — NetworkBackup (PVE2)

Field Value
IP 10.48.200.99
OS Ubuntu/Linux
SSH ssh myron@10.48.200.99 — password: Joker1974! (then sudo)
Purpose Network backup storage / backup operations
JARVIS Agent ID: networkbackup_NetworkB

CT110 — WireGuard Exit Container (PVE1)

Field Value
IP 10.48.200.19 / 10.48.200.67
Purpose Legacy WireGuard exit tunnel to DO (10.200.0.4 via wg-exit) — currently NOT used by MediaStack/Jellyfin
Note MediaStack uses NordVPN directly; Jellyfin uses wg1 peer on MediaStack for NFS only
2026-07-06 incident MediaStack's wg0 client for this tunnel (/etc/wireguard/wg0.conf) had a PostUp hook installing its own iptables kill-switch (REJECT all output not via wg0 or marked 51820), and wg-quick@wg0.service was still enabled at boot on MediaStack despite this tunnel being unused — this caused a full LAN/SSH lockout to MediaStack the moment that service came up. Disabled wg-quick@wg0 at boot on MediaStack to prevent recurrence; the tunnel itself and CT110 are untouched. See NordVPN section below for the related (separate) LAN Discovery bug found in the same incident.

5. NAS STORAGE

Synology NAS

Field Value
IP 10.48.200.249
Login nas / Joker1974!!!
DSM Web UI http://10.48.200.249:5000
Purpose Primary media and download storage

NFS Share: /volume1/video and its subpaths /volume1/video/movies, /volume1/video/tv — all exported to MediaStack (10.48.200.35) only, per /etc/exports on the NAS (verified 2026-07-06)

Directory structure:

/volume1/video/
  movies/       ← Radarr imports here; NFS-exported to Jellyfin via MediaStack
  tv/           ← Sonarr imports here; NFS-exported to Jellyfin via MediaStack
  downloads/    ← qBittorrent downloads here (temp)
    incomplete/ ← in-progress torrents

Important: Synology NFS ACL only allows root to write. All services on MediaStack run as root.


6. WEBSITES (ALL ON DO)

All sites are at /home/<domain>/public_html/ on DO (165.22.1.228).
Auto-deploy: Push to main on GitHub → webhook → server pulls in ~1 min.
GitHub PAT: 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 (rotated 2026-07-05 — old PAT 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 was found exposed in .git/config on all 6 original sites and must be treated as compromised/revoked)


jarvis.orbishosting.com — JARVIS AI Dashboard (MOVED TO PVE1 VM 211)

Field Value
URL http://jarvis.orbishosting.com (port 80 — old :1972 reference was wrong, corrected 2026-07-04)
Path /var/www/jarvis/ (on JARVIS VM 10.48.200.211)
GitHub myronblair/jarvis
Login myron / Joker1974!!!
Purpose Iron Man-style AI home dashboard with voice control, smart home, media, planner

See Section 7 for full JARVIS details.


worktracking.orbishosting.com — ChuckCo Time Keeper

Field Value
URL https://worktracking.orbishosting.com
Path /home/worktracking.orbishosting.com/public_html/
GitHub myronblair/chucko (private)
Gitea myron/chucko (pull-mirror of GitHub)
Local clone C:\Users\myron\repos\chucko on admin Windows machine
Purpose Work-tracking app for a flat-rate 5-day (FriThu) work week — self-reported hours via personal secret-URL tokens (no login), single shared admin password, phone-friendly screenshot pages for texting workers/payer
Admin URL https://worktracking.orbishosting.com/admin/login.php — password Joker1974!!!
DB workt_track_db / workt_track_user / ZWCNMRP2N5NVPsghmve5aRS9
Linked from Blair HQ web.orbishosting.com dashboard's "Websites" card has direct links to the admin login and the all-workers overview page (site-wide token 972f82cbf7832fdb2cffcdcc84129a4af69e30bd)
Note Built 2026-07-05. includes/config.php (DB creds, admin password hash, site token) lives outside public_html/webroot and is intentionally NOT in the git repo.
Admin login rate limiting Added 2026-07-06 — login_attempts table (ip_address, attempts, last_attempt) in workt_track_db; 5 failed attempts locks that IP out for 15 minutes.
Code review (2026-07-06) 5 findings fixed and deployed: w.php mark_paid now rejects any week_start that isn't the true current week (was trusting client input); admin/worker.php no longer double-HTML-escapes the page title; all.php now shows a Paid/Unpaid badge per worker for the displayed week; s.php/p.php/all.php validate the week param before building dates (malformed input used to throw an uncaught DateTime exception); admin login rate-limited (see above).

tomsjavajive.com — Tom's Java Jive

Field Value
URL https://tomsjavajive.com
Path /home/tomsjavajive.com/public_html/
GitHub myronblair/tomsjavajive
Purpose Coffee shop e-commerce — products, orders, loyalty, wallet, reviews
Admin URL https://tomsjavajive.com/admin/
Admin Login admin@tomsjavajive.com / Joker1974!!! OR myronblair@outlook.com / Joker1974!!!
DB toms_tjj_db / toms_tjj_user / +60wlPc+55e@gFq4
Email CyberMail API key: sk_live_7f9b0f9a29f6de31a0d229d4af75d56b094ad724fc58a57d
Email From noreply@tomsjavajive.com / Toms Java Jive (set in DB settings table)

epictravelexpeditions.com — Epic Travel Expeditions

Field Value
URL https://epictravelexpeditions.com
Path /home/epictravelexpeditions.com/public_html/
GitHub myronblair/epictravelexpeditions
Purpose Travel booking / expeditions website
DB epic_travel_db (see api/config.php)

parkerslingshot.epictravelexpeditions.com — Parker Slingshot (OLD)

Field Value
URL https://parkerslingshot.epictravelexpeditions.com
Path /home/epictravelexpeditions.com/parkerslingshot/
GitHub myronblair/parkerslingshot
Purpose Old slingshot rental site (superseded by parkerslingshotrentals.com)

parkerslingshotrentals.com — Parker Slingshot Rentals (LIVE)

Field Value
URL https://www.parkerslingshotrentals.com
Path /home/parkerslingshotrentals.com/public_html/
GitHub myronblair/parkerslingshotrentals
Purpose Polaris Slingshot rental — bookings, e-signature waiver, admin management
Admin /admin/index.phpadmin / Parker2026!
DB park_slingshot / park_slingshotuser / 4@rxg*8kovxCr7w6
Square Production token: EAAAl3FsAu_2ri8kZE_ENEyi2T_C8HXXm5XQFY6Lbnd8SX6FqYp8J_upUeXNYh7v

orbishosting.com — Orbis Hosting (Landing Page)

Field Value
URL https://orbishosting.com
Path /home/orbishosting.com/public_html/
GitHub myronblair/orbishosting
Purpose Public landing page for Orbis Hosting brand

orbis.orbishosting.com — Orbis Hosting Portal

Field Value
URL https://orbis.orbishosting.com
Path /home/orbis.orbishosting.com/public_html/
GitHub myronblair/orbis-hosting-portal
Purpose Customer-facing hosting portal

tomtomgames.com — TomTom Games

Field Value
URL https://tomtomgames.com
Path /home/tomtomgames.com/public_html/
GitHub myronblair/tomtomgames
Purpose Gaming website
DB tomtom_games_db (see config)
Email CyberMail API key: sk_live_7f9b...

Code review pass — all 4 DO-hosted business sites (2026-07-06)

Full security/correctness review of tomsjavajive.com, tomtomgames.com, parkerslingshotrentals.com, epictravelexpeditions.com (orbishosting.com apex and orbis.orbishosting.com excluded — not live/do-not-touch per earlier note). 10 findings, all fixed, tested, committed, and pushed to each site's main branch same day.

Critical (live exploitable, now fixed):

  • tomsjavajive.com api/orders.php had a literal // Admin check would go here comment — anyone who knew/found an order_id could silently change any order's status (cancelled/delivered/refunded) or overwrite tracking numbers, and read another customer's full order (name, email, address, items), with zero auth. Fixed: update_status now requires AdminAuth::isLoggedIn(); the GET ?id= lookup now requires admin or the order's own customer.
  • parkerslingshotrentals.com uploads/ — customer driver's license and insurance-card photos were directly downloadable with no login at all (the Order deny,allow/Require all denied pattern in uploads/.htaccess doesn't work on this OpenLiteSpeed setup, same class of gotcha as the .git exposure found earlier). Verified live via a throwaway test file before fixing. Fixed with the working RewriteRule .* - [F,L] pattern in both the nested uploads/.htaccess and the root .htaccessrequired an actual systemctl restart lshttpd to take effect (touching /usr/local/lsws/cgid alone, which only prevents the cron's own restart trigger, was NOT sufficient for a new rewrite rule to be picked up — worth remembering for future .htaccess changes on this server).

High (fixed):

  • tomsjavajive.com admin/orders.php — the exact "same named PDO param reused twice" bug that already bit awardPoints() had recurred in the order search box (:search bound once, referenced 3 times), causing a fatal SQLSTATE[HY093] on every admin search. Fixed with distinct :search1/:search2/:search3.
  • tomtomgames.com admin/index.php — stored XSS: renderGamerOverview() inserted username/alias/email into innerHTML without the escHtmlA() helper used correctly everywhere else in the same file. Alias has no character restriction, so a malicious alias could execute script in an admin's session the moment they open that user's profile. Fixed.

Medium (fixed):

  • tomtomgames.com includes/square.php (untracked by git — lives outside public_html, fix deployed to the server only) — charge()/refund() generated a fresh uniqid() idempotency key on every call, defeating Square's duplicate-protection entirely. Fixed: keyed off md5(source_id) for charges and md5(payment_id . amount) for refunds, so retries/double-clicks are recognized as duplicates.
  • parkerslingshotrentals.com contact.php — booking availability check + insert had no locking, allowing a double-booking race under concurrent submissions; the deposit-hold idempotency key was suffixed with time() (changes every second, so retries aren't deduped). Fixed: wrapped the check+insert in a MySQL GET_LOCK/RELEASE_LOCK pair scoped to the requested date range, and made the idempotency key stable ({ref}-dep, no time suffix).
  • epictravelexpeditions.com api/config.php — DB credentials, JWT secret, admin password hash, and mail API key sat in plaintext inside the webroot (protected only by an .htaccess rewrite rule, unlike every sibling site where secrets already live outside public_html). Relocated to /home/epictravelexpeditions.com/api-secrets.php (was already gitignored, so no git history exposure). .git itself was already correctly relocated to git-data/ (just a 49-byte pointer file in the webroot, blocked by .htaccess) — no action needed there.

Low (fixed):

  • tomtomgames.com api/purchase.phplogActivity() referenced undefined $paymentMethod/$amountDollars (should be $method/$priceCents) in two places, producing PHP warnings and blank values in the purchase audit log for every transaction. Fixed.
  • epictravelexpeditions.com api/api/testimonials.php — the public image-upload endpoint (no login required, by design) had no rate limiting, allowing storage/bandwidth abuse via scripted repeat uploads. Added a upload_rate_limits table + per-IP cap (5 uploads/hour).

7. JARVIS AI SYSTEM

URL: http://jarvis.orbishosting.com
Files: /var/www/jarvis/ on JARVIS VM (PVE1 VM 211 — 10.48.200.211, 8 cores, 16GB RAM)
DB: jarvis_dbjarvis_user / J4rv1s_Pr0t0c0l_2026!
Login: myron / Joker1974!!!
Admin portal: http://jarvis.orbishosting.com/admin

Security hardening (2026-07-06 code review)

Full front-end + admin panel review found and fixed 8 issues, 2 of them live critical exposures:

  • INFRASTRUCTURE-REFERENCE.md was publicly downloadable with zero auth (static nginx path bypassed the admin session check entirely). Fixed: file moved to /var/www/jarvis-private/INFRASTRUCTURE-REFERENCE.md (owned www-data:www-data, mode 640, NOT under public_html so nginx never serves it directly), and the DOCS tab now downloads it via a new authenticated docs_download action in admin/index.php that gates on the existing loggedIn() check and streams the file with readfile().
  • Two full backup copies of the 5000-line admin panel (index.php.bak2, index.php.bak.<date>) were sitting in public_html/admin/ and downloadable with no auth, exposing the entire admin source/attack surface. Relocated to /root/jarvis-old-backups/ (no secrets were found in them, so no credential rotation was needed).
  • esc() (the admin panel's JS HTML-escaper) doesn't escape ', so it doesn't protect values embedded inside a single-quoted JS string within an onclick attribute — HTML-decoding happens before the JS parser sees it. Added a proper escJs() helper (backslash + quote + newline escaping, then HTML-escape) and applied it to the Network/Alerts/Intents/Custom-News/Calendar-Feeds edit-modal onclick handlers, which were reachable by e.g. any device on the LAN setting a malicious DHCP/mDNS hostname.
  • Same class of bug on the front-end dashboard (assets/js/jarvis-app.js, assets/js/panels/jarvis-agents.js): device names and news article titles/sources were inserted into innerHTML completely unescaped — a rogue LAN device or a malicious/compromised news feed could inject script that runs with the logged-in session. Added escHtml()/escJs() helpers directly in jarvis-app.js and applied them to device names, VM names, agent hostnames, and news content.
  • session.cookie_httponly was Off server-wide (PHP default), meaning the actual session cookie — not just the app's own bearer token — was readable via document.cookie from any of the above XSS bugs. Fixed at the PHP-FPM level (/etc/php/8.3/fpm/php.ini): session.cookie_httponly = 1, session.cookie_samesite = Lax, php8.3-fpm restarted. Verified live: Set-Cookie now includes HttpOnly; SameSite=Lax.
  • api.php had Access-Control-Allow-Origin: * — tightened to an explicit allow-list of the real JARVIS origin only, with Access-Control-Allow-Credentials: true only sent when the origin matches.
  • Two admin actions (Arc Reactor restart/setup) called an undefined function k() instead of the real JSON responder j(), causing a PHP fatal error even though the underlying systemctl command still fired. Fixed (verified via direct API test — clean {"ok":true,...} response now).
  • Calendar feed passwords were stored and returned in plaintext via cal_feeds_list's SELECT *. Changed to return a has_password boolean instead of the raw password (the edit UI never actually displayed the password back anyway — write-only field, "leave blank to keep").

All fixes verified via direct API testing (SSH + curl through the login/action flow) since this doesn't have a staging environment. Pushed to myronblair/jarvis master, commit 24bc876.

Functional bugs fixed (2026-07-06)

  • "WEB HOST" card on the front dashboard always showed --%/offline. Root cause: the DO server (165.22.1.228) never had the JARVIS monitoring agent installed — every other host in the fleet had one, this one didn't. Installed it with curl -sk http://10.48.200.211/install-agent.sh | bash -s jarvis-do linux (hostname arg jarvis-do + the DO server's actual machine hostname orbis produces the expected agent_id=jarvis-do_orbis that do_server.php queries for). Also found and fixed two secondary issues hit along the way: the agent's config pointed at a dead/stale Tailscale peer (jarvis-211, 100.77.178.42, offline 9+ days) instead of the current active one (jarvis-211-1, 100.78.153.71) — likely left over from a VM Tailscale re-auth at some point; and a stale cached API key in /var/lib/jarvis-agent/state.json from a registration attempt that never actually completed server-side, which had to be deleted to force a clean re-registration. Verified live: do_server field in /api/do now returns real cpu/mem/disk/online:true instead of an empty array.
  • "WEBSITES" list (part of the same JARVIS SERVER panel) was always empty, and the KB intent generator's 4-hour "don't run again too soon" guard was potentially never actually throttling correctly. Root cause, found while investigating the above: api/config.php sets date_default_timezone_set('America/Chicago'), and several places compute "how old is this DB timestamp" via PHP's time() - strtotime($mysqlDatetimeString). Since MySQL's NOW()/stored datetimes are naive UTC strings, strtotime() under a non-UTC default timezone misinterprets them as being in Chicago time, which throws every such comparison off by the UTC offset (5-6 hours) — in this case making facts_collector.php's freshness gate for the sites (and incidentally proxmox/ollama) categories always look artificially fresh, so the site-health checks that populate the WEBSITES list stopped actually running. Fixed in facts_collector.php's $fresh() helper and kb_intent_generator.php's run-guard by moving the elapsed-time comparison entirely into SQL (updated_at > DATE_SUB(NOW(), INTERVAL ? SECOND)), which sidesteps PHP timezone handling altogether. Also fixed a leftover cosmetic label (do_server.php) still showing jarvis.orbishosting.com:1972 from before the JARVIS port fix.
  • Note for future work: the time() - strtotime($dbTimestamp) anti-pattern appears in a couple of other files (chat.php, email.php, planner.php) but only for display formatting of dates, not elapsed-time threshold checks — lower priority, not fixed in this pass, but worth a look if any displayed timestamps look off by a few hours.

Major fixes (2026-07-07)

/var/www/jarvis finally became a real git repo. It had no .git at all — the actual live admin panel had diverged completely from myronblair/jarvis on GitHub (the 2026-07-06 security review above was done via direct SSH edits on the live server, never committed anywhere). Reconciled by committing the live state as the new baseline and merging origin/master history with -s ours (preserves lineage without overwriting the live content), then pushed. /tmp/jarvis-deploy (a separate, stale clone used briefly as a staging area) is now superseded — don't use it; edit /var/www/jarvis directly.

KB Intent Generator cron was silently broken since ~2026-07-05. The rotation-engine refactor (moving from a hardcoded topic array to the kb_generator_topics table) had dropped the $SYSTEM prompt and the entire safe_insert() function — every 6-hour run crashed instantly on an uncaught TypeError, invisible everywhere because config.php sets error_reporting(0). Restored both from a .bak2 backup. Table grew from 386 → 900+ real intents once fixed.

GitHub auto-deploy pipeline was completely non-functional — likely since it was first set up, not just recently:

  • The GitHub webhook itself pointed at the dead port :1972 (already known-stale) → 502 on every delivery
  • WEBHOOK_SECRET was never defined in config.phpwebhook.php would have 500'd even if reached
  • Branch check compared against refs/heads/main, but this repo has always used master → every real push would have been silently skipped
  • /usr/local/bin/jarvis-deploy.sh (the queue consumer) didn't exist on this host at all
  • All fixed: webhook URL updated to use port 1972 (confirmed working again after the 2026-07-07 WAN IP change — Cloudflare passes this non-standard port through cleanly, unlike port 80 which it force-redirects to HTTPS), secret added, branch check fixed, jarvis-deploy.sh rebuilt (git pull + per-file PHP syntax check + auto-revert on failure, /var/log/jarvis/deploy.log, cron * * * * *) and jarvis-watchdog.sh rebuilt (checks nginx/php8.3-fpm/mariadb/redis-server, restarts if down, /var/log/jarvis/watchdog.log, cron */5 * * * *) — both were referenced in the Works tab and by cron history but the actual scripts had never existed on this VM. Verified end-to-end with a real push: webhook → queue → cron puller → git pull → live, inside ~1 minute.

Admin panel (Works / Site Health / Email Intelligence tabs) — several real bugs, not just polish:

  • Facts Collector always showed "UNKNOWN" last-run (log-format regex expected a [bracketed] timestamp; this script's lines don't have one) — it was actually running fine every 3 min the whole time
  • Calendar Sync always showed "UNKNOWN" and was never actually scheduled at all (no crontab entry existed, despite the UI listing it as an active "every 15 min" job) — added the missing cron entry, fixed a regex typo (\d{2}{4}\d{4})
  • Only KB Intent Generator's "RUN" button had a live-streaming-log popup; Facts Collector/Stats Cache/Calendar Sync just fired a toast with no way to see the eventual result. Added a generic worker_log/worker_history backend pair and reused the same popup pattern for all of them
  • Site Health: parkerslingshotrentals.com showed as truncated "parkerslingshots" (stale frontend label map referencing an old backend key name that no longer exists) — fixed, and widened the card layout (.site-grid/.site-card, 260px min vs the shared 160px stat-card) since domain names don't fit the generic dashboard-stat width. Also fixed the site-check freshness guard (300s vs a 180s cron interval meant sites were only re-checked every other run) — now 170s
  • Email Intelligence inbox always 404'd — it proxied to https://165.22.1.228/api/email, a leftover from before JARVIS moved off the DO server; that endpoint hasn't existed since the migration. Now reads email_triage directly (same table email_action_items already used)
  • Compose/reply email silently produced blank drafts regardless of AI provider status — the PHP dispatcher and the Python reactor used different field names for the same payload (recipient/subject/auto_send vs the reactor's to_email/subject_hint/send; content vs body for replies). Fixed both call sites
  • llm_call()'s provider fallback never actually fired for any explicitly-requested provider (i.e. every real caller) — a failure on the first-choice provider raised straight up instead of falling through to the others. Fixed; also fixed _ollama_call() silently returning "" instead of raising when Ollama reports an error (e.g. model not pulled), which had been masking failures as fake successes
  • Ollama had no text-completion model installed (only llava:7b, the vision model) — pulled llama3.1:8b (~4.9GB) so the Claude→Groq→Ollama fallback chain is a real 3-tier chain, not 1.5. Bumped Ollama's call timeout 30s→90s (cold loads alone took 15s+ in testing)
  • Added Groq 429 retry-with-backoff (parses Groq's own rate-limit-reset header) — this account's tier is only 12,000 tokens/min, and gmail_triage alone can exhaust it, so transient contention with compose/reply is common
  • Compose modal used to toast "dispatched" and never check back — if the job failed 3090s later in the background there was no notification at all, it just silently vanished. Now polls the actual job status (arc_job_get) until done/failed and shows the real result
  • Added a missing SEND button for queued drafts in the Outbox — compose had always only ever produced a status='queued' draft with no way to actually send it. Also fixed VIEW always showing "(no body)" (it read from the list endpoint, which omits body for performance, instead of the single-item endpoint)
  • Still open: Claude API credits are depleted (billing, not a code issue) — restore at console.anthropic.com if Claude-quality drafting is wanted as the default again

Architecture (end-to-end)

Voice (browser mic)
  → SpeechRecognition API
  → Wake phrase: "wake up JARVIS" / "daddy's home"
  → "JARVIS [command]" triggers action
  → /api/chat.php (4-tier AI)
       Tier 0.7: KB intents / planner (tasks, appointments)
       Tier 1:   Knowledge Base (MySQL)
       Tier 1.5: Ollama (10.48.200.210:11434, llama3.1:8b) — local LLM
       Vision:   Ollama llava:7b (via Arc Reactor _vision_call cascade)
       Tier 2:   Groq (cloud, model: compound-beta-mini)
       Tier 3:   Claude API (Anthropic, fallback)
  → ElevenLabs TTS → browser speaker

Arc Reactor (AI Job Processor)

Service: jarvis-arc (systemd) — port 7474 Runtime: /opt/jarvis-arc/ (Python venv, reactor.py) Log: /var/log/jarvis/arc.log Admin button: Workers → Daemons → SETUP (live popup) / RESTART Vision: Claude → Ollama llava:7b → graceful fallback Vision config: /etc/systemd/system/jarvis-arc.service.d/vision.conf

systemctl status jarvis-arc
systemctl restart jarvis-arc
journalctl -u jarvis-arc -f

To re-deploy Arc Reactor from source: Use Workers → Daemons → SETUP in JARVIS admin (live log popup shows progress).

Planner: Tasks / Directives / Missions

Admin UI sections backed by real schema + live API — verified working 2026-07-02 (created/read/deleted a test row in each, end-to-end).

Tasks — simple to-do list, stored directly in jarvis_db.

  • Table: tasks (title, notes, category, priority [urgent/high/normal/low], status [pending/in_progress/done/cancelled], due_date, due_time)
  • API: task_list (GET), task_save (POST, form-encoded), task_done (POST), task_delete (POST)

Directives — OKR-style goals with key results, stored directly in jarvis_db.

  • Tables: directives (title, description, category, status, priority, target_date) + directive_key_results (directive_id, title, current_value, target_value, unit) + directive_links (directive_id, link_type, link_id — links a directive to a task/etc.)
  • API: directive_list, directive_get, directive_save (POST, JSON body via php://input, id passed as ?id= query param on update), directive_delete

Missions — automation workflows, NOT stored in jarvis_db — proxied through Arc Reactor (port 7474) which owns the mission state.

  • Arc Reactor endpoints used by admin: GET/POST /missions, GET /missions/{id}, GET /missions/{id}/runs, PUT/POST /missions/{id}, DELETE /missions/{id}, POST /missions/{id}/run
  • JARVIS-side mirror tables exist (missions, mission_runs, mission_steps) but the admin panel reads/writes live via Arc Reactor's HTTP API, not directly against these tables
  • API: mission_list, mission_get, mission_runs, mission_save (POST, JSON body, id as ?id= on update), mission_delete, mission_run, mission_toggle
  • If Arc Reactor is down, these calls return {"error":"Arc Reactor unreachable"} — check systemctl status jarvis-arc first

As of 2026-07-02: all three tables are empty (0 rows) — features are fully functional, just unused so far.

Deploy Pipeline

Code edit → git push → GitHub webhook → /webhook.php (HMAC verified)
→ /tmp/jarvis-deploy-queue.txt → /usr/local/bin/jarvis-deploy.sh (cron 1min)
→ git pull + PHP syntax check → deploy or auto-revert

Webhook secret: 4c8805f0285214ff0a0602b5880270b935f36a896946c7f1

Agent System

Agents installed on all servers — phone home every 10s (heartbeat) / 30s (metrics).
Registration key: [rotated 2026-07-07 — stored in api/config.php on VM211, not documented here]
Install command: curl -sk http://10.48.200.211/install-agent.sh | bash -s <hostname> <linux|proxmox>

Self-Healing Watchdog

/usr/local/bin/jarvis-watchdog.sh — runs every 5 min (root cron on DO)
Restarts: lsws, mysql, redis if down
Restarts offline Proxmox VM agents via qm guest exec

Cron Jobs (DO server)

Schedule Script Purpose
Every 1 min jarvis-deploy.sh Process GitHub deploy queue
Every 3 min facts_collector.php Collect agent metrics, KB facts, site health
Every 5 min stats_cache.php Weather, news, Proxmox stats refresh
Every 5 min jarvis-watchdog.sh Self-healing: restart dead services

8. PHONE SYSTEM (FUSIONPBX)

Extensions

Ext Name Phone IP SIP Password
1000 Myron Blair — Desk Yealink T48S 10.48.200.2 Xk9mPw3nQv7rLs2t
1001 Tommy Ivy — Desk Yealink T48S 10.48.200.43 Tv8xNm4pWq6rZs3k
1002 Myron Blair — WiFi Work Yealink AX86R 10.48.200.65 yXHaJTwa8rj?$GkrVFQB
1003 Kitchen Yealink T57W 10.48.200.83
1004 Master Bedroom Yealink T57W 10.48.200.85
1010 Parker County Slingshot Virtual (voicemail only)
1011 Epic Travel Expeditions Virtual (voicemail only)
1012 Tom's Java Jive Virtual (voicemail only)
900 IVR (auto-attendant)

Phone SIP Settings (all phones):

  • Server: 134.209.72.226
  • Port: 5080
  • Transport: UDP

Provisioning URL: https://fusion.orbishosting.com/app/provision/
(Username: provision-master, Password: Joker1974!!!)

Call Flow

Inbound (+18177645007)
→ SignalWire → FusionPBX:5080 (UDP)
→ signalwire-inbound dialplan (catch-all ^.*$)
→ IVR ext 900 (ivr_menu_16k.wav)
→ Routes to extensions 1000/1001/1002/1003/1004

Outbound
→ Phone → FusionPBX:5080
→ signalwire gateway → SignalWire → PSTN

FreeSWITCH CLI Commands

fs_cli -x "sofia status profile external reg"   # check registrations
fs_cli -x "sofia xmlstatus gateway"             # check SignalWire gateway
fs_cli -x "reloadxml"                           # reload config (safe)
fs_cli -x "reloadacl"                           # reload ACL (safe)
# AVOID: sofia profile external restart (drops all phone registrations)

9. NETWORKING & VPN

FortiGate Firewall

  • WAN IP: 97.245.77.197 (dynamic — changed 2026-07-07; see Section 1 for the full VIP list, all 35 updated same day)
  • DDNS: orbisne.fortiddns.com (FortiGate auto-updates on IP change)
  • Note: a WAN IP change requires manually updating every VIP's extip (the FortiGate does not do this automatically even though DDNS updates) — the FortiGate CLI script used 2026-07-07 is a simple loop of edit "<vip-name>" / set extip <new-ip> / next for all 35 VIPs. Also update Cloudflare's DNS A-records for any *.orbishosting.com hostnames pointing at this WAN IP.
  • Blocks: outbound port 53 (DNS) — MediaStack uses PVE1 dnsmasq (10.48.200.90) as resolver → 100.100.100.100
  • Upstream DNS (changed 2026-07-05): Network → DNS set to "Specify" mode — Primary 1.1.1.1 (Cloudflare), Secondary 8.8.4.4 (Google). Previously defaulted to the router itself (10.48.200.1)/ISP-provided servers. Admin: https://10.48.200.1:9443admin / Joker1974!!!. Note: 8.8.8.8 specifically showed as "Unreachable" during setup (transient — ISP's own DNS servers were also showing high latency at that moment); 1.1.1.1/8.8.4.4 tested healthy and are what's live now.

Port Forwards:

External Port Internal Destination Purpose
:8006 PVE1:8006 Proxmox web UI
:8123 HA VM:8123 Home Assistant
:22 HA VM:22 HA SSH (unreliable)

WireGuard — Jellyfin ↔ MediaStack

  • MediaStack runs WireGuard server on wg1 (port 51820, subnet 10.200.0.1/24)
  • Jellyfin peer: 10.200.0.3 (active handshake)
  • Used for NFS media file access ONLY — not internet VPN

NordVPN — MediaStack Internet Traffic

  • Interface: nordlynx on MediaStack
  • Exit: rotating US (Dallas) NordVPN servers, not a fixed IP
  • Policy routing: table 205 (non-LAN traffic via nordlynx), managed partly by NordVPN itself and partly by a custom nordvpn-routing.service unit (/etc/systemd/system/nordvpn-routing.service) that adds the ip rule for fwmark 0xe1f1 → table 205
  • Required for IPTorrents access (blocks non-VPN IPs)

2026-07-06 incident (fixed): nordvpnd had been crash-looping since ~2026-06-07 (/var/lib/nordvpn/data/settings.dat was corrupted/empty) — meaning NordVPN was not actually protecting MediaStack's traffic for about a month; downloads were exiting on the plain home IP. Fixed by clearing the corrupt file, restarting the daemon, and re-logging in.

While fixing this, found NordVPN's own "LAN Discovery" setting was disabled. With Routing: enabled and LAN Discovery off, connecting NordVPN's full-tunnel routing suppresses the main routing table's resolution for anything that would exit via eth0including same-subnet LAN traffic — so the entire VM became unreachable (SSH/ping) from the rest of the LAN the moment NordVPN connected. Fixed with nordvpn set lan-discovery on, which adds explicit priority rules for private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16) ahead of the tunnel catch-all. Verified LAN reachability holds with Kill Switch + Firewall both re-enabled after this fix.

If MediaStack becomes unreachable after a NordVPN reconnect again: check nordvpn settings for LAN Discovery: enabled first. If SSH is already dead, the network path itself is unusable — recover via the Proxmox host instead: qm guest exec 103 -- /bin/bash -c 'export HOME=/root; nordvpn ...' (guest-exec needs HOME set manually or the nordvpn CLI fails).

Current confirmed-good settings (2026-07-06): Firewall enabled, Kill Switch enabled, Auto-connect enabled, LAN Discovery enabled, Routing enabled.


10. BACKUP SYSTEMS

Audited 2026-07-07 — file-coverage gap found and fixed in 2 of the 4 systems below. The core question asked: if the source server were lost outright, does the backup actually contain what's needed to restore it, or just a database/config fragment? JARVIS and the DO server both failed that test and are now fixed; Proxmox and FusionPBX were checked and are genuinely fine as designed.

JARVIS Backup

  • Script: /usr/local/bin/jarvis-backup.sh (also at /var/www/jarvis/deploy/)
  • Output: /var/backups/jarvis/jarvis_backup_TIMESTAMP.tar.gz
  • Log: /var/backups/jarvis/backup.log
  • Retention: 7 days (auto-purge)
  • Trigger: JARVIS admin → Backups → RUN BACKUP NOW, or run script directly
  • DB: jarvis_dbjarvis_user / J4rv1s_Pr0t0c0l_2026!
  • Fixed 2026-07-07: this was MySQL-dump-only — a DB dump alone is useless without the actual application. Now also archives /var/www/jarvis (app code + config.php), /opt/jarvis-arc (reactor daemon), the nginx site config, the jarvis-arc.service systemd unit, and the root crontab. Also fixed a typo ($SIYE$SIZE) that silently broke the backup-size line in the log. Latest test run: 20MB.

DO Server Backup

  • Repo: myronblair/do-server-config
  • Schedule: Weekly, Sunday 4am
  • Launcher: /usr/local/bin/do-server-backup on DO
  • Fixed 2026-07-07: previously covered scripts/systemd/WireGuard/OLS-vhosts/cron/SSH keys//opt/infra — real server configuration — but captured zero actual data: no website files for any of the 7 hosted sites, and only a list of database names (mysql/databases.txt), never the data itself. If the droplet were lost, you'd get the server environment back but lose every site's real content and every business database. Now also archives sites/<domain>/public_html/ for all 7 sites (rsync, .git excluded — each site's own repo already covers code history) and gzipped mysqldump output for every real business DB (epic_epic_db, epic_parkersling, park_slingshot, toms_tjj_db, tomt_ttg_db, workt_track_db — CyberPanel's own internal DB and MySQL system schemas are skipped). Verified live: all 6 DBs dumped with real content, all 7 sites' files present, repo still only 5.4MB after the first full run (318 files, ~48K lines).
  • Restore: 8-phase wizard in restore.sh (predates the 2026-07-07 file/DB additions — the wizard itself hasn't been updated yet to restore sites//databases/, only the original config pieces. Worth revisiting before relying on it for a full restore.)
  • Downloadable zip: as of 2026-07-07, backup.sh also writes /opt/do-server-backup.zip (current working tree minus .git, regenerated every run) — see "Blair HQ Backup Downloads" below.

Proxmox Config Backup

  • Repo: myronblair/proxmox-config
  • Schedule: Weekly, Sunday 3am (both PVE1 and PVE2)
  • Launcher: /usr/local/bin/proxmox-backup on each node
  • Covers: VM .conf files, network, cron, systemd, scripts (host-level config only, by design)
  • VM disks: Verified 2026-07-07 — real nightly vzdump job backup-aa6b1890-23c0 (all VMs, snapshot mode, keep-last=3, 21:00 daily, to SynologyProx) genuinely covers full VM disk data separately. Confirmed via pvesh get /cluster/backup — this is not just documented, it's an active, enabled job. No gap here; config-in-git + data-via-vzdump is a legitimate split, not an oversight.

FusionPBX Backup

  • Repo: myronblair/fusionpbx-config
  • Schedule: Weekly, Sunday 5am
  • Launcher: /usr/local/bin/fusionpbx-backup
  • Covers: PostgreSQL dump (gzip, ~29-60MB) + FreeSWITCH configs + call recordings
  • Verified 2026-07-07: already does a real pg_dump of the actual fusionpbx database (dialplans, SIP gateways, IVR, ring groups, devices, voicemail, users — all real data, not just a name list) plus pg_dumpall --globals-only, plus config files and recordings. No gap here.
  • Restore: 10-phase wizard in restore.sh
  • Known issue (found 2026-07-05, still open): this repo has grown to ~166MB on GitHub / ~196MB on Gitea because the DB dump gets committed directly into git history on every backup run (3 copies in history as of this writing, each ~60MB) rather than being excluded/rotated. Options not yet decided: gitignore the dump going forward, or purge it from history with git filter-repo + force-push (destructive, needs explicit sign-off).

Blair HQ Backup Downloads (web.orbishosting.com, added 2026-07-07)

A new Backup Downloads card on the Blair HQ dashboard (next to the existing Backup Status card) surfaces the actual JARVIS and DO Server backup files for direct download — source location, timestamp, and file size are shown, pulled from /downloads/backup-files-status.json.

  • Pull script: /usr/local/bin/pull-backups.sh on VM110 (NovaCPX), cron 0 5 * * * (daily, safely after both source backups) — SCPs the latest jarvis_backup_*.tar.gz from VM211 (key-based SSH; a new ed25519 keypair was generated on VM110 and added to VM211's authorized_keys for this specifically) and /opt/do-server-backup.zip from the DO server (password auth via sshpass), then writes the status JSON. Log: /home/webacct/logs/pull-backups.log.
  • Security fix that made this safe to build: /home/webacct/public_html/downloads/ had zero access protection — anything dropped there (including the infra reference doc, previously) was fetchable by anyone on the open internet with no login, since web.orbishosting.com is fully public (confirmed reachable from outside, HTTP 200). Added HTTP Basic Auth (myron / Joker1974!!!) scoped to the /downloads/ location in /etc/nginx/sites-enabled/novacpx-webacct.conf, with backup-status.json and backup-files-status.json explicitly exempted (exact-match location blocks ahead of the auth'd prefix block) since the dashboard's own client-side JS fetches those with no credentials to send.
  • Also fixed while auditing this: a daily VM110 cron (0 4 * * * curl ... -o .../INFRASTRUCTURE-REFERENCE.md) had been silently overwriting the infra doc with an empty file every night since the 2026-07-06 security review relocated the real copy off any public path — the cron's source URL no longer existed. Restored a LAN-only static copy at /var/www/jarvis/public_html/admin/downloads/INFRASTRUCTURE-REFERENCE.md on VM211 (safe: VM211 isn't internet-reachable directly, only from the internal LAN) so this automation has something real to pull again. The authenticated docs_download action in the JARVIS admin UI is unaffected either way.

MSP360 Backup Status (Dashboard Integration)

  • Client software: MSP360 (CloudBerry) Backup CLI installed on all 6 hosts — PVE1, JARVIS (211), NovaCPX (110), Jellyfin (33), MediaStack (35), Homebridge (18)
  • Storage target: NAS-MSPBackups destination → Synology NAS CIFS share, mounted at /mnt/nas-backups/MSPBackups
  • Mount reliability: /usr/local/bin/msp360-mount-ensure.sh (cron */15 * * * * on hosts using the NAS mount) — bind-mounts the MSPBackups subdir onto itself since MSP360's pre-flight mountpoint check fails on a subdirectory of a CIFS mount otherwise
  • Collector: /usr/local/bin/backup-status-collect.sh on PVE1 (runs via key-trusted root SSH — PVE1 is the only host with passwordless SSH to all 6 targets; other hosts use password auth via sshpass)
    • Queries each host's plan via cbb plan -l (legacy v1 CLI — outputs State: / Last result: fields directly, unlike cbbV2/cbbCommandLineV2 which needs plan list -b and different parsing)
    • Writes /tmp/backup-status.json, then scps it to root@10.48.200.110:/home/webacct/public_html/downloads/backup-status.json
    • Schedule: daily 0 6 * * * on PVE1 (>> /var/log/backup-status-collect.log)
  • Dashboard card: web.orbishosting.com "BACKUP STATUS" card (index.html) fetches /downloads/backup-status.json client-side (loadBackupStatus()), color-codes dots by result (green=Success, yellow=Warning, red=Fail, cyan=Running, gray=unknown)
  • JSON schema: {"updated": "<ISO8601 UTC>", "hosts": [{"name","ip","state","result"}, ...]}
  • Homebridge (2026-07-04): dropped MSP360 entirely. After extensive troubleshooting (RAM starvation, a bug where its account scanned every other host's shared backup data, missing bind-mount depths, CIFS tuning, a full plan recreation) Homebridge's MSP360 agent kept failing with a false "storage drive not mounted" error at a consistent ~60-75s mark, root cause never conclusively identified (survived every environmental fix, looked like an app-level bug tied to any custom/non-default account path). Since Homebridge (VM 118) was already being backed up successfully every night by the cluster-wide Proxmox vzdump job (backup-aa6b1890-23c0, all VMs, 21:00 daily, keep-last=3, to SynologyProx storage), MSP360 was stopped/disabled on Homebridge (systemctl disable msp360-backup.service msp360-backupWA.service) and removed from the dashboard collector's per-host MSP360 check. The collector now reads Homebridge's status directly from /mnt/pve/SynologyProx/dump/vzdump-qemu-118-*.vma.zst on PVE1 instead of querying an in-guest agent.
  • Known state (2026-07-04): 4/5 remaining MSP360 hosts report Warning, NovaCPX reports Fail — plan-level result, not investigated further; worth checking each host's MSP360 GUI/log for root cause if backups need to be trusted for restore. Homebridge reports Success via Proxmox.

11. SSH QUICK REFERENCE

# DO (main web server)
sshpass -p 'Gonewalk1974!@#' ssh -o StrictHostKeyChecking=no root@165.22.1.228

# FusionPBX (must relay via DO)
sshpass -p 'Gonewalk1974!@#' ssh root@165.22.1.228 \
  'sshpass -p "Joker1974!@#" ssh root@134.209.72.226 "CMD"'

# PVE1 (direct or via DDNS)
sshpass -p 'Joker1974!!!' ssh -o StrictHostKeyChecking=no root@orbisne.fortiddns.com
sshpass -p 'Joker1974!!!' ssh -o StrictHostKeyChecking=no root@10.48.200.90

# PVE2
sshpass -p 'Joker1974!!!' ssh -o StrictHostKeyChecking=no root@10.48.200.91

# MediaStack (via PVE1)
sshpass -p 'Joker1974!!!' ssh root@10.48.200.90 \
  'ssh -i /root/.ssh/id_rsa root@10.48.200.35 "CMD"'

# Jellyfin (direct, password enabled 2026-06-14)
sshpass -p 'Joker1974!!!' ssh -o StrictHostKeyChecking=no root@10.48.200.33

# NovaCPX (direct)
sshpass -p 'Joker1974!!!' ssh -o StrictHostKeyChecking=no root@10.48.200.110

# Ollama / Homebridge / NetworkBackup (myron user, then sudo)
sshpass -p 'Joker1974!' ssh myron@10.48.200.210  # Ollama (NOT .95 - that's a naming leftover, nothing listens there)
sshpass -p 'Joker1974!' ssh myron@10.48.200.18   # Homebridge
sshpass -p 'Joker1974!' ssh myron@10.48.200.99   # NetworkBackup

# Run command inside VM via Proxmox (requires QEMU agent installed; use VMID not IP)
sshpass -p 'Joker1974!!!' ssh root@10.48.200.90 \
  'qm guest exec 106 -- bash -c "export HOME=/root; CMD"'   # Ollama = VMID 106
# Other VMIDs: Jellyfin=112, MediaStack=103, JARVIS=211, NovaCPX=120, HomeAssistant=101, Homebridge=118

Password fallback order: Joker1974!@#Joker1974!!!Joker1974!


12. CRITICAL CREDENTIALS MASTER LIST

SSH / Root Access

System User Password Notes
DO (165.22.1.228) root Gonewalk1974!@# Main web server
FusionPBX (134.209.72.226) root Joker1974!@# Via DO relay
PVE1 (10.48.200.90) root Joker1974!!! Also via DDNS
PVE2 (10.48.200.91) root Joker1974!!!
MediaStack (10.48.200.35) root key only Via PVE1 (/root/.ssh/id_rsa)
Jellyfin (10.48.200.33) root Joker1974!!! Enabled 2026-06-14
NovaCPX (10.48.200.110) root Joker1974!!! Direct SSH works
Ollama / Homebridge / Backup VMs myron Joker1974! Then sudo

Web Panels & Admin

System URL User Password
CyberPanel https://165.22.1.228:8090 myron Joker1974!!!
phpMyAdmin (DO) https://165.22.1.228/phpmyadmin myron Joker1974!!!
Proxmox PVE1 https://orbisne.fortiddns.com:8006 root Joker1974!!!
Proxmox PVE2 https://10.48.200.91:8006 root Joker1974!!!
JARVIS http://jarvis.orbishosting.com myron Joker1974!!!
JARVIS Admin http://jarvis.orbishosting.com/admin myron Joker1974!!!
ChuckCo Time Keeper Admin https://worktracking.orbishosting.com/admin/login.php Joker1974!!!
FusionPBX https://fusion.orbishosting.com admin fY7XP5swgtpbzrYLhkeVYkA4744
Home Assistant http://orbisne.fortiddns.com:8123 myron (HA password)
NovaCPX Admin https://10.48.200.110:8882 admin Admin2026!
Jellyfin http://10.48.200.33:8096 token: 635142c218f2457e813a2de8dec75b05
qBittorrent http://10.48.200.35:8080 admin Joker1974!!!
Sonarr http://10.48.200.35:8989 admin Joker1974!!!
Radarr http://10.48.200.35:7878 admin Joker1974!!!
Prowlarr http://10.48.200.35:9696 admin Joker1974!!!
Synology NAS http://10.48.200.249:5000 nas Joker1974!!!
Parker Slingshot Admin https://parkerslingshotrentals.com/admin admin Parker2026!
TJJ Admin https://tomsjavajive.com/admin admin@tomsjavajive.com OR myronblair@outlook.com Joker1974!!!

Databases

Site DB Name DB User DB Password
JARVIS jarvis_db jarvis_user J4rv1s_Pr0t0c0l_2026!
Tom's Java Jive toms_tjj_db toms_tjj_user +60wlPc+55e@gFq4
Parker Slingshot Rentals park_slingshot park_slingshotuser 4@rxg*8kovxCr7w6
Epic Travel epic_travel_db (see config.php) (see config.php)
Epic/Parker Slingshot epic_parkersling epic_parkersling Joker1974!!!
NovaCPX SQLite: /var/lib/novacpx/panel.db
ChuckCo Time Keeper workt_track_db workt_track_user ZWCNMRP2N5NVPsghmve5aRS9
FusionPBX PostgreSQL fusionpbx pSJaF9mUJqPr4Sj5mwJyRqvCCpc
MySQL root (DO) root b71e5c1a8c7457541b9c1db822de37adfa271926a38b6c20

API Keys

Service Key
GitHub PAT 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 (rotated 2026-07-05, scopes repo+workflow)
JARVIS Agent Registration [rotated 2026-07-07 — stored in api/config.php on VM211, not documented here]
Proxmox API Token root@pam!jarvis=c45b5feb-f9a9-445d-a626-14fbb959f78b
HA Long-lived Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzNmI0N2I1Njk5ZGQ0MTQ2ODMwZWFmYjZiYTQ1MjJkMSIsImlhdCI6MTc4MDIwMzU5NCwiZXhwIjoyMDk1NTYzNTk0fQ.sYRok-jRDlA4lFgWxLQELcEjkJNGQdprk6ZziLwLtXE
Sonarr API b43e04350a594846b4ee95261c29e9e0
Radarr API 53c4268360444feeae5f98c0cc24e0e3
Prowlarr API 9d0ce6c5660743b5bf1c7951efc62252
Jellyfin Admin Token 635142c218f2457e813a2de8dec75b05
Square (Parker) Production EAAAl3FsAu_2ri8kZE_ENEyi2T_C8HXXm5XQFY6Lbnd8SX6FqYp8J_upUeXNYh7v
Square App ID (Parker) sq0idp-YSM7BU9IVyOWSzpeP-0nzQ
Webhook HMAC Secret 4c8805f0285214ff0a0602b5880270b935f36a896946c7f1

SIP / Phone

Extension Name SIP Password
1000 Myron Blair — Desk (10.48.200.2) Xk9mPw3nQv7rLs2t
1001 Tommy Ivy — Desk (10.48.200.43) Tv8xNm4pWq6rZs3k
1002 Myron Blair — WiFi Work (10.48.200.65) yXHaJTwa8rj?$GkrVFQB
1003 Kitchen (10.48.200.83)
1004 Master Bedroom (10.48.200.85)
1010 Parker County Slingshot (voicemail only)
1011 Epic Travel Expeditions (voicemail only)
1012 Tom's Java Jive (voicemail only)

13. GIT & REPOSITORY MANAGEMENT

GitHub (myronblair, 26 private repos as of 2026-07-05) is the permanent/source-of-truth storage. Gitea (gitea.orbishosting.com, hosted on the Synology NAS, login myron / Joker1974!!!) mirrors it one-way — GitHub → Gitea only, via pull-mirrors triggered by POST /api/v1/repos/<owner>/<repo>/mirror-sync. Never push directly to a Gitea remote.

5 repos are Gitea-only by design, never pushed to GitHub (deliberately kept off a third-party cloud service since they hold real credentials): fortigate-config, infra-private, jarvis-secrets, msp360-config, proxmox-secrets.

Local clones live on the admin Windows machine at C:\Users\myron\repos\ (currently chucko, web-dashboard — more cloned there as needed). Periodic maintenance: git gc --aggressive --prune=now to keep loose objects packed.

Per-site deploy pattern (the 7 DO-hosted sites + jarvis + web-dashboard): .git metadata is relocated outside the public webroot (e.g. /home/<site>/git-data with a gitdir: pointer file left in public_html/.git) so .git itself is never web-accessible, while git commands run normally from inside public_html. Most sites deploy via a GitHub webhook → queue file → cron puller (~1 min); chucko (ChuckCo Time Keeper) currently has no auto-deploy hook — pushes are manual (SSH in, git pull/push directly).

Known repo-hygiene issue: fusionpbx-config bloated to ~166-196MB from repeatedly committing a large DB dump straight into history — see Section 10.


14. NETWORK EQUIPMENT & CLIENT DEVICE INVENTORY

Compiled 2026-07-06 from a live ARP scan off PVE1 (~90 hosts), MAC-vendor lookups, and direct confirmation from Myron. Built to support a future VLAN segmentation project — see VLAN-Segmentation-Plan.docx in the home folder for the full plan; this section is the durable factual record to carry forward (e.g. into JARVIS) independent of that plan's status.

14.1 Core Network Equipment

Device Model Role
Firewall FortiGate 60F Primary/active firewall — confirmed the top unit in the rack. A second Fortinet unit is stacked below it; its role is not yet identified — not confirmed as an HA pair.
Primary switch Cisco Catalyst 3560-E Series PoE-48 48-port, full PoE, enterprise-managed — full 802.1Q VLAN/trunk support
Secondary switch FortiSwitch 108F-FPOE 8-port PoE, FortiLink-managed
KVM switch TRENDnet TK-802R Physical console access to rack servers — not networked
WiFi extender 1 TP-Link RE305 — 10.48.200.16 WiFi clients only, no wired devices
WiFi extender 2 TP-Link RE305 — 10.48.200.89 WiFi clients only, no wired devices
WiFi extender 3 TP-Link RE305 — 10.48.200.93 Wired network printers plugged into its Ethernet port; no WiFi clients on this unit
WiFi extender 4 Brand unconfirmed (Shenzhen Xunman-branded/OEM) — MAC fc:22:1c:30:60:14 seen at 10.48.200.100/.64 Wired Pioneer VSX-822 AV receiver plugged into its Ethernet port; no wireless clients of its own — same bridge-mode pattern as extender 3 above
Wireless bridge Good Story Networks WB610H — 10.48.200.80 Links the main house network to the storage shed (a detached building). OEM manufacturer is Shenzhen LiWiFi Technology Co., Ltd (rebranded by Good Story Networks). Plan: once fully deployed, this bridge replaces the need for the RE305 units at .16 and .89 — see the VLAN plan doc for details. VLAN/802.1Q trunk capability not yet confirmed — check before relying on it for segmented WiFi.

Note on consumer extenders and VLANs: budget range extenders like the RE305 (and likely extender 4 above) generally cannot map multiple SSIDs to separate VLANs over a trunk — they repeat one network. This matters if/when wireless VLAN segmentation is implemented; see the VLAN plan doc's "Wireless VLAN Feasibility" section.

14.2 Client & Peripheral Device Inventory (by category)

Printers:

IP Device
10.48.200.76 Epson ET-3750
10.48.200.204 HP LaserJet 500 Color MFP M570dn (wired to the RE305 at .93)
10.48.200.205 HP LaserJet M1536dnf MFP (wired to the RE305 at .93)
10.48.200.201, .202 Unidentified — likely more printers/peripherals on the same RE305 port, given the pattern above (not yet confirmed)

Storage:

IP Device
10.48.200.249 Synology NAS
10.48.200.41 WD My Cloud — a second NAS alongside the Synology

AV / Entertainment:

IP Device
10.48.200.42, .72 Vizio smart TVs
10.48.200.100 Pioneer VSX-822 AV Receiver — confirmed 2026-07-06, wired into a 4th WiFi range extender/AP (Shenzhen Xunman-branded/OEM, MAC fc:22:1c:30:60:14, no wireless clients of its own). This explains the earlier MAC discrepancy cleanly: the extender reports its own MAC in ARP for the wired Pioneer behind it, same bridge-mode pattern seen with the RE305 at .93 and its wired printers — the Pioneer's own labeled MAC (74:5E:1C:0E:7C:0B, genuine Pioneer Corporation OUI) simply never appears on the wire. 10.48.200.64 shares this same extender's MAC — likely the same device (Pioneer or the extender itself) at a prior IP, not a separate device.

Smart home / IoT:

IP Device
10.48.200.38 Samsung SmartThings hub (MAC vendor: Physical Graph Corporation, the original SmartThings company)
10.48.200.250, .251 Goalake Smart Switch 1 and 2
10.48.200.5, .7, .8, .9, .36, .61, .74 Generic ESP32/ESP8266-based smart plugs/sensors (Espressif chipset)
10.48.200.14, .60 Tuya Smart plugs/switches
10.48.200.34 Bouffalo Lab-chipset IoT device
10.48.200.116 FN-LINK-chipset IoT device
10.48.200.6, .10, .15, .23, .24, .27, .28, .30, .31, .32, .37, .62, .82, .86, .87, .105 TP-Link Tapo smart devices (16 total)
10.48.200.17, .39, .40, .46, .53, .68, .71, .106, .115, .118 Amazon devices (Echo/Fire TV/Kindle)

Security cameras:

IP Device
10.48.200.57, .78, .94, .95, .101, .103, .104 Reolink cameras + NVR (7 addresses total — one of these is the NVR itself, not confirmed which). Cameras are PoE-connected directly to the Catalyst 3560-E, not cabled to the NVR; the NVR pulls streams over the network like any other client. Feeds are viewed both locally on the LAN and remotely via the Reolink app.
10.48.200.21, .22 Ring doorbell/camera — also viewed both locally and via the Ring app remotely

VoIP (Yealink) — see Section 8 for extension details: .2, .3, .43, .65, .83, .85

Personal computers / hobby devices:

IP Device
10.48.200.54 Apple device (iPhone/iPad/Mac)
10.48.200.66 Dell PC
10.48.200.52 Intel-NIC PC
10.48.200.45 Microsoft device (Surface or Xbox — not yet confirmed which)
10.48.200.50 Raspberry Pi 5 — hobby/tinkering only, no production workload

Still unidentified as of 2026-07-06 (MAC vendor lookup only, no direct confirmation yet):

IP Vendor signature Notes
10.48.200.13 Murata Manufacturing Embedded WiFi module — device unknown
10.48.200.59 Guangzhou Shiyuan Electronic Often AV/display equipment — device unknown
10.48.200.119 Macherey-Nagel GmbH & Co. KG A lab-equipment brand (chromatography/filtration) — unusual on a home network, device unknown
10.48.200.77 Liteon Technology Could be a PC PSU with network mgmt, or a peripheral — device unknown
10.48.200.201, .202 (shares MAC with the RE305 at .93 and the confirmed printers at .204/.205) Likely more printers/peripherals, not confirmed

Minor known artifact, low priority: MAC 7a:e1:7e:44:08:29 is shared across 10.48.200.93 (RE305), .204/.205 (confirmed printers, benign RE305 bridge-mode behavior), and .251 (Goalake Smart Switch 2 — does NOT fit the "wired to the RE305" explanation, so this one specific overlap looks like a genuine duplicate/cloned MAC, common in ultra-budget IoT hardware). Worth a quick check of the Goalake switch's real MAC via its own admin UI at some point; not urgent.


This document contains sensitive credentials. Store securely and do not share.