Savannah Cloud

Project Structure

Directory layout and key files in the Savannah Cloud monorepo.

distributed-task-queue/
├── cmd/server/main.go         # Entry point — wires all services, registers job handlers
├── dashboard/
│   └── src/
│       ├── components/        # Sidebar, AppShell, UI primitives (Button, Input, Badge, Toast)
│       ├── context/           # AuthContext
│       ├── lib/               # api.ts — typed API client
│       └── pages/             # Dashboard, Deployments, Pipelines, Domains,
│                              #   Email, SMS, Team, Billing, APIKeys, Settings,
│                              #   Login, Join
├── api.go                     # HTTP router + all handlers
├── auth.go                    # JWT, bcrypt, API key, session helpers
├── deploy.go                  # Deployment engine (framework detection, Docker build/run)
├── dns.go                     # Custom domain handlers + background DNS verifier goroutine
├── email.go                   # send_email job handler (Resend)
├── sms.go                     # send_sms job handler + /sms/send handler (Africa's Talking)
├── mcp.go                     # MCP server (JSON-RPC 2.0 + SSE)
├── metrics.go                 # Prometheus counters
├── postgres.go                # All PostgreSQL queries (jobs, orgs, users, domains, deployments)
├── session.go                 # Redis session engine (sliding 30min TTL, HttpOnly cookie)
├── worker.go                  # Worker pool (BLPOP, retry, backoff)
├── redis_queue.go             # Redis queue implementation
├── docker-compose.yml         # 5-service stack (app, dashboard, postgres, redis, pdns)
├── Dockerfile                 # Multi-stage Go + Node build
├── pg_hba.conf                # Custom postgres auth rules (trust for Docker network)
├── pdns.conf                  # PowerDNS configuration
└── fix_scram.sh               # One-shot postgres SCRAM password reset script

Key Files

cmd/server/main.go

Entry point. Wires together all services and registers job handlers:

pool.Register("send_email", emailHandler)
pool.Register("send_sms", smsHandler)
pool.Register("webhook", webhookHandler)
pool.Register("deploy", deployHandler)

Add custom handlers here.

api.go

HTTP router using net/http. All route registrations and handler functions. Organized by domain: auth, jobs, deployments, domains, SMS, MCP, usage, team, API keys.

postgres.go

All database queries — no ORM. Uses pgx/v5 with named structs. Each domain (jobs, orgs, deployments, etc.) has its own section.

worker.go

Worker pool with configurable concurrency (WORKER_COUNT, default 4). Each goroutine runs BLPOP with 5s timeout, processes the job, and handles retry logic.

deploy.go

Deployment engine. Detects framework from repo contents, runs the appropriate build command, creates a Docker container, assigns a port from 9100–9999, and injects the Savannah badge.

dns.go

Custom domain management. Creates PowerDNS zones via REST API and runs the background verifier goroutine on a 5-minute ticker.

mcp.go

MCP server. Implements tools/list and tools/call over JSON-RPC 2.0. SSE stream for real-time events.

pg_hba.conf

Custom PostgreSQL auth rules. Tracked in the repo and mounted read-only into the postgres container:

# Docker network — trust (no password needed)
host all all 172.0.0.0/8 trust

# External connections — scram-sha-256
host all all 0.0.0.0/0 scram-sha-256

fix_scram.sh

One-shot script to reset the PostgreSQL SCRAM password hash if auth fails after a container rebuild. Run from the server:

bash fix_scram.sh

Dashboard Pages

PageFileDescription
/dashboardpages/Dashboard.tsxLive job feed, WebSocket, quick stats
/deploymentspages/Deployments.tsxApp deployments, logs, env vars
/pipelinespages/Pipelines.tsxCI/CD pipeline view
/domainspages/Domains.tsxCustom domain management
/emailpages/Email.tsx3-column email compose UI
/smspages/SMS.tsxSMS compose + history
/teampages/Team.tsxMembers, invite, roles
/billingpages/Billing.tsxPlan, usage, Paystack
/api-keyspages/APIKeys.tsxCreate/revoke API keys
/settingspages/Settings.tsxOrg settings
/loginpages/Login.tsxSign in
/joinpages/Join.tsxAccept team invite

On this page