# Service Monitoring Patterns & Operations Notes (Jul 8 2026)

Collection of monitoring cron patterns used on Core for external services.
Each pattern is a no_agent cron that checks health and alerts on failure.

## Apex Mail Watchdog

**Purpose:** Monitor WP Mail SMTP email delivery on a remote WordPress site (wphost02).
**Every:** 5 min
**Script:** `/root/.hermes/scripts/apex-mail-watchdog.sh`
**Deliver:** origin (only messages on failure)

### Actions:
1. SSH to wphost02 via itpp-infra key
2. Send test email via SMTP (c1113726.sgvps.net:2525)
3. Check wp_wpmailsmtp_debug_events table for recent failures
4. Silent on success (no output = no message), only alerts on failure

### Common SMTP failure signs:
- WP Mail SMTP password PHP serialization length mismatch (`s:72` vs actual `s:13`)
- Sender address containing comma-separated emails (invalid `From:` header)
- SMTP server rejecting `MAIL FROM` for unowned domain

### Fix pattern:
1. Check `wp_wpmailsmtp_debug_events` for most recent error content
2. Test SMTP directly via Python: `smtplib.SMTP(host, 2525).starttls()`
3. If authentication fails: the PHP serialization of the password in `wp_options` is likely corrupted
4. SQL to fix: `UPDATE wp_options SET option_value = REPLACE(option_value, 's:72:\\\"pass\\\"', 's:13:\\\"pass\\\"') WHERE option_name = 'wp_mail_smtp'`

### Known failure mode (Jul 8 2026):
WP Mail SMTP password was stored with wrong PHP serialization length (`s:72` for 13-char password `apex.track!!`). WPForms `sender_address` had comma-separated emails (invalid `From:` header). Both fixed via SQL.

## Ops Data Collector

**Purpose:** Collect comprehensive infrastructure status data for the ops dashboard at ops.itpropartner.com
**Every:** 5 min
**Script:** `/root/.hermes/scripts/ops-data-collector.py`
**Deliver:** local (writes JSON file only)

### Data sources:
- Hermes cron jobs (from /root/.hermes/cron/jobs.json)
- Systemd service status via systemctl
- S3 backup timestamps via AWS CLI (wasabisys endpoint)
- API health checks (admin-ai, Caddy, ports, Cloudflare)
- Hetzner server list via API
- netcup server info via SCP API
- Local disk/memory usage
- Service versions

### JSON output:
/var/www/ops/data/ops-status.json -- consumed by the ops dashboard HTML page.

### Build pattern (Jul 8 2026):
- Collector: self-contained Python stdlib script, no external deps
- Dashboard: single HTML page, no CDN/frameworks, dark theme (#0f172a / #1e293b), mobile-first
- Site: Caddy reverse proxy at ops.itpropartner.com with HTTP-01 ACME challenge
- DNS for itpropartner.com is at SiteGround (not Cloudflare), so Caddy handles TLS directly
- Data/ directory gets CORS headers for cross-origin access

## Home Router Watchdog

**Purpose:** Verify home router is online.
**Every:** 5 min
**Script:** `/root/.hermes/scripts/home-router-watchdog.sh`
**Deliver:** origin

### Router Backup Pipeline
**Purpose:** Backup MikroTik router configs to S3.
**Every:** Daily at 6 AM
**Script:** run-wisp-backup.sh (referenced from /root/.hermes/cron/jobs.json)

### VPN dependencies for L2TP/IPsec router backup:
- xl2tpd package
- strongSwan-starter package
- IP in config must match the router's SSH bind address (often WireGuard tunnel IP, not LAN IP)

### Failure mode (Jul 8 2026):
Cron was using old home-router-backup.sh instead of proper run-wisp-backup.sh. Router SSH export produced stuck .in_progress files blocking SCP. Fix: switched cron, cleaned files, installed missing paramiko/xl2tpd/strongSwan.

## Warm Standby Sync

**Purpose:** Keep app1-bu's local state fresh for rapid failover.
**Every:** 10 min
**Script:** `/root/.hermes/scripts/hermes-standby-sync.sh` on app1-bu
**Triggered by:** System cron on app1-bu, not Hermes cron.

## Netcup SCP API Notes (Discovered Jul 8 2026)

The REST API lives at `https://www.servercontrolpanel.de/scp-core/api/v1/`. Auth is OIDC password grant via Keycloak at `https://www.servercontrolpanel.de/realms/scp/protocol/openid-connect/token` using `client_id=scp` with username/password. The CCP API key from Master Data does NOT work with the SCP API.

Credentials stored in /root/.hermes/.env as NETCUP_CUSTOMER, NETCUP_PASSWORD, NETCUP_API_KEY.

## Infrastructure Consolidation Strategy (Jul 8 2026)

Hetzner new-server pricing is significantly inflated. API returns CPX11 at ~$17.49/mo vs historical ~$4. netcup root servers (RS 1000 at 4C/8G/256GB for ~$12.24/mo) offer better value. Strategy: consolidate Hetzner workloads onto netcup rather than provisioning new Hetzner servers.

## Domain/Portal Separation Rule (Established Jul 8 2026)

Service-related operational tools must be hosted on the provider's own domain, NOT a customer's/industry domain. Two-portal architecture:
- **ops.itpropartner.com** -- internal admin dashboard, infra monitoring, scripts, status pages (Cloudflare Access SSO protected)
- **portal.itpropartner.com** -- customer-facing portal
- **Never** put IT Pro Partner operational tools on customer/themed subdomains (e.g. debtrecoveryexperts.com)

This is a hard architectural rule. When deciding where a new dashboard, page, or tool lives, the default answer is ops.itpropartner.com unless the user explicitly says otherwise.

## Log Paths

| Watchdog | Log |
|----------|-----|
| Apex mail | /var/log/apex-mail-watchdog.log |
| Home router | /var/log/hermes-standby-watchdog.log |
| Standby sync | /var/log/hermes-standby-sync.log |
| Ops collector | /var/log/ops-collector.log |
