Infrastructure
VPS Services Overview
The VPS at 31.97.132.219 (Hostinger, Ubuntu 24.04.3, 193GB disk, 34% used as of April 15, 2026) is the workhorse for everything that needs to be online 24/7. Laptops come and go, the VPS never sleeps. Everything that answers a webhook, replies to a customer, or fires on a cron schedule runs here. See Machines for the machine registry and Network Diagram for how this plugs into the rest of the fleet.
What the VPS Is For
Section titled “What the VPS Is For”Three jobs:
- Serve customer-facing apps that must stay online (CRMs, booking engines, upload pipelines).
- Run the automation layer (cron jobs that scan CRMs, send follow-ups, generate reports, email digests).
- Host the data plane (two self-hosted Supabase instances and an n8n workflow engine).
Every service below falls into one of those three buckets.
Customer-Facing Services (reverse-proxied through Caddy)
Section titled “Customer-Facing Services (reverse-proxied through Caddy)”Caddy terminates TLS on the public IP and routes by hostname to a local process or Docker container. Auto-renews Let’s Encrypt certs, no config required.
| Domain | Port | Service | What It Does |
|---|---|---|---|
crm.gokartpark.com | 3200 | claude-code-crm | Go Kart Park booking engine. Owns the full customer conversation — inbound SMS reply generation, welcome messages, follow-up escalation, staged message approval, Stripe payments, waivers, party countdown reminders, review monitoring. FastAPI. |
crm.ddxweb.com | 3210 | ddxweb-crm | Multi-tenant CRM. Receives submissions from marketing sites (Springville, cleaning leads) and routes them into the right GHL subaccount. |
ath-upload.jameshurst.com | 9879 | ath-upload-receiver | Wayne Lewis’s job upload pipeline. Photos and notes from a handyman job flow in, get AI-processed, and get distributed to Google Business Profile, the All Things Handy website, and Facebook. |
cc.jameshurst.com | 3847 | remote-claude | Web interface to control Claude Code remotely from any browser. The way you talk to Claude when you are not at a keyboard. |
upp.jameshurst.com | 9876 | Ultimate Prompt Pack API | Prompt library backend. |
webhook.jameshurst.com | 9878 | webhook-receiver | GitHub push webhooks land here and trigger auto-pull on every repo so the VPS stays in sync without you touching it. |
n8n.srv1249251.hstgr.cloud | 5678 | n8n | Workflow automation. Auto-posts, integrations, glue between services. |
db.jameshurst.com | 8000 | Supabase Kong (main) | Main self-hosted Supabase project. Postgres, auth, storage, edge functions, studio. |
gospel-library.jameshurst.com | 3100 | gospel-library app | AI Gospel Library front-end. |
gospel-db.jameshurst.com | 8001 | Supabase Kong (gospel) | Second Supabase instance dedicated to the Gospel Library project. Kept separate from the main one so neither can starve the other. |
meals-api.jameshurst.com | 8091 | meal-planning-api | Recipe scraper + meal planning API. |
speech-helper.jameshurst.com | 8090 | speech-helper | Voice helper endpoint. |
quotes.jameshurst.com | — | QuoteVault | Quote collection app. |
Long-Running Services (systemd)
Section titled “Long-Running Services (systemd)”Anything that needs to stay up between reboots runs as a systemd unit. systemctl status {name}.service shows health, journalctl -u {name}.service -f tails logs.
| Service | Purpose |
|---|---|
claude-code-crm.service | Go Kart Park booking engine (the FastAPI app behind crm.gokartpark.com). |
ddxweb-crm.service | Multi-tenant CRM. |
ai-assistant-webhook.service | Receives GHL webhooks for the AI Assistant scanner (contact updates, conversation events). |
ath-upload-receiver.service | Upload endpoint for Wayne’s handyman jobs. |
dmarc-receiver.service | SMTP listener on port 25 that ingests DMARC aggregate reports. Part of the deliverability monitoring stack. |
meal-planning-api.service | Recipe scraper API. |
remote-claude.service | Claude Code web interface. |
webhook-receiver.service | GitHub auto-pull receiver. |
caddy.service | Reverse proxy for everything above. |
The Automation Layer (cron)
Section titled “The Automation Layer (cron)”This is where most of the day-to-day magic happens. Roughly 25 cron jobs live on the VPS. Grouped by purpose:
Customer conversation (Go Kart Park)
Section titled “Customer conversation (Go Kart Park)”- Follow-up engine — every minute. Scans for contacts with a pending follow-up step and fires the next SMS.
- Party countdown — hourly. Sends role-aware reminders as a party approaches (waiver not signed, payment pending, driver list incomplete).
- Birthday re-engagement — daily at 6 AM UTC. Moves contacts into the birthday re-engagement stage and kicks off the message sequence.
- Message audit — 3x daily (8 AM, noon, 6 PM MST). Pipeline health check on ongoing conversations.
- DB backup — every 6 hours.
CRM intelligence (AI Assistant)
Section titled “CRM intelligence (AI Assistant)”- ATH and GKP scanners — every 3 hours during business hours. Score contacts, surface what needs attention, alert you via SMS.
- Callback checker — every 5 minutes during business hours. Catches missed callbacks before the customer goes cold.
- CRM scanner (narrative) — daily at 8 AM MST. Contact-by-contact audit with “what should James do next” recommendations, delivered by email.
- Auditor — daily at 6:15 AM. Proactive anomaly hunt across the CRM data.
Site health and SEO
Section titled “Site health and SEO”- GSC daily health report — 8 AM MT. Multi-site Search Console sweep, auto-saves a benchmark snapshot.
- Cloudflare audit — 2 AM. Zone config drift check.
- Content-hash (YouTube) — 7:13 AM MT. Detects duplicate content before upload.
Personal automation
Section titled “Personal automation”- Auto-journal email — 10:30 PM MST. Stitches calendar, git commits, and the day’s Claude conversation into a narrative email.
- Daily health check — 6 AM. Infrastructure pulse across the fleet.
- Usage tracker — 10 PM MST. Claude Code token consumption summary.
- Token keepalive — 6 AM. Prevents OAuth tokens from aging out.
- Upwork scanner — 4x daily (6 AM, 10 AM, 2 PM, 6 PM). Scans for relevant jobs, scores them, emails the hot ones.
- Cron inventory — weekly Monday 7 AM. Emails the full cron list so nothing gets forgotten.
- Fleet health check — daily 7:15 AM. Cross-machine status sweep.
- Update manager — daily 2 AM. Checks for package and dependency updates.
- UPP content library refresh — weekly Sunday 3:17 AM.
The Data Plane (Docker)
Section titled “The Data Plane (Docker)”Two Supabase instances and n8n run as Docker Compose stacks. Containers are visible via docker ps.
Main Supabase (db.jameshurst.com)
Section titled “Main Supabase (db.jameshurst.com)”Standard self-hosted Supabase: supabase-db (Postgres 15.8), supabase-kong (API gateway), supabase-auth (gotrue), supabase-rest (postgrest), supabase-storage, supabase-realtime, supabase-studio, supabase-edge-functions, supabase-analytics (logflare), supabase-pooler (supavisor), supabase-imgproxy, supabase-vector, supabase-meta.
Gospel Library Supabase (gospel-db.jameshurst.com)
Section titled “Gospel Library Supabase (gospel-db.jameshurst.com)”Identical stack, separate instance. Prefixed gospel-*. Isolates the Gospel Library project so it cannot affect the main Supabase tenants.
n8n-n8n-1 on port 5678, fronted by n8n-traefik-1.
How It All Fits Together
Section titled “How It All Fits Together”A customer text lands at crm.gokartpark.com → Caddy proxies to claude-code-crm on port 3200 → FastAPI routes the incoming message through claude_responder.py → AI generates a reply → either queues for approval or sends directly via GHL → logs the outcome → a cron job an hour later checks if the party countdown needs to fire → that same cron writes a row to Postgres → which gets backed up every 6 hours → and the daily CRM scanner email tells James at 8 AM what happened overnight.
Each layer is boring in isolation. The pattern is: Caddy handles ingress, systemd keeps long-running apps alive, cron drives the scheduled work, and Docker holds the databases. Everything else is glue code.
Gotchas and Operating Notes
Section titled “Gotchas and Operating Notes”- VPS repos live at
/root/apps/, not/Users/ojhurst/apps/. Symlinked in spirit only. Commits authored on the VPS carry theJames Hurst (VPS)git identity tag so you always know where something was written. - VPS dirty check before closeout —
bash ~/apps/cc/vps-dirty-check.shcatches uncommitted work on the VPS. VPS sessions often leave changes that would otherwise sit forever. - No direct analytics on the VPS. Web analytics for the sites that front this server (gospel-library, meals-api, etc.) lives at the Cloudflare edge. See the How We Build Websites SOP for the Cloudflare plus D1 pattern.
- DNS is Cloudflare-managed. Records for
jameshurst.compoint to the VPS IP.*.gokartpark.com,*.ddxweb.com, and a handful of other domains follow the same pattern. - Tailscale is active (
tailscaledon100.87.130.105). Enables thevps-claudeSSH alias and secure cross-machine access.
Known Gaps / TODOs
Section titled “Known Gaps / TODOs”- No uptime monitoring dashboard. UptimeRobot or BetterStack on the public endpoints would catch outages before a customer reports one. The daily health check email is reactive, not proactive.
- No service dependency map. If
claude-code-crmgoes down, what else breaks? Nothing tracks this explicitly. - Docker stacks are not documented as code here. Supabase and n8n compose files live on the VPS only. Worth pulling them into
tms-internalfor reference. - Service-level logging is inconsistent. Some services log to
/root/apps/{name}/logs/, some tojournalctl, some to/tmp/. A single convention would make triage faster.