Files
itpp-infrastructure/audit/phase-one/findings/sys-b.md
T
root f5175f1ce0 Sync docs, audit artifacts, project notes, and VerdictTank proposal docs
- audit/phase-one + phase-two: security audit briefs, findings, credential-rotation plan, Docker-USER hardening scripts, rollback refs
- disaster-recovery/restore-test-log.md + backup-dr-audit-2026-08-10.md
- clients/ (modelortho SEO audit, ai-biz-dev competitive landscape), notes/ (tiktok strategy)
- projects/: front-desk-voice-agent, seo-visibility-checker product plan, hotnow-savannah HTML, resend-transactional-email, backup-dashboard-enhancements, code-review-graph, seo-ci-architecture
- proposals/verdicttank/: architecture v4.0, methodology, judge-pool review, consolidation reasoning, cross-check review
- docs/super-search/firecrawl-provider-strategy.md
- updates: CHANGELOG, model-chain, projects-master-readme, intelsight.io
- .gitignore: exclude nested standalone repos (seo-tool, venturebuilt)
2026-08-26 02:27:28 -04:00

24 KiB

Sys-B Findings - ITPP Phase One Infrastructure Audit (Read-Only)

Auditor: Sys-B (sysadmin inventory auditor) Scope: app2 (152.53.39.202), app3 (152.53.241.111), app1-bu (5.161.225.131), wphost02 (5.161.62.38) Date: 2026-08-13 Method: Read-only SSH (ssh -i /root/.ssh/itpp-infra, BatchMode, ConnectTimeout=10). No mutation performed. All remediation is deferred to Phase Two.


1. Host Reachability

Host IP Role Status
app2 152.53.39.202 Docker app host REACHABLE
app3 152.53.241.111 CloudPanel web host REACHABLE
app1-bu 5.161.225.131 Warm standby (Core failover) REACHABLE
wphost02 5.161.62.38 Legacy WordPress / RunCloud REACHABLE (NOT decommissioned)

No access limitations. All four hosts answered over SSH with the shared key.


2. app2 (152.53.39.202) - Docker Application Host

OS: Debian (netcup), up 3+ weeks. Docker host running ~40 containers across 9 compose projects plus two orphan containers.

2.1 Per-Service Inventory

Service Image / Version Purpose Ports Restart Health Notes
support-api support-api:latest (custom build, 3d) Internal support API 0.0.0.0:6880 unless-stopped healthy Python healthcheck /health
bookstack lscr.io/linuxserver/bookstack:latest Docs (support.itpropartner.com) 0.0.0.0:6875->80 - up APP_URL set; SMTP via mail.germainebrown.com:2525
bookstack-db lscr.io/linuxserver/mariadb:latest Bookstack DB 3306 (internal) - healthy -
happy_rosalind lscr.io/linuxserver/bookstack:latest ORPHANED 2nd Bookstack (no compose project, no host port) 80/443 (internal only) - up Auto-generated name; no external binding; apparent leftover
docker-ragflow-cpu-1 infiniflow/ragflow:v0.26.4 RAGFlow AI platform 9380-9384, 9392->80, 9393->443 - up -
docker-mysql-1 mysql:8.0.39 (2yr old) RAGFlow MySQL 127.0.0.1:3306 - healthy 2-year-old image tag
docker-minio-1 pgsty/minio:RELEASE.2026-03-25 RAGFlow object store 127.0.0.1:9000, 0.0.0.0:9001 - healthy -
docker-redis-1 valkey/valkey:8 RAGFlow cache 127.0.0.1:6379 - healthy -
docker-infinity-1 infiniflow/infinity:v0.7.0 RAGFlow vector DB 0.0.0.0:23817/23820, 127.0.0.1:5432 - healthy 23817/23820 exposed publicly
technitium technitium/dns-server:latest Authoritative DNS server 0.0.0.0:53 tcp/udp, 127.0.0.1:5380 - healthy Public recursive/authoritative DNS
dawarich_app freikin/dawarich:latest Location tracking (Dawarich) 127.0.0.1:3002 - healthy -
dawarich_sidekiq freikin/dawarich:latest Dawarich background jobs 3000 (internal) - healthy -
dawarich_db postgis/postgis:17-3.5-alpine Dawarich DB 5432 (internal) - healthy -
dawarich_redis redis:7.4-alpine Dawarich cache 6379 (internal) - healthy -
traccar traccar/traccar:latest GPS fleet tracking 0.0.0.0:5000-5150 (tcp+udp), 0.0.0.0:8082 - healthy 151 device ports publicly exposed
gitea gitea/gitea:latest Git server (git hosting) 0.0.0.0:3022->22, 127.0.0.1:3001 - up SSH port public
unifi-controller jacobalberty/unifi:latest (8mo) UniFi controller 0.0.0.0:8080/8443/8843/8880, 3478/10001/udp - healthy linuxserver/unifi image also present (5wk) - migration drift
unms-nginx ubnt/unms-nginx:latest UNMS reverse proxy 0.0.0.0:81/8089/8444 - up -
unms-api ubnt/unms:latest UNMS API (EOL product) internal - healthy UNMS discontinued by Ubiquiti 2021
unms-device-ws-1..11 ubnt/unms:latest UNMS device websockets (11 replicas) internal - healthy -
unms-netflow ubnt/unms-netflow:latest UNMS netflow collector 0.0.0.0:2055/udp - up -
unms-postgres ubnt/unms-postgres:latest UNMS DB 5432 (internal) - up -
unms-siridb ubnt/unms-siridb:latest UNMS time-series DB 9000/9010 (internal) - healthy -
unms-rabbitmq rabbitmq:3.7.28-alpine (5yr) UNMS message broker 4369/5671-5672/25672 - up 5-year-old image, EOL, known CVEs
unms-fluentd ubnt/unms-fluentd:latest UNMS logging 5140, 127.0.0.1:24224 - up -
ucrm ubnt/unms-crm:4.5.33 UCRM billing 80-81, 443, 9000, 2055/udp - up -
hudu-app-1 hududocker/hudu:latest Hudu IT documentation 127.0.0.1:3000 - up -
hudu-worker-1 hududocker/hudu:latest Hudu sidekiq worker 3000 (internal) - up restarted 15h ago
hudu-db-1 postgres:16.2 (2yr) Hudu DB 5432 (internal) - up 2-year-old postgres tag
hudu-redis-1 redis:latest Hudu cache 6379 (internal) - up -

