Rename the whole project from CommGate to gsmnode and re-skin every surface onto the new signal-green + ink design system (Space Grotesk / IBM Plex Sans / JetBrains Mono, flat surfaces, two-arrow routing mark, lowercase gsm+node wordmark). - Web App + API panel: rewrite token layer, gn-* utilities, data-gsm-theme, new logo/favicon assets; both builds verified. - Phone App (Flutter): green theme, new mark/wordmark widgets, adaptive launcher icons; rename Android applicationId/package to app.gsmnode.phone; bump AGP to 8.6.0 and google_fonts to 8.1.0; debug APK build verified. - API Server (Go): panel routes, User-Agent, health service id, branding. - Home Assistant Plugin: rename integration domain sms_gateway to gsmnode (folder, DOMAIN, services, entity ids, classes); py_compile verified. - Update all READMEs; add .gitignore (excludes Design/, build artifacts). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gsmnode — API Server
Go service that is the single trusted entry point in front of PocketBase. The Web App and Phone App talk only to this server; it performs all PocketBase access as a superuser and enforces ownership in application logic.
Web App ─┐
├─► API Server (:8080) ─► PocketBase (10.2.1.10:8028)
Phone App ─┘
The server root (GET /) serves a gsmnode-branded web panel: a live health
readout plus a quick reference of both API audiences. Open
http://localhost:8080/ in a browser to check the server at a glance.
The panel is a Vue 3 + Tailwind v4 app in panel/, built into
internal/api/dist and embedded into the Go binary at compile time:
cd panel
npm install
npm run build # outputs to ../internal/api/dist
cd ..; go build -o api-server.exe ./cmd/server # embeds the fresh dist
For panel development with hot reload (proxies /api to a running server on
:8080): cd panel; npm run dev → http://localhost:5174.
Requirements
- Go 1.26+ (
go version) - A reachable PocketBase v0.23+ instance and its superuser credentials
- Node 18+ (only for the setup scripts)
1. Configure
Copy-Item .env.example .env
# edit .env: set PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD, and a strong JWT_SECRET
| Variable | Purpose | Default |
|---|---|---|
API_ADDR |
Listen address | :8080 |
POCKETBASE_URL |
PocketBase base URL | http://10.2.1.10:8028 |
PB_ADMIN_EMAIL / PB_ADMIN_PASSWORD |
PocketBase superuser login | — (required) |
JWT_SECRET |
Signs client JWTs | dev placeholder |
JWT_ACCESS_TTL |
Access-token lifetime | 24h |
CORS_ALLOW_ORIGINS |
Comma list, or * |
* |
2. Set up PocketBase collections
Creates/updates devices, messages, inbox, webhooks (the default users
auth collection is reused). Idempotent.
$env:POCKETBASE_URL="http://10.2.1.10:8028"
$env:PB_ADMIN_EMAIL="admin@example.com"
$env:PB_ADMIN_PASSWORD="your-password"
node scripts/setup-pocketbase.mjs
Create a login user:
node scripts/create-user.mjs user@example.com "user-password" "Display Name"
3. Run
./scripts/Run-ApiServer.ps1
# or: go run ./cmd/server
Health check: GET http://localhost:8080/api/health.
API
Authentication scheme
- Client API (
/api/...):Authorization: Bearer <JWT>from/api/auth/login. Used by the Web App and integrators. - Mobile API (
/api/mobile/...):Authorization: Bearer <device_token>returned by device registration. Used by the Phone App.
Client / 3rd-party endpoints
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Readiness probe (public) |
POST |
/api/auth/login |
{email, password} → JWT + user |
POST |
/api/auth/refresh |
New JWT (auth) |
GET |
/api/auth/me |
Current user |
GET |
/api/devices |
List your devices |
DELETE |
/api/devices/{id} |
Remove a device |
POST |
/api/messages |
Enqueue SMS {phone_numbers[], text_message, device_id?, sim_number?, schedule_at?} |
POST |
/api/calls |
Enqueue a phone call {phone_number, device_id?} |
GET |
/api/messages |
List messages (?status=&device_id=&type=&page=&per_page=) |
GET |
/api/messages/{id} |
Message state |
GET |
/api/inbox |
Received SMS |
GET |
/api/webhooks |
List webhooks |
POST |
/api/webhooks |
Register {event, url, device_id?} |
DELETE |
/api/webhooks/{id} |
Delete webhook |
Mobile / device endpoints
| Method | Path | Description |
|---|---|---|
POST |
/api/mobile/v1/device |
Register device (auth: user JWT) → returns auth_token |
POST |
/api/mobile/v1/ping |
Heartbeat (auth: device token) |
GET |
/api/mobile/v1/messages |
Pull pending messages; marks them Processed |
PATCH |
/api/mobile/v1/messages/{id} |
Report {status, error?} (Sent/Delivered/Failed) |
POST |
/api/mobile/v1/inbox |
Report received SMS {phone_number, message, received_at?} |
Message lifecycle
Pending → (device pulls) Processed → Sent → Delivered, or Failed.
Calls are stored as messages with type: "call" (SMS is type: "sms") and flow
through the same pull/report pipeline. The device dials the number natively and
reports Sent (a call has no delivery report) or Failed.
Webhook events
sms:received, sms:sent, sms:delivered, sms:failed. Delivered as
POST {event, device_id, payload, created_at} to the registered URL.
Quick smoke test
# log in
$login = curl -s -X POST http://localhost:8080/api/auth/login `
-H "Content-Type: application/json" `
-d '{"email":"user@example.com","password":"user-password"}' | ConvertFrom-Json
$token = $login.access_token
# register a device (as the phone app would)
curl -s -X POST http://localhost:8080/api/mobile/v1/device `
-H "Authorization: Bearer $token" -H "Content-Type: application/json" `
-d '{"device_id":"test-1","name":"Test Phone","platform":"android"}'
Project layout
cmd/server/main.go entry point, wiring, graceful shutdown
internal/config env/.env configuration
internal/pb PocketBase REST client (superuser)
internal/auth JWT issue/verify, device token generation
internal/api router, middleware, handlers
scripts/ PocketBase setup + run helpers