feat: supervise worker with pm2 instead of cron PID-check

Passenger only manages the Next.js app process; the worker now gets
the same crash-recovery/auto-restart via pm2, replacing the cron-driven
PID-file restart hack. Updates README deployment steps accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 14:49:49 +02:00
co-authored by Claude Sonnet 5
parent 65f92abe7b
commit 2b5f68752f
6 changed files with 807 additions and 62 deletions
-1
View File
@@ -5,6 +5,5 @@ node_modules/
.env.local
*.log
storage/
worker/worker.pid
# Added by code-review-graph
.code-review-graph/
+20 -11
View File
@@ -6,7 +6,7 @@ Interface web de téléchargement de vidéos via [yt-dlp](https://github.com/yt-
1. L'utilisateur soumet une URL depuis l'interface web.
2. La demande est enregistrée en base (statut `PENDING`).
3. Le worker Passenger (Node.js, arrière-plan) dépile les jobs et exécute `yt-dlp` en parallèle (concurrence configurable).
3. Le worker (Node.js, arrière-plan, supervisé par pm2) dépile les jobs et exécute `yt-dlp` en parallèle (concurrence configurable).
4. Le fichier téléchargé est stocké localement ; un token signé (24h) est généré.
5. L'utilisateur est redirigé vers une page de statut qui se rafraîchit jusqu'à ce que le fichier soit prêt.
6. Un cron quotidien supprime les fichiers expirés et marque les entrées `FILE_DELETED` — la DB conserve l'historique complet.
@@ -18,7 +18,7 @@ Interface web de téléchargement de vidéos via [yt-dlp](https://github.com/yt-
| Framework | Next.js 16 (App Router) |
| Base de données | MariaDB 11 via Prisma 7 |
| Frontend | React 19, TypeScript |
| Worker | Node.js (tsx) |
| Worker | Node.js (tsx), supervisé par pm2 |
| Conteneur (dev) | Docker Compose |
## Prérequis
@@ -70,8 +70,12 @@ L'interface est accessible sur [http://localhost:3000](http://localhost:3000).
| `npm run build` | Build de production |
| `npm start` | Serveur de production |
| `npm test` | Tests Jest |
| `npm run worker` | Worker de téléchargement (boucle de polling) |
| `npm run worker:check` | Vérifie si le worker tourne, le démarre sinon (usage cron) |
| `npm run worker` | Worker de téléchargement (boucle de polling, exécution directe/dev) |
| `npm run worker:pm2:start` | Démarre le worker sous pm2 (`ecosystem.config.cjs`), avec auto-restart |
| `npm run worker:pm2:stop` | Arrête le worker géré par pm2 |
| `npm run worker:pm2:restart` | Redémarre le worker géré par pm2 |
| `npm run worker:pm2:status` | Affiche l'état du process `ombrora-worker` |
| `npm run worker:pm2:logs` | Affiche les logs du worker géré par pm2 |
| `npm run worker:cleanup` | Expire et supprime les fichiers anciens (usage cron) |
| `npm run db:migrate` | Crée et applique les migrations Prisma |
| `npm run db:generate` | Régénère le client Prisma |
@@ -119,8 +123,9 @@ src/
worker/
├── index.ts # Boucle de polling principale
├── processor.ts # Exécution de yt-dlp, mise à jour DB
── cron-cleanup.ts # Nettoyage des fichiers expirés
└── cron-check.ts # Supervision du worker (cron)
── cron-cleanup.ts # Nettoyage des fichiers expirés
ecosystem.config.cjs # Config pm2 du process "ombrora-worker"
prisma/
└── schema.prisma # Modèles : Download, DownloadToken
@@ -132,18 +137,22 @@ prisma/
- **Pas de compilation sur le serveur** : `yt-dlp` et `ffmpeg` doivent être des binaires précompilés pour la plateforme cible, placés dans `BIN_DIR`.
- **`node_modules` unique** : Passenger ne crée qu'un seul `node_modules` à la racine. Toute dépendance runtime importée par le frontend doit figurer dans le `dependencies` du `package.json` racine (en plus de `frontend/package.json`).
- **Worker** : lancer `npm run worker:check` depuis un cron cPanel pour maintenir le worker actif.
- **Worker** : Passenger ne supervise que l'app Next.js. Le worker est un process à part, maintenu vivant par **pm2** (auto-restart en cas de crash), à la manière de Passenger pour l'app web.
### Étapes de déploiement
1. Uploader les sources (hors `node_modules`, `.env`, `storage/`).
2. Déposer `yt-dlp` et `ffmpeg` dans `BIN_DIR`.
3. Créer `.env` avec les valeurs de production.
4. Via cPanel > "Setup Node.js App" : pointer sur le dépôt, lancer `npm install` puis `npm run build`.
4. Via cPanel > "Setup Node.js App" : pointer sur le dépôt, lancer `npm install` puis `npm run build`. `pm2` est installé comme dépendance de production par ce même `npm install`.
5. Appliquer les migrations : `npm run db:migrate`.
6. Configurer deux crons cPanel :
- `npm run worker:check` toutes les 5 minutes (redémarre le worker si arrêté).
- `npm run worker:cleanup` une fois par jour (suppression des fichiers expirés).
6. Démarrer le worker sous pm2 (en SSH, dans l'environnement Node fourni par cPanel) :
```bash
npm run worker:pm2:start
pm2 save # persiste la liste des process pour un `pm2 resurrect` après reboot
```
7. Configurer un cron cPanel pour `npm run worker:cleanup` une fois par jour (suppression des fichiers expirés). pm2 gère lui-même le redémarrage du worker en cas de crash ; il n'y a donc plus besoin de cron de supervision dédié.
8. Si l'hébergeur redémarre le serveur, relancer `pm2 resurrect` (ou `npm run worker:pm2:start` si la sauvegarde `pm2 save` n'a pas été faite) pour reprendre le worker.
## Tests
+16
View File
@@ -0,0 +1,16 @@
module.exports = {
apps: [
{
name: 'ombrora-worker',
script: 'npm',
args: 'run worker',
cwd: __dirname,
autorestart: true,
max_restarts: 10,
restart_delay: 5000,
env: {
NODE_ENV: 'production',
},
},
],
}
+765 -13
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -13,7 +13,11 @@
"test": "jest",
"worker": "tsx worker/index.ts",
"worker:cleanup": "tsx worker/cron-cleanup.ts",
"worker:check": "tsx worker/cron-check.ts",
"worker:pm2:start": "pm2 start ecosystem.config.cjs",
"worker:pm2:stop": "pm2 stop ecosystem.config.cjs",
"worker:pm2:restart": "pm2 restart ecosystem.config.cjs",
"worker:pm2:status": "pm2 status ombrora-worker",
"worker:pm2:logs": "pm2 logs ombrora-worker",
"db:migrate": "prisma migrate dev",
"db:generate": "prisma generate"
},
@@ -40,6 +44,7 @@
"next": "^16.3.0",
"next-intl": "^4.13.6",
"next-themes": "^0.4.6",
"pm2": "^7.0.3",
"prisma": "^7.9.1",
"react": "^19.2.8",
"react-dom": "^19.2.8"
-36
View File
@@ -1,36 +0,0 @@
import { existsSync, readFileSync, writeFileSync } from 'fs'
import { spawn } from 'child_process'
import path from 'path'
const PID_FILE = path.join(__dirname, 'worker.pid')
function isRunning(pid: number): boolean {
try {
process.kill(pid, 0)
return true
} catch {
return false
}
}
if (existsSync(PID_FILE)) {
const pid = parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10)
if (!isNaN(pid) && isRunning(pid)) {
console.log(`[cron-check] worker already running (PID ${pid})`)
process.exit(0)
}
}
const child = spawn(
process.execPath,
[require.resolve('tsx/cli'), path.join(__dirname, 'index.ts')],
{ detached: true, stdio: 'ignore' }
)
child.unref()
if (child.pid) {
writeFileSync(PID_FILE, String(child.pid))
console.log(`[cron-check] worker started (PID ${child.pid})`)
}
process.exit(0)