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>
243 lines
9.2 KiB
Markdown
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.
|