goipd is a standalone Rust service for Hybertone/Dbltek GOIP1, GOIP4, GOIP8, and GOIP16 gateways. It receives authenticated gateway events over UDP, sends SMS using GOIP's UDP or HTTP protocols, stores asynchronous jobs in SQLite, and delivers signed webhooks.
Rust 1.97 or newer is required.
cp goipd.example.toml goipd.toml
# Replace every example secret and adjust the gateway inventory.
cargo run --releaseConfiguration comes from goipd.toml, or the path in GOIPD_CONFIG. Any secret may contain env:VARIABLE_NAME; startup fails if that variable is absent. GOIPD_API_TOKEN and GOIPD_WEBHOOK_SECRET directly override their configured values, while GOIPD_HTTP_BIND, GOIPD_UDP_BIND, and GOIPD_DATABASE_PATH override runtime locations. For HTTP sending, host may be a hostname/IP (HTTP is assumed) or an explicit URL.
Secrets may also use file:/run/secrets/name. File references are preferred in production; one trailing newline is removed. Production API and webhook secrets must contain at least 32 bytes.
curl --fail-with-body http://127.0.0.1:8080/v1/messages \
-H 'Authorization: Bearer replace-me' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order-123' \
-d '{"gateway":"office","line":1,"transport":"udp","to":"+380000000000","text":"Hello"}'Poll GET /v1/messages/{id} with the same bearer token. GET /health/live, GET /health/ready, and GET /metrics are unauthenticated and should be network-restricted. Terminate TLS at a reverse proxy.
The Compose stack starts directly in simulation mode and works with Docker Compose, Podman's Compose provider, or podman-compose. Create the local environment file and replace its API token:
cp .env.example .env
docker compose up --build -d
# Alternatively:
podman compose up --build -d
# Or, when installed separately:
podman-compose up --build -dFollow startup logs with the matching Compose command and stop cleanly with compose down. SQLite data is retained in the goipd-data named volume; compose down -v intentionally deletes that data. The stack publishes HTTP on port 8080 and simulated inbound UDP on unprivileged port 13333 by default, both configurable in .env.
Production is a single active instance backed by local SQLite. Do not share its database volume between replicas. The production Compose definition uses an immutable GHCR image, mounted secrets, a read-only root filesystem, health checks, resource limits, log rotation, a separate backup volume, and rootless-compatible UDP port 13333.
cp goipd.production.example.toml goipd.production.toml
mkdir -m 700 secrets
openssl rand -base64 48 > secrets/api-token
openssl rand -base64 48 > secrets/webhook-secret
# Add the three GOIP credentials referenced by compose.production.yml.
chmod 600 secrets/*
export GOIPD_VERSION=2.0.0
podman machine start # macOS only
podman compose -f compose.production.yml config
podman compose -f compose.production.yml up -dEdit the gateway address, IDs, sources, and webhook URL before starting. Configure physical gateways to send inbound events to host UDP 13333. TCP 8080 is bound to host loopback for a TLS reverse proxy; the management listener remains inside the container on 127.0.0.1:9090.
Validate configuration and create an immediate snapshot with:
podman compose -f compose.production.yml exec goipd goipd check-config
podman compose -f compose.production.yml exec goipd goipd backupThe service creates a validated SQLite snapshot every 24 hours and retains seven daily plus four weekly snapshots. Copy the backup volume to separate storage; snapshots on the same host are not disaster recovery. Completed jobs and fully processed events are removed after 90 days in bounded batches. Pending webhook deliveries are never removed by retention.
To restore, stop goipd, preserve the current database plus its -wal and -shm files, copy the selected validated snapshot to the configured database path as UID 10001, remove stale -wal/-shm files only after preserving them, and restart. Confirm /health/ready, queue metrics, and a simulation or controlled gateway send before deleting the preserved database. Never use compose down -v during upgrade, rollback, or restore.
Release tags publish linux/amd64 and linux/arm64 images to ghcr.io/rustedbytes/goipd with version/commit tags, an SBOM, and provenance. Upgrade by pulling a new immutable tag, running check-config and a backup, changing GOIPD_VERSION, and recreating the service. Roll back by restoring the previous image tag; restore the database snapshot only when a schema/data problem requires it.
Recommended alerts are: no successful backup for 30 hours, any backup failure, oldest queued job above five minutes, any permanently failed webhook, sustained gateway authentication failures, less than 20% disk free, or repeated container restarts. Logs are JSON and include version, revision, request IDs, job IDs, gateway names, and lines where relevant; message bodies and credentials are not logged.
Use simulation mode to exercise the complete service without a physical GOIP gateway:
cargo run --release -- --simulateThe flag registers a temporary gateway named simulator with line 1, starts compatible UDP and HTTP gateway emulators on loopback, and emits authenticated inbound gateway events every three seconds. Submit jobs through the normal API using either transport:
curl --fail-with-body http://127.0.0.1:8080/v1/messages \
-H "Authorization: Bearer $GOIPD_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"gateway":"simulator","line":1,"transport":"udp","to":"001","text":"UDP simulation"}'
curl --fail-with-body http://127.0.0.1:8080/v1/messages \
-H "Authorization: Bearer $GOIPD_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"gateway":"simulator","line":1,"transport":"http","to":"001","text":"HTTP simulation"}'The Compose stack already supplies --simulate. The regular SQLite, API, metrics, and webhook paths remain active, so simulated jobs and inbound events are processed exactly like gateway traffic. Simulation binds local ports 19991/udp and 18081/tcp inside the container process and is intended only for development and verification.
Configure GOIP to send events to udp_bind. Packets are accepted only when id, source IP, and pass/password match the inventory. Valid REQ, STATE, RECORD, HANGUP, RECEIVE, and DELIVER packets are acknowledged and durably published. Invalid packets receive no ACK. Phone and device identifiers remain strings, and credentials are redacted from storage, API output, logs, and webhooks.
Delivery is at least once. Deduplicate with event_id or X-Goipd-Event-Id. Verify X-Goipd-Signature as sha256= plus the hex HMAC-SHA256 of X-Goipd-Timestamp + "." + raw_body. Failures retry with bounded exponential backoff; exhausted deliveries remain recorded in SQLite.
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo build --locked --releaseSee openapi.yaml for the API. SQLite is single-instance state; do not share one database between replicas.