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:
@@ -5,6 +5,5 @@ node_modules/
|
||||
.env.local
|
||||
*.log
|
||||
storage/
|
||||
worker/worker.pid
|
||||
# Added by code-review-graph
|
||||
.code-review-graph/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
Generated
+765
-13
File diff suppressed because it is too large
Load Diff
+6
-1
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user