Old/unused images present: ubnt/ucrm-conntrack:latest (5yr), mongo:7.0 (6wk, no container), linuxserver/unifi-network-application:latest (5wk, not the running unifi image), caddy:latest, alpine:latest.

2.2 Dependencies (text map)

  • RAGFlow depends on: mysql-1, minio-1, redis-1, infinity-1. Downstream: end users of the RAGFlow UI.
  • Bookstack depends on: bookstack-db (MariaDB). Downstream: support docs users.
  • Dawarich depends on: dawarich_db (postgis), dawarich_redis, sidekiq worker.
  • UNMS stack depends on: unms-postgres, unms-siridb, unms-rabbitmq, unms-fluentd, unms-nginx. UCRM shares the UNMS stack.
  • Hudu depends on: hudu-db-1 (postgres), hudu-redis-1, hudu-worker-1.
  • Traccar, Gitea, Technitium, support-api, UniFi: self-contained (single container each; Traccar/Gitea embed storage).
  • All containers depend on Docker daemon + host disk. Technitium depends on external DNS delegations.

2.3 Config Files / Drift

  • /opt/support-api/docker-compose.yml - single service, port 6880, Python /health healthcheck.
  • /opt/bookstack/docker-compose.yml - APP_URL=https://support.itpropartner.com, SMTP mail.germainebrown.com:2525, DB/Mail passwords and APP_KEY present in compose (redacted in this report).
  • /opt/gitea/docker-compose.yml, /root/docker/hudu/docker-compose.yml, /root/docker/traccar/docker-compose.yml, /root/docker/dawarich/docker-compose.yml - env-style secrets inline (redacted).
  • /home/unms/app/ - UNMS install (docker-compose, unms.conf, install-full.sh, update.sh).
  • /root/.aws/ - credentials + backups present (redacted). /root/.docker/ - token seed files.
  • Drift: orphan happy_rosalind Bookstack container (no compose project, no host port). Two UniFi images (jacobalberty running vs linuxserver pulled) suggest an in-flight migration.

2.4 Cron Jobs

Schedule User Command Purpose Failure mode
30 2 * * * root /root/backup.sh 2>&1 | logger -t app2-backup Nightly backup to Wasabi S3 See 2.5

No other system or user cron jobs beyond /etc/cron.d defaults.

2.5 Backup Status (CRITICAL GAP)

