96 lines
3.5 KiB
Markdown
96 lines
3.5 KiB
Markdown
|
|
# 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 <repo> && 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.
|