# Deploy — founder's DC (Docker Compose, §3/§16 M6) ## Layout | Service | What | Where | |---|---|---| | `db` | PostgreSQL 16 | any node | | `app` | FastAPI backend (dashboard, admin, widget, webhooks, action links) | any node | | `voice` | AudioSocket server + Whisper STT | **GPU node** | | `asterisk` | SIP server (softphones, GSM gateway) | host networking, reachable by the gateway | ## First install ```bash git clone && cd gogotelefon cp .env.example .env # fill everything in; strong GOGO_SECRET_KEY! docker compose build docker compose up -d db app # app runs alembic upgrade head on start docker compose --profile voice up -d voice # Asterisk config comes from the DB: docker compose exec app python -m gogo.telephony.asterisk --out ./asterisk/conf \ --backend http://app:8000 --voice voice:9092 python scripts/generate_fallback_audio.py ./asterisk/sounds # needs a TTS key docker compose --profile voice up -d asterisk # seed the first admin (or python -m gogo.seed for a full demo tenant): docker compose exec app python - <<'EOF' import asyncio from gogo.auth import hash_password from gogo.db import get_sessionmaker from gogo.models import Admin async def main(): async with get_sessionmaker()() as s: s.add(Admin(email="you@gogotelefon.ba", password_hash=hash_password("CHANGE-ME"))) await s.commit() asyncio.run(main()) EOF ``` Put a TLS reverse proxy (caddy/nginx) in front of `app:8000` for `GOGO_BASE_URL` — action links and the widget are public. ## GPU node for Whisper `Dockerfile.voice` installs CPU wheels by default. On the GPU node use the NVIDIA runtime and set `GOGO_WHISPER_DEVICE=cuda`, `GOGO_WHISPER_MODEL=large-v3` (one RTX 4090/L4 handles 10–15 concurrent calls, §3). Scale by adding voice containers and pointing different tenants' Asterisk `GOGO_VOICE` at them. ## Operational jobs (built-in, APScheduler in `app`) - proposal expiry + owner reminders — every 5 min - partner polling fallback — every 5 min - retention: delete call audio older than `GOGO_AUDIO_RETENTION_DAYS` (transcripts kept, §5.4), purge stale call registrations — daily 04:00 ## Monitoring - `GET /health` (app), `GET /health/db`, `GET /health/voice` (heartbeat age) - admin panel → *Sistem*: DB, LLM key, email/SMS mode, voice heartbeat, delivery errors from `sms_log`/`email_log` - per-call latency breakdown: `calls.trace.turns[].{stt_ms,llm_ms}` ## Backups ```bash # nightly dump (cron on the DB node) — keep 14 days docker compose exec -T db pg_dump -U gogo gogo | gzip > /backup/gogo-$(date +%F).sql.gz find /backup -name 'gogo-*.sql.gz' -mtime +14 -delete ``` Recordings volume: rsync `/data/recordings` if call audio must survive node loss (transcripts are in the DB either way; audio expires after 90 days). Restore: `gunzip -c dump.sql.gz | docker compose exec -T db psql -U gogo gogo`. ## Update procedure ```bash git pull docker compose build app voice docker compose up -d app voice # alembic migrates on app start # if workers/tenants changed the SIP layout: docker compose exec app python -m gogo.telephony.asterisk --out ./asterisk/conf ... docker compose exec asterisk asterisk -rx 'core reload' ``` ## Security notes - OAuth tokens + partner API keys are encrypted at rest (Fernet from `GOGO_SECRET_KEY`) — changing the key orphans them (reconnect/re-enter). - `/internal/*` must not be exposed publicly (compose keeps it on the internal network; the reverse proxy should only forward what's needed). - Action links are signed + single-use; widget is rate-limited per IP.