/root/backup.sh (Wasabi S3 target hermes-vps-backups, s3.us-east-1.wasabisys.com) calls per-service backup scripts. Live evidence from 2026-08-13 02:30 run:

  • Completed OK: Traccar (DB+config), Dawarich, Technitium DNS, RAGFlow (MySQL dump + Infinity DB + Minio objects).
  • Logged "Backing up..." but NO completion line: Gitea, Hudu, UNMS, UniFi.
  • Root cause: /root/backup.sh references gitea-backup.sh, hudu-backup.sh, unms-backup-sync.sh, unifi-backup-sync.sh which do not exist under /root/.hermes/scripts/. Those sections are silently skipped.
  • bookstack-backup.sh, support-api-backup.sh, ragflow-backup.sh exist but Bookstack and support-api are not scheduled in cron.

Impact: Gitea, Hudu, UNMS, UniFi, Bookstack, support-api have effectively NO working backup. No restore test is documented for any app2 service.

2.6 app2 Critical-Service RTO/RPO

Service RPO RTO (est.) Restore tested?
Hudu none (backup broken) 4-8h No
UNMS none (backup broken, product EOL) 4-8h No
UniFi none (backup broken) 2-4h No
Gitea none (backup broken) 2-4h No
Bookstack / support-api none (script not scheduled) 2h No
Traccar 24h 2-4h No
Dawarich 24h 2h No
Technitium DNS 24h 1h No
RAGFlow 24h 2-4h No

3. app3 (152.53.241.111) - CloudPanel Web Host

OS: Debian 13 (trixie), up 34d. 31 GiB RAM / 12 CPU. / 1TB (80G used, 9%). Runs CloudPanel + nginx + Percona MySQL 8.4 + 10 PHP-FPM versions + Docker (Hexclave Stack Auth, Buzz relay) + several systemd app services.

3.1 Systemd Services

Service Version Purpose Notes
nginx 1.30.4 Primary web server 80/443
clp-nginx - CloudPanel control panel 8443
clp-agent / clp-php-fpm - CloudPanel agent -
mysql (Percona Server) 8.4.10 Shared DB for all sites 127.0.0.1:3306 (datadir /home/mysql)
php7.1-fpm ... php8.5-fpm 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5 Per-site PHP pools 7.1-8.0 are EOL
varnishd 7.5.0 HTTP cache 6081
memcached - Object cache 11211
redis - Object cache 6379
proftpd - FTP server 21 (plaintext FTP)
postfix - Outbound mail 25
gitea.modelortho.service Gitea Git for git.modelortho.com 3001
msp-forms.service FastAPI Shared form handler (forms.itpropartner.com) -
docs-auth-validator.service - Stack Auth JWT validator for docs.itpropartner.com -
backup-restore.service - Backup-restore web UI (my.itpropartner.com/backup) -
percona-telemetry-agent - Percona telemetry -
fail2ban, cron, chrony, containerd, docker - platform -

3.2 Docker Services

Container Image Purpose Notes
hexclave-* (server, postgres, clickhouse, cron) stackauth/server:latest, postgres 16/17-alpine, clickhouse 25.10 Hexclave Stack Auth No backup coverage found
buzz-prod-* (relay, postgres, redis, minio) ghcr.io/block/buzz:main Buzz relay (Block open-source) Production relay; no backup coverage found

3.3 Sites Hosted (CloudPanel)

WordPress (10 wp-config.php instances across 9 users): apextrackexperience.com, boxpilotlogistics.com, debtrecoveryexperts.com (x2 - under BOTH debtreecoveryexperts and drecovery users), iamgmb.com, intelsight.io, mainwp.itpropartner.com, vigilanttac.com, voipsimplicity.com (+ www subdomain).

Static / non-WordPress: docs, forms, mockups, proposals, support, my.verdicttank.com, verdicttank.com, modelortho.com (+www), transitpin.com, my.transitpin.com, panel, my.voipsimplicity.com, timapta.org, katiewattsdesign.com, buzz.iamgmb.com, hexclave-api/dash, gmb, auth2 + auth2-api.

3.4 Cron Jobs

Schedule User Command Purpose
0 3 * * * root /root/backup.sh | logger -t app3-backup Nightly full backup
0 1,13 * * * root /opt/backup-restore/snapshot.sh Twice-daily WP snapshots
30 4 * * * root /root/gitea-modelortho-backup.sh | logger -t gitea-backup Gitea (modelortho) backup
15 3 * * * clp clpctl db:backup ... --retentionPeriod=7 CloudPanel DB backup
15 4 * * * clp /home/clp/scripts/create_backup.sh CloudPanel backup
5-25 * * * * clp certbot/letsencrypt renewals, vhost import, cloudflare IPs platform

