Fix : obs bug 1
Some checks failed
release / build (push) Successful in 38s
release / verify-windows (push) Failing after 1m3s

This commit is contained in:
jeanotx32
2026-08-11 19:04:32 +02:00
parent e284337fb2
commit b85a535dd8
2 changed files with 51 additions and 5 deletions

View File

@@ -242,7 +242,8 @@ La rotation des jetons se fait alors depuis le dashboard, agent par agent.
| Le conteneur redémarre en boucle | `docker compose logs server` — le plus souvent `ADMIN_PASSWORD` ou `SESSION_SECRET` absent de `.env` |
| `unable to open database file` (`ERR_SQLITE_ERROR`, errcode 14) | Le répertoire `./data` de l'hôte n'appartient pas à l'uid du conteneur. L'entrypoint le corrige automatiquement ; si tu as forcé un `user:` dans le compose, il ne peut plus le faire — aligne alors la propriété à la main : `sudo chown -R 1000:1000 data` |
| Agent absent du dashboard | `--server` erroné, ou `ENROLLMENT_TOKEN` différent de celui du serveur. `journalctl -u stream-control-agent -f` |
| Agent en ligne, OBS déconnecté | obs-websocket désactivé ou mot de passe erroné. Le diagnostic teste le port |
| Agent en ligne, OBS déconnecté — `ECONNREFUSED` | Rien n'écoute : OBS n'est pas lancé, ou *Outils → Paramètres du serveur WebSocket* n'est pas activé. Le mot de passe n'intervient pas à ce stade |
| Agent en ligne, OBS déconnecté — erreur d'authentification | Le mot de passe du dashboard et celui d'OBS diffèrent. Laisse-le vide **uniquement** si l'authentification est décochée dans OBS |
| Rappel plein écran sans effet (Linux) | Session Wayland au lieu de X11, ou `DISPLAY` inaccessible au service. Le diagnostic signale les deux |
| Rappel plein écran sans effet (Windows) | La tâche ne tourne pas dans la session interactive : vérifie que le compte de `-RunAsUser` est bien celui ouvert sur la VM |
| Agent en double après réinstallation | `agent.config.json` avait été supprimé : l'agent s'est ré-enrôlé. Supprime le doublon dans le dashboard |

View File

@@ -33,6 +33,43 @@ type ObsSnapshot = Pick<
>;
const RECONNECT_DELAY_MS = 5000;
const CONNECT_TIMEOUT_MS = 10_000;
/** Borne une promesse qui pourrait ne jamais se résoudre. */
async function withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {
let timer: NodeJS.Timeout | undefined;
try {
return await Promise.race([
promise,
new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error(message)), ms);
}),
]);
} finally {
if (timer) clearTimeout(timer);
}
}
/**
* Traduit une erreur de connexion en message actionnable. Le brut
* (« ECONNREFUSED ») dit ce qui a échoué, jamais quoi faire ensuite.
*/
function describeConnectionError(err: unknown): string {
const message = err instanceof Error ? err.message : String(err);
const sysCode = (err as NodeJS.ErrnoException).code;
const obsCode = (err as { code?: unknown }).code;
if (sysCode === 'ECONNREFUSED' || message.includes('ECONNREFUSED')) {
return `${message} — rien n'écoute sur ce port. Lance OBS, puis Outils → Paramètres du serveur WebSocket → coche « Activer le serveur WebSocket ».`;
}
if (obsCode === 4009 || /authent/i.test(message)) {
return `${message} — mot de passe obs-websocket incorrect ou manquant. Dans OBS : Outils → Paramètres du serveur WebSocket → Afficher les informations de connexion.`;
}
if (sysCode === 'ENOTFOUND' || sysCode === 'EHOSTUNREACH' || sysCode === 'ETIMEDOUT') {
return `${message} — hôte injoignable. Vérifie l'adresse configurée pour cet agent.`;
}
return message;
}
/**
* Enveloppe obs-websocket : maintient la session, expose les actions du
@@ -101,16 +138,24 @@ export class ObsController extends EventEmitter {
this.connecting = (async () => {
try {
const info = await this.obs.connect(url, this.settings.password || undefined, {
rpcVersion: 1,
});
// Un port ouvert par autre chose qu'obs-websocket ne renvoie jamais de
// poignée de main : sans ce délai, la promesse resterait éternellement
// en attente et, comme elle est réutilisée ci-dessus, toute tentative
// ultérieure serait bloquée jusqu'au redémarrage de l'agent.
const info = await withTimeout(
this.obs.connect(url, this.settings.password || undefined, { rpcVersion: 1 }),
CONNECT_TIMEOUT_MS,
`aucune réponse après ${CONNECT_TIMEOUT_MS / 1000} s — le port répond mais ne parle pas obs-websocket ?`,
);
this.connected = true;
this.lastError = undefined;
this.cachedVersion = info.obsWebSocketVersion;
this.emit('log', 'info', `Connecté à OBS ${url} (obs-websocket ${info.obsWebSocketVersion})`);
} catch (err) {
this.connected = false;
this.lastError = err instanceof Error ? err.message : String(err);
this.lastError = describeConnectionError(err);
// Après un délai dépassé, le socket peut rester à demi ouvert.
await this.obs.disconnect().catch(() => undefined);
throw new Error(`Connexion à OBS impossible (${url}) : ${this.lastError}`);
} finally {
this.connecting = null;