fix : fail to connect agent
Some checks failed
release / build (push) Successful in 24s
release / verify-windows (push) Failing after 52s

This commit is contained in:
jeanotx32
2026-08-11 21:37:52 +02:00
parent eb2c741038
commit 3b614c5d05

View File

@@ -1,6 +1,7 @@
import { execFile } from 'node:child_process'; import { execFile } from 'node:child_process';
import net from 'node:net'; import net from 'node:net';
import { promisify } from 'node:util'; import { promisify } from 'node:util';
import { WebSocket } from 'ws';
import { CONFIG_PATH, type AgentConfig } from './config.ts'; import { CONFIG_PATH, type AgentConfig } from './config.ts';
const run = promisify(execFile); const run = promisify(execFile);
@@ -30,6 +31,67 @@ function probeTcp(host: string, port: number, timeoutMs = 4000): Promise<string
}); });
} }
/**
* Tente la poignée de main WebSocket réelle, jeton compris.
*
* On se déconnecte sans envoyer `hello` : le serveur n'attache un agent qu'à
* réception de ce message, donc ce test ne peut pas évincer la session du
* service en cours d'exécution.
*/
function probeHandshake(
config: AgentConfig,
timeoutMs = 8000,
): Promise<{ ok: boolean; detail: string }> {
return new Promise((resolve) => {
let socket: WebSocket;
try {
socket = new WebSocket(config.serverUrl, {
headers: { authorization: `Bearer ${config.token}` },
rejectUnauthorized: !config.insecureTls,
handshakeTimeout: timeoutMs,
});
} catch (err) {
resolve({ ok: false, detail: err instanceof Error ? err.message : String(err) });
return;
}
const finish = (result: { ok: boolean; detail: string }) => {
clearTimeout(timer);
try {
socket.close();
} catch {
/* ignore */
}
resolve(result);
};
const timer = setTimeout(
() => finish({ ok: false, detail: 'aucune réponse à la poignée de main' }),
timeoutMs + 1000,
);
socket.once('open', () => finish({ ok: true, detail: 'jeton accepté' }));
socket.once('error', (err: Error) => {
const message = err.message;
if (message.includes('401')) {
finish({
ok: false,
detail:
'JETON REFUSÉ (401). Le serveur ne reconnaît pas ce jeton : il a été régénéré, ' +
"ou l'agent a été supprimé. Régénère-le dans le dashboard puis réinstalle avec --reset-identity.",
});
return;
}
if (message.includes('certificate') || message.includes('self-signed')) {
finish({ ok: false, detail: `certificat TLS rejeté (${message})` });
return;
}
finish({ ok: false, detail: message });
});
});
}
async function commandExists(command: string, args: string[]): Promise<boolean> { async function commandExists(command: string, args: string[]): Promise<boolean> {
try { try {
await run(command, args, { timeout: 8000, windowsHide: true }); await run(command, args, { timeout: 8000, windowsHide: true });
@@ -71,12 +133,20 @@ export async function runDiagnostics(config: AgentConfig): Promise<number> {
if (serverUrl) { if (serverUrl) {
const port = Number(serverUrl.port) || (serverUrl.protocol === 'wss:' ? 443 : 80); const port = Number(serverUrl.port) || (serverUrl.protocol === 'wss:' ? 443 : 80);
const error = await probeTcp(serverUrl.hostname, port); const tcpError = await probeTcp(serverUrl.hostname, port);
if (tcpError) {
results.push(line('fail', 'serveur', `${serverUrl.host} injoignable (${tcpError})`));
} else {
// Le port ouvert ne prouve rien : un jeton refusé produit exactement la
// même réponse TCP. On tente donc la vraie poignée de main.
const handshake = await probeHandshake(config);
results.push( results.push(
error handshake.ok
? line('fail', 'serveur', `${serverUrl.host} injoignable (${error})`) ? line('ok', 'serveur', `${serverUrl.host} — jeton accepté`)
: line('ok', 'serveur', `${serverUrl.host} joignable`), : line('fail', 'serveur', `${serverUrl.host} ${handshake.detail}`),
); );
}
if (serverUrl.protocol === 'ws:' && !isLoopback(serverUrl.hostname)) { if (serverUrl.protocol === 'ws:' && !isLoopback(serverUrl.hostname)) {
line('warn', 'transport', 'ws:// non chiffré — à réserver à un réseau privé'); line('warn', 'transport', 'ws:// non chiffré — à réserver à un réseau privé');
} }