3.5 Backup Status

/root/backup.sh (2026-08-13 03:00 run, all OK) covers: CloudPanel SQLite DB, MSP Forms, all 10 MySQL DBs, all 10 WordPress file trees, static sites, nginx/configs. Destination Wasabi S3 app3/. snapshot.sh adds twice-daily local snapshots of WP sites (30-day retention). Gitea (modelortho) has its own nightly S3 backup.

Backup gap: the Docker services (Hexclave Stack Auth, Buzz relay) and TransitPin are not referenced by any backup script. Their postgres/clickhouse/minio data has no scheduled backup.

3.6 Config / Drift

  • Plaintext MySQL root password hardcoded in BOTH /root/backup.sh (root-only) and /opt/backup-restore/snapshot.sh (775 world-readable). Same password in both.
  • Duplicate WordPress site (debtrecoveryexperts.com) under two different system users - ambiguous ownership.
  • /home/.swap = 2GB swap file on web data volume.
  • Five EOL PHP-FPM runtimes (7.1, 7.2, 7.3, 7.4, 8.0) still running.

3.7 app3 Critical-Service RTO/RPO

Service RPO RTO (est.) Restore tested?
CloudPanel + all WP/static sites 12-24h (daily + 2x snapshots) 4-8h No
MySQL (10 DBs) 24h 4h No
Gitea (modelortho) 24h 2h No
Hexclave Stack Auth none (no backup) 4h No
Buzz relay none (no backup) 4h No
TransitPin none (no backup) 4h No

4. app1-bu (5.161.225.131) - Warm Standby for Core

OS: Ubuntu 24.04.4, up 28d. 3.7 GiB RAM / 3 CPU / 75G disk (37% used). No Docker. Runs only SSH, tailscale, fail2ban, cron + Hermes agent (v0.18.2). This is the warm standby for the live Core Hermes box.

4.1 Services

Service Purpose Notes
ssh Admin access 22
tailscaled Private mesh access Tailscale
fail2ban SSH brute-force protection -
cron Schedules watchdog + sync -
hermes (binary) Hermes Agent v0.18.2 installed NOT currently running (dormant)

4.2 Cron Jobs (the failover mechanism)

Schedule Command Purpose Last run
*/5 * * * * /root/.hermes/scripts/hermes-standby-watchdog.sh Ping live Core; failover if down ~3.5 min Active (log entries hourly through 2026-08-13)
*/10 * * * * /root/.hermes/scripts/hermes-standby-sync.sh S3 sync of config/skills/plugins/cron/references Active (last sync 2026-08-13T14:10Z)

Failover behavior (verified, NOT triggered): the watchdog pings LIVE_HOST=152.53.192.33; if it fails 4 consecutive 60s cycles, it sends Telegram+email alerts, runs aws s3 sync s3://hermes-vps-backups/live/ -> ~/.hermes/, then hermes gateway start. A standalone "failover" cron does not exist; failover is embedded in the watchdog script (runs every 5 min). The sync script correctly skips sync when the live host is unreachable.

4.3 Standby Readiness Assessment (NOT fully ready)

  • Config-level state is fresh: config.yaml (2026-08-12), .env (2026-08-12), skills/ (2026-08-12), cron output (2026-08-13). Synced every 10 min from S3 live/.
  • Data-level state is STALE: state.db (2.1GB, Jul 15), memory_store.db (Jul 15), sessions/ (Jul 15), memories/ (Jul 15). The sync script deliberately excludes these (DIRS="skills profiles plugins cron references", files config.yaml .env .bashrc only).
  • Consequence: on failover, Hermes would start with ~4-week-old session, memory, and state DB. This is a config-level warm standby, NOT a true data-level warm standby.

4.4 Config / Drift (secrets)

  • /root/.hermes/.env (mode 644, world-readable) holds ~20 plaintext secrets: Cloudflare API token, Netcup API key + customer password, SyncroMSP token, Telegram bot token, root passwords for app1/app2/app3 (SERVER_152_53_*.PASS), RingLogix creds, and API keys for OpenAI/Perplexity/Groq/xAI/Mistral/Fireworks/Google/Cohere.
  • /root/.hermes/migration-creds.txt (644), /root/.hermes/scripts/.hetzner_token (644), /root/.hermes/scripts/.netcup_api_key (644) - further plaintext credentials.
  • hermes-standby-watchdog.sh (mode 755, world-readable) contains a plaintext email password and Telegram bot token inline.
  • /root/.hermes/state.db.corrupted (1.8GB, Jul 9) and state.db-wal lingering.

