Plateforme de vente de packs vidéos avec streaming sécurisé, optimisée pour un homelab avec Traefik.
- Fonctionnalités
- Stack technique
- Architecture
- Installation
- Configuration
- Déploiement
- Gestion des vidéos
- Webhooks LemonSqueezy
- Sécurité
- 🔐 Authentification : NextAuth avec vérification email obligatoire
- 💳 Paiements : Intégration LemonSqueezy avec webhooks
- 🎥 Streaming sécurisé : HLS + X-Accel-Redirect via Nginx
- 🔒 Protection : Liens signés + watermarking côté client
- 🌐 Reverse proxy : Traefik pour le routage et SSL
- 📧 Emails : Nodemailer pour vérification et confirmations
- Framework : Next.js 14+ (App Router, TypeScript)
- Auth : NextAuth.js (Auth.js) avec Prisma
- Database : PostgreSQL
- Cache : Redis
- ORM : Prisma
- React : Server & Client Components
- Video Player : Video.js avec HLS
- Styling : CSS moderne avec design system
- Reverse Proxy : Traefik (sur l'hôte)
- Media Server : Nginx (X-Accel-Redirect)
- Containerisation : Docker Compose
- Streaming : HLS (HTTP Live Streaming)
┌─────────────┐
│ Traefik │ ← Port 80/443 (sur l'hôte)
└──────┬──────┘
│
▼
┌─────────────────┐
│ Next.js App │ ← Labels Traefik
│ (Port 3000) │
└────────┬────────┘
│
├─────→ PostgreSQL (DB)
├─────→ Redis (Cache)
└─────→ Nginx (Media Server)
│
└─── /videos (volume partagé)
- Liens signés : Génération HMAC avec TTL configurable
- X-Accel-Redirect : Nginx sert les fichiers via location interne
- HLS Streaming : Empêche le téléchargement direct
- Watermarking client : Overlay dynamique avec email utilisateur
- Docker & Docker Compose
- Traefik configuré sur l'hôte avec le réseau
br-dams - Node.js 20+ (pour le développement local)
- FFmpeg (pour la conversion HLS)
git clone <votre-repo>
cd dw-streamCopier le fichier d'environnement :
cp .env.example .envÉditer .env avec vos valeurs :
# Database
DATABASE_URL="postgresql://voduser:vodpassword@db:5432/voddb"
# NextAuth
NEXTAUTH_URL="https://vod.yourdomain.com"
NEXTAUTH_SECRET="votre-secret-tres-long-min-32-caracteres"
# LemonSqueezy
LEMONSQUEEZY_API_KEY="votre-api-key"
LEMONSQUEEZY_STORE_ID="votre-store-id"
LEMONSQUEEZY_WEBHOOK_SECRET="votre-webhook-secret"
# SMTP (exemple avec Gmail)
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_USER="[email protected]"
SMTP_PASSWORD="votre-app-password"
SMTP_FROM="VOD Platform <[email protected]>"
# Signed URLs
SIGNED_URL_SECRET="autre-secret-pour-videos"
SIGNED_URL_TTL="3600"Dans docker-compose.yml, ajustez le domaine :
labels:
- "traefik.http.routers.vod-app.rule=Host(`vod.yourdomain.com`)"sudo mkdir -p /home/user/data/vod/videos
sudo chown -R $USER:$USER /home/user/data/vod# Installer les dépendances
npm install
# Démarrer PostgreSQL et Redis (via Docker ou local)
# ...
# Générer Prisma Client
npx prisma generate
# Créer les migrations
npx prisma migrate dev
# Démarrer le serveur de dev
npm run devAccéder à : http://localhost:3000
# Construire et démarrer les conteneurs
docker-compose up -d
# Vérifier les logs
docker-compose logs -f app
# Exécuter les migrations Prisma
docker-compose exec app npx prisma migrate deployAccéder à : https://vod.yourdomain.com (via Traefik)
# Arrêter les services
docker-compose down
# Reconstruire après modification
docker-compose up -d --build
# Voir les logs
docker-compose logs -f
# Accéder à la base de données
docker-compose exec db psql -U voduser -d voddb
# Prisma Studio (interface DB)
docker-compose exec app npx prisma studioRendre le script exécutable :
chmod +x scripts/convert-to-hls.shConvertir une vidéo :
./scripts/convert-to-hls.sh ma-video.mp4 formation-1 introCela crée :
/home/user/data/vod/videos/
└── formation-1/
└── intro/
├── playlist.m3u8
├── segment_000.ts
├── segment_001.ts
└── ...
Pour plus de contrôle :
ffmpeg -i input.mp4 \
-c:v libx264 -crf 23 -preset medium \
-c:a aac -b:a 128k \
-sc_threshold 0 \
-g 48 -keyint_min 48 \
-hls_time 10 \
-hls_playlist_type vod \
-hls_segment_filename "/home/user/data/vod/videos/bundle-1/video-1/segment_%03d.ts" \
-f hls \
"/home/user/data/vod/videos/bundle-1/video-1/playlist.m3u8"Paramètres expliqués :
-crf 23: Qualité (0-51, 23 = bonne qualité)-hls_time 10: Durée des segments (10 secondes)-hls_playlist_type vod: Type de playlist VOD-g 48 -keyint_min 48: Keyframe tous les 2 secondes (pour seeking)
Via Prisma Studio (npx prisma studio) ou SQL :
-- 1. Créer un bundle
INSERT INTO "Bundle" (id, title, description, price, currency, "variantId", active)
VALUES (
'bundle-1',
'Formation Complete',
'Apprenez tout sur...',
99.99,
'EUR',
'lemonsqueezy-variant-id',
true
);
-- 2. Ajouter les vidéos
INSERT INTO "Video" (id, title, description, "hlsPath", "bundleId", "order")
VALUES (
'video-1',
'Introduction',
'Présentation du cours',
'formation-1/intro/playlist.m3u8',
'bundle-1',
1
);- Dans votre Dashboard LemonSqueezy, allez dans Settings > Webhooks
- Créez un nouveau webhook :
- URL :
https://vod.yourdomain.com/api/webhooks/lemonsqueezy - Events : Sélectionner
order_created(et optionnellementsubscription_created) - Secret : Générer et copier dans
.env→LEMONSQUEEZY_WEBHOOK_SECRET
- URL :
order_created: Création automatique dePurchaseen DBsubscription_created: Support des abonnements (optionnel)
Utiliser l'outil de test LemonSqueezy ou :
curl -X POST https://vod.yourdomain.com/api/webhooks/lemonsqueezy \
-H "Content-Type: application/json" \
-H "X-Signature: votre-signature-hmac" \
-d '{ "meta": { "event_name": "order_created" }, ... }'Le watermarking est côté client uniquement pour économiser le CPU :
- Overlay React avec email de l'utilisateur
- Position et rotation aléatoires toutes les 10s
- Semi-transparent, non-interactif
Limites : Un utilisateur technique peut le contourner (CSS/DevTools).
Protection principale : HLS + Liens signés empêchent le partage direct des URLs.
- Génération HMAC SHA-256 avec secret
- TTL configurable (défaut: 1 heure)
- Validation côté serveur avant streaming
- Nginx sert les fichiers via location
internal - Accès direct aux fichiers bloqué
- Next.js contrôle l'autorisation
- Secrets forts : Utilisez des secrets de 32+ caractères
- HTTPS obligatoire : Traefik avec Let's Encrypt
- Rate limiting : Ajoutez Traefik rate limiting si nécessaire
- Backups : Sauvegardez régulièrement PostgreSQL et
/videos
dw-stream/
├── docker-compose.yml
├── Dockerfile
├── nginx.conf
├── package.json
├── tsconfig.json
├── next.config.js
├── prisma/
│ └── schema.prisma
├── scripts/
│ └── convert-to-hls.sh
├── src/
│ ├── app/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── globals.css
│ │ ├── auth/
│ │ │ ├── signin/page.tsx
│ │ │ └── signup/page.tsx
│ │ ├── bundles/page.tsx
│ │ ├── dashboard/page.tsx
│ │ └── api/
│ │ ├── auth/
│ │ │ ├── [...nextauth]/route.ts
│ │ │ ├── register/route.ts
│ │ │ └── verify-email/route.ts
│ │ ├── stream/[videoId]/route.ts
│ │ └── webhooks/lemonsqueezy/route.ts
│ ├── components/
│ │ ├── Navigation.tsx
│ │ ├── Providers.tsx
│ │ ├── VideoPlayer.tsx
│ │ └── WatermarkOverlay.tsx
│ ├── lib/
│ │ ├── auth.ts
│ │ ├── email.ts
│ │ ├── lemonsqueezy.ts
│ │ ├── prisma.ts
│ │ ├── redis.ts
│ │ └── signed-urls.ts
│ ├── middleware.ts
│ └── types/
│ └── next-auth.d.ts
└── README.md
- Vérifier que le volume est monté :
docker-compose exec app ls -la /videos - Vérifier les permissions :
ls -la /home/user/data/vod/videos - Vérifier les logs Nginx :
docker-compose logs media-server
- Vérifier l'authentification (session NextAuth)
- Vérifier l'achat en DB (
Purchasetable) - Vérifier la signature du lien (pas expiré)
- Vérifier les credentials SMTP dans
.env - Pour Gmail, utiliser un "App Password"
- Vérifier les logs :
docker-compose logs app | grep "Email"
- Vérifier que
LEMONSQUEEZY_WEBHOOK_SECRETest correct - Tester la signature HMAC
- Vérifier les logs :
docker-compose logs app | grep "Webhook"
MIT
Créé pour un homelab personnel avec amour ❤️
Note : Ce projet est optimisé pour un homelab. Pour une production à grande échelle, envisagez :
- CDN pour le streaming
- Watermarking serveur avec GPU
- Load balancing
- Monitoring (Prometheus/Grafana)