ti-pote/docs/infrastructure.md
ordinarthur 674109ea22 feat: initial project setup — Phase 0
Monorepo pnpm avec NestJS backend en architecture hexagonale.
- Structure hexagonale complète (ports, adapters, domain entities)
- 9 entities TypeORM (Home, User, Device, Credentials, Session, Message, Memory, Timer)
- Migration initiale SQL avec pgvector support
- Docker Compose (PostgreSQL 16 + pgvector + Redis 7)
- Config partagée (tsconfig, ESLint, Prettier)
- Outbound ports définis (STT, TTS, LLM, Cache, Storage, VectorStore)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 09:01:52 +01:00

243 lines
9.2 KiB
Markdown

# Infrastructure
## Vue d'ensemble du déploiement
```
┌─────────────────────────────────────────────────────────────┐
│ VPS Personnel │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ NestJS │ │ PostgreSQL │ │ Redis │ │
│ │ Core │ │ + pgvector │ │ │ │
│ │ (Docker) │ │ (Docker) │ │ (Docker) │ │
│ └──────┬───────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ┌──────┴───────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Nginx │ │ Certbot │ │ Prometheus │ │
│ │ (Reverse │ │ (SSL/TLS) │ │ + Grafana │ │
│ │ Proxy) │ │ │ │ (Monitoring)│ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ Docker Compose orchestre l'ensemble │
└─────────────────────────────────────────────────────────────┘
│ HTTPS / WSS
┌──────────┐ ┌──────────────┐
│ Robots │ │ App Web / │
│ Ti-Pote │ │ Mobile │
└──────────┘ └──────────────┘
```
## Composants d'infrastructure
### VPS
Recommandation pour commencer : un VPS avec 4 vCPU, 8 Go RAM, 80 Go SSD. Suffisant pour un usage personnel avec quelques robots connectés. Providers à considérer : Hetzner (bon rapport qualité/prix en Europe), OVH, ou Scaleway.
Quand le projet grandira, on pourra passer sur un setup avec plusieurs containers ou migrer vers du Kubernetes, mais pour le MVP un seul VPS suffit largement.
### Docker Compose
Tout le stack tourne dans Docker Compose. Avantages : déploiement reproductible, isolation des services, facilité de mise à jour.
```yaml
# docker-compose.yml (structure simplifiée)
version: '3.8'
services:
core:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://tipote:${DB_PASSWORD}@postgres:5432/tipote
- REDIS_URL=redis://redis:6379
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- LLM_API_KEY=${LLM_API_KEY}
- STT_API_KEY=${STT_API_KEY}
- TTS_API_KEY=${TTS_API_KEY}
depends_on:
- postgres
- redis
restart: unless-stopped
postgres:
image: pgvector/pgvector:pg16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_DB=tipote
- POSTGRES_USER=tipote
- POSTGRES_PASSWORD=${DB_PASSWORD}
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redisdata:/data
command: redis-server --appendonly yes
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- certbot-etc:/etc/letsencrypt
depends_on:
- core
restart: unless-stopped
volumes:
pgdata:
redisdata:
certbot-etc:
```
### Nginx (Reverse Proxy)
Nginx sert de point d'entrée unique. Il gère le TLS (via Let's Encrypt / Certbot), le routing entre REST et WebSocket, et le rate limiting.
Configuration clé : le WebSocket nécessite les headers `Upgrade` et `Connection` pour fonctionner à travers le proxy.
```nginx
# Extrait de config nginx pertinent
upstream core {
server core:3000;
}
server {
listen 443 ssl;
server_name tipote.example.com;
ssl_certificate /etc/letsencrypt/live/tipote.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/tipote.example.com/privkey.pem;
# REST API
location /api/ {
proxy_pass http://core;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# WebSocket
location /ws/ {
proxy_pass http://core;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400; # 24h — connexion persistante
}
# Frontend (SPA)
location / {
proxy_pass http://core;
}
}
```
### PostgreSQL + pgvector
PostgreSQL 16 avec l'extension pgvector pour le stockage des embeddings (mémoire sémantique).
Stratégie de backup : pg_dump quotidien automatisé + envoi vers un stockage externe (S3 ou équivalent). Un cron job sur le VPS suffit pour le MVP.
```sql
-- Activation de pgvector
CREATE EXTENSION IF NOT EXISTS vector;
-- Exemple : table pour les embeddings de mémoire
CREATE TABLE memory_embeddings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
content TEXT NOT NULL,
embedding vector(1536), -- dimension OpenAI ada-002
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON memory_embeddings
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
```
### Redis
Utilisé pour trois choses :
1. **Sessions de conversation actives** — Historique des messages en cours, avec un TTL de 30 minutes d'inactivité.
2. **Timers et alarmes** — Stockés avec un TTL correspondant au délai. Redis déclenche un événement à l'expiration via les keyspace notifications.
3. **Cache** — Résultats de recherche web, réponses fréquentes, pour réduire la latence et les coûts API.
### Monitoring (Prometheus + Grafana)
À mettre en place dès le MVP pour suivre :
- Latence de bout en bout (audio in → audio out)
- Taux d'erreur par service externe (STT, TTS, LLM)
- Utilisation des ressources (CPU, RAM, disque)
- Nombre de conversations actives
- Coût estimé en tokens LLM par jour
## Sécurité
### TLS partout
Toutes les communications (robot → core, app → core) passent par TLS. Le WebSocket utilise WSS. Let's Encrypt fournit les certificats, renouvelés automatiquement par Certbot.
### Gestion des secrets
Les secrets sont gérés à plusieurs niveaux :
**Variables d'environnement du VPS** — Les clés API (LLM, STT, TTS) et la clé de chiffrement maître sont stockées dans un fichier `.env` sur le VPS, jamais dans le code. Le fichier `.env` a des permissions restrictives (600, root only).
**Chiffrement des credentials utilisateur** — Les tokens OAuth (Google, Apple) et mots de passe SMTP des utilisateurs sont chiffrés en AES-256-GCM avant d'être stockés en base PostgreSQL. La clé de chiffrement est la variable d'environnement `ENCRYPTION_KEY`.
```typescript
// Exemple simplifié du module de chiffrement
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
const ALGORITHM = 'aes-256-gcm';
export function encrypt(plaintext: string, key: Buffer): string {
const iv = randomBytes(16);
const cipher = createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
// Stocke IV + authTag + données chiffrées, encodés en base64
return Buffer.concat([iv, authTag, encrypted]).toString('base64');
}
export function decrypt(encryptedBase64: string, key: Buffer): string {
const data = Buffer.from(encryptedBase64, 'base64');
const iv = data.subarray(0, 16);
const authTag = data.subarray(16, 32);
const encrypted = data.subarray(32);
const decipher = createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(authTag);
return decipher.update(encrypted) + decipher.final('utf8');
}
```
**Évolution future** — Quand le projet aura plus d'utilisateurs, migration vers HashiCorp Vault ou un équivalent cloud pour la rotation automatique des clés et l'audit trail.
### Authentification
- **Robot → Core** : authentification par JWT (device token) émis lors de l'enregistrement du device. Token rafraîchi périodiquement.
- **App → Core** : authentification classique (email/password ou OAuth2). JWT access token (courte durée) + refresh token (longue durée).
- **Core → Services tiers** : OAuth2 avec stockage chiffré des refresh tokens. Le core rafraîchit les access tokens automatiquement.
## CI/CD
Pour le MVP, un pipeline simple :
1. Push sur `main` → GitHub Actions lance les tests
2. Si les tests passent → build de l'image Docker
3. Push de l'image vers un registry (GitHub Container Registry)
4. Déploiement sur le VPS via SSH + docker compose pull + redémarrage
À terme, on pourra ajouter des environnements de staging, du blue-green deployment, etc.