5. wphost02 (5.161.62.38) - Legacy WordPress / RunCloud (STILL LIVE)

OS: Ubuntu 24.04.4, up 34d. 3.7 GiB RAM / 3 CPU / 75G disk (82% full - 59G used). RunCloud-managed LEMP stack. NOT decommissioned.

5.1 Services

Service Version Purpose Ports
nginx-rc - RunCloud web server 80/443
apache2-rc - RunCloud apache (secondary) 127.0.0.1:81
mariadb 11.1.6 Shared DB 127.0.0.1:3306
php81rc-fpm ... php85rc-fpm 8.1-8.5 Per-site PHP pools -
runcloud-agent - RunCloud remote mgmt agent *:34210 (rcsa service)
node_exporter - Prometheus exporter 0.0.0.0:9100
postfix - Mail 25
fail2ban, firewalld, supervisor - platform -

firewalld active zone runcloud allows only 22, 80, 443 + rcsa (RunCloud agent) inbound.

5.2 WordPress Sites (8, still active)

Site DB Files (Aug activity)
apextrackexperience apextrackexperience_1781549652 active (files modified Aug)
boxpilotlogistics boxpilotlogistics_1770339547 active
debtrecoveryexperts debtrecoveryexperts_1778934554 active
iAmGMB iAmGMB_1764020288 no changes since Jul 1
katiewattsdesign katiewattsdesign_1735425014 active
MainWP mainWP_1717713767 active
vigilanttac vigilanttac_1728911691 active
voipsimplicity voipsimplicity_1732250845 active (13k files modified)

All eight sites ALSO exist on app3 (CloudPanel). Both hosts appear live and actively modified -> split-brain migration state.

5.3 Cron Jobs

Schedule Command Purpose
*/5 * * * * /root/apex-mail-watchdog-daemon.sh mail watchdog
30 2 * * * /root/db-dump.sh | logger -t db-dump nightly DB dump

5.4 Backup Status (CRITICAL GAP)

  • /root/db-dump.sh (scheduled daily 02:30) dumps only 2 of 8 DBs (apextrackexperience, boxpilotlogistics) to local /root/db-backups/ with 7-day retention. No offsite copy.
  • /root/backup.sh (full sites + all DBs + RunCloud config -> Wasabi S3) exists but is NOT scheduled in any crontab (grep across /etc/cron* and /var/spool/cron returned nothing).
  • Net effect: 6 of 8 WordPress DBs and ALL site file trees have no running backup. 2 of 8 DBs have local-only 7-day backups. No restore test documented.

5.5 wphost02 Critical-Service RTO/RPO

Service RPO RTO (est.) Restore tested?
apextrackexperience / boxpilotlogistics DBs 24h (local only, 7d retention) 4-8h No
Other 6 WP DBs + all site files none (no scheduled backup) 4-8h No

6. Severity-Rated Findings

CRITICAL

  • C1 - app2: Four production services have silently failing backups. /root/backup.sh references gitea-backup.sh, hudu-backup.sh, unms-backup-sync.sh, unifi-backup-sync.sh which do not exist. The 2026-08-13 02:30 log shows "Backing up..." for these with no completion. Gitea, Hudu, UNMS, and UniFi have no effective backup; a loss would be unrecoverable. (Evidence: journalctl app2-backup + missing files under /root/.hermes/scripts/.)
  • C2 - app1-bu: World-readable secrets file. /root/.hermes/.env (mode 644) contains ~20 plaintext credentials including root passwords for app1/app2/app3, Telegram bot token, Cloudflare/Netcup/SyncroMSP tokens, and eight AI-provider API keys. Any local user or compromised process can read the entire secret estate. (Evidence: ls -la + key names.)
  • C3 - app3: MySQL root password hardcoded in plaintext in backup scripts. /opt/backup-restore/snapshot.sh is mode 775 (world-readable) and contains MYSQL_PASS='[REDACTED]'; the same password is in /root/backup.sh. Credential leakage plus shared superuser credential across all app3 databases. (Evidence: script contents.)
  • C4 - wphost02: Effective backup coverage is ~25% and local-only. Scheduled db-dump.sh backs up 2 of 8 DBs to local disk (7-day retention); the full offsite S3 script /root/backup.sh is not in cron. 6 WordPress DBs and all site files have no running backup on a host that is still live. (Evidence: crontab + db-dump.sh + grep backup.sh /etc/cron* /var/spool/cron empty.)
  • C5 - app1-bu: Warm standby is not data-ready. Sync covers only config/skills/plugins/cron/references; state.db, memory_store.db, and sessions/ are stale at Jul 15 (~4 weeks). A failover today would restore Hermes without the last month of session, memory, and state. (Evidence: stat mtimes + sync script DIRS/excludes.)

