Architecture

Camel Towers service architectureGuests use Cudy captive Wi-Fi and the guest portal. The Go API coordinates payments and access in PostgreSQL, while RADIUS connects back to the AP.Guest devicePhone or laptopCudy AP1300CoovaChilli / UAMRADIUS clientGuest portalNext.js · wateja-towers/ and /uamGo APIapi-towers · REST + RADIUSUDP 1812 / 1813PostgreSQLInvoices · grantssessions · auditAblinerMobile-money pushsigned webhookSendAfricaOutbound SMSWi-Ficaptive redirectHTTPS APIRADIUS / accountingSQLdeposit requestpayment callbackSMS API

Services and public names

ServicePublic nameContainer / portRole
Go APIapi-towers.camelcreatives.comapi:8000/TCPREST endpoints, provider callbacks, business rules
Guest portalwateja-towers.camelcreatives.comportal:3000/TCPPublic plan selection, payment polling, UAM browser flow
Admintowers.camelcreatives.comadmin:3000/TCPStaff-only management console
Documentationdocs-towers.camelcreatives.comdocs:3000/TCPPublic read-only handbook
PostgreSQLprivatedb:5432/TCPPersistent records
RADIUS authAPI VPS public IP1812/UDPAP Access-Request / Access-Accept
RADIUS accountingAPI VPS public IP1813/UDPAP accounting Start, Interim, Stop
CoAAP reachable address3799/UDPAPI sends Disconnect-Request

The Camel Creatives VPS uses host Nginx to terminate HTTPS and route the four names to loopback-bound Compose containers. The optional Caddy Compose profile is for a fresh host without an existing reverse proxy. RADIUS is UDP and travels directly to the VPS; neither Nginx nor Caddy proxies those packets.

Trust boundaries

Network diagram

Cudy AP and server network pathsDNS sends public hostnames to the VPS. Caddy routes HTTPS to the matching web service. RADIUS travels over UDP from AP to server and CoA returns to the AP.AP1300 OutdoorGuest VLAN / subnetCudy CoovaChilliPublic DNSapi-towerswateja-towersCaddy on VPSTCP 80 / 443TLS terminationGo APIHTTPS API + webhookUDP RADIUS 1812 / 1813Guest portalwateja-towers/uamAdmin + docstowers · docs-towersDNS + HTTPSweb trafficapi hostguest hostadmin / docs hostsUDP 1812 / 1813outbound CoA UDP 3799

Request paths

Guest purchase

  1. The browser loads active plans from the guest portal’s same-origin /api/v1 route proxy.
  2. The Go API creates a guest, invoice, and pending payment using the request idempotency key.
  3. The API calls Abliner to send a payment approval prompt to the guest’s mobile number.
  4. Abliner sends a signed webhook to https://api-towers.camelcreatives.com/webhooks/abliner.
  5. The API validates the signature and invoice amount, then commits the paid state and access grant together.
  6. The browser polls the invoice using its private purchase key. When paid, the UAM path returns a router login URL.

AP authentication

The captive browser submits the CoovaChilli challenge data to the API, receives the router’s local /logon URL, and returns to the AP. CoovaChilli then sends RADIUS authentication to the Go service. The API accepts a matching active grant and returns its remaining time as Session-Timeout.

Current status signal

The API marks a registered router online when it receives an Access-Request or accounting packet with its registered NAS ID. This is RADIUS activity, not a separate heartbeat. The API does not currently age a router to offline after an inactivity timeout; dashboard “online” counts can therefore remain stale until a later status update or manual change.

Separate apps in one deployment

The source lives in one repository and Compose stack, but each web app and the Go API is built into its own container. This keeps deployment coordinated while preserving separate runtime boundaries and public domains.