HIGH

  • H1 - app1-bu: Failover watchdog targets the wrong IP. Watchdog pings 152.53.192.33 (labeled "Core/App1 netcup"), but .env and the audit brief reference Core/app1 at 152.53.36.131. If the live Core moves/differs, the standby will fail over to nothing or never detect an outage. (Evidence: watchdog script LIVE_HOST vs .env SERVER_152_53_36_131_PASS.)
  • H2 - app1-bu: Plaintext email password + Telegram token in watchdog script (mode 755, world-readable). (Evidence: script contents.)
  • H3 - app2: Very old images in production. rabbitmq:3.7.28-alpine (5 years, EOL with known CVEs), ubnt/ucrm-conntrack:latest (5 years), mysql:8.0.39 (2 years), postgres:16.2 (2 years), jacobalberty/unifi:latest (8 months). (Evidence: docker images.)
  • H4 - app2: UNMS is end-of-life software. Ubiquiti discontinued UNMS in 2021 (successor UISP). The entire ubnt/unms stack (13+ containers) is EOL and should be migrated. (Evidence: image set + product lifecycle.)
  • H5 - wphost02: Legacy host not decommissioned; split-brain with app3. All 8 WordPress sites exist and are actively modified on BOTH wphost02 (RunCloud) and app3 (CloudPanel). No cutover or redirection evident; ambiguous source of truth for client content. (Evidence: identical site lists + Aug file mtimes on wphost02.)
  • H6 - app3: Dockerized production services have no backup. Hexclave Stack Auth, Buzz relay, and TransitPin (postgres/clickhouse/minio data) are absent from every backup script. (Evidence: grep -ril 'buzz|hexclave|transitpin' /root/*.sh /opt/backup-restore/ -> only DB content hits.)
  • H7 - app3: Five EOL PHP-FPM runtimes running. PHP 7.1, 7.2, 7.3, 7.4, 8.0 are all end-of-life and unpatched. (Evidence: systemctl list-units php*-fpm.)

MEDIUM

  • M1 - app2: Orphaned container happy_rosalind (2nd Bookstack, no compose project, no host port binding) running with no apparent purpose.
  • M2 - app2: Large public attack surface. 40 containers with many 0.0.0.0-bound ports: 53 (DNS), 5000-5150 (Traccar device range), UniFi 8080/8443/8843/8880, UNMS 81/8089/8444, netflow 2055, infinity 23817/23820, support-api 6880, bookstack 6875.
  • M3 - app2: Bookstack and support-api backup scripts exist but are not scheduled. Effective RPO = none despite a written script.
  • M4 - app3: Duplicate WordPress install debtrecoveryexperts.com under two users (debtreecoveryexperts and drecovery).
  • M5 - app3: Single-host SPOF. ~25 client sites + shared MySQL + CloudPanel all on one VPS with no HA.
  • M6 - wphost02: node_exporter bound to 0.0.0.0:9100 and postfix on :25 on a legacy host.

LOW

  • L1 - wphost02: Disk 82% full (59G/75G).
  • L2 - app1-bu: state.db.corrupted (1.8GB) and stale state DBs linger consuming disk.
  • L3 - app3: 2GB swap file /home/.swap on the web data volume with 429MiB in use.
  • L4 - app3/wphost02: RunCloud agent (rcsa) port exposed on a host slated for decommission - remote management surface retained.

7. Cross-Host Observations

  • No restore test is documented for any service on any host. Backups are write-only everywhere a backup exists.
  • Backup destinations are Wasabi S3 (hermes-vps-backups) for app2, app3, and app1-bu sync; wphost02's working backup is local-only.
  • Single points of failure are pervasive: each application is on a single VPS; only Core (Hermes) has a standby, and that standby is config-level only.
  • Credentials are routinely stored in plaintext world-readable files on app1-bu and app3 (and inline in compose files on app2).