concierge: lire airtria_ro depuis l'env/.env + alerte d'echec d'auth
Le mot de passe airtria_ro est desormais lu depuis AIRTRIA_RO_PASSWORD (variable d'environnement, sinon .env monte dans le conteneur), plus depuis .pg-airtria : EVCS renouvelle ce mot de passe, un fichier fige faisait echouer l'auth EN SILENCE. readPgPassword LEVE (rattrapable) au lieu de process.exit ; mot de passe resolu paresseusement dans makeClient. Nouveau module concierge-health.js (meme patron que le moniteur LLM) : battement de coeur horodate a CHAQUE cycle detect, statut live dans HA, et alerte WhatsApp UNIQUEMENT sur transition ok<->erreur (dedup, pas de rappel periodique). Chemin HA independant de PostgreSQL : l'alerte auth KO part meme quand c'est l'auth qui tombe. Best-effort : si les helpers HA manquent, le concierge n'echoue pas. - concierge-health.test.js : machine a etats (dedup + transitions) verifiee, sans reseau - concierge-health-ha-package.yaml.draft : helpers HA (a integrer cote proprietaire) - SPEC : source du secret corrigee + section supervision Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZsbhyGmRwZogdoBwT6C6y
This commit is contained in:
86
workspace/concierge-health.js
Normal file
86
workspace/concierge-health.js
Normal file
@@ -0,0 +1,86 @@
|
||||
// concierge-health.js — santé du concierge véhicule, publiée dans Home Assistant.
|
||||
// -----------------------------------------------------------------------------
|
||||
// Même patron que le moniteur LLM (llm-monitor.js), appliqué au cycle `detect` :
|
||||
// - BATTEMENT DE CŒUR à CHAQUE passage (horodatage sur input_datetime) : sans lui, un
|
||||
// concierge mort — cron arrêté, conteneur tombé, auth qui échoue au chargement — est
|
||||
// indiscernable d'un système sain. HA peut alors crier quand l'horodatage se fige.
|
||||
// Valeur qui CHANGE à chaque fois (horodatage, pas « ok ») : HA ne rafraîchit pas le
|
||||
// last_changed d'un helper réécrit à l'identique.
|
||||
// - ALERTE WhatsApp UNIQUEMENT sur TRANSITION (ok↔erreur), jamais un rappel périodique
|
||||
// (« events not states ») : une panne d'auth qui dure ne spamme pas le canal.
|
||||
// - Chemin HA INDÉPENDANT de PostgreSQL : l'alerte « auth KO » part précisément quand
|
||||
// c'est PG/l'auth qui tombe — le signal de vie n'emprunte pas le chemin qui tombe.
|
||||
// - Best-effort : ne fait JAMAIS échouer le concierge (aucune entité requise, aucun jet).
|
||||
// - Interrupteur HA : OFF → pas de WhatsApp, mais l'affichage reste à jour.
|
||||
// -----------------------------------------------------------------------------
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const STATE = process.env.CONCIERGE_HEALTH_STATE ||
|
||||
path.join(__dirname, 'concierge-health-state.json');
|
||||
const HA_BASE = process.env.HA_BASE || 'http://10.10.10.17:8123';
|
||||
|
||||
const HA_ENABLED = 'input_boolean.concierge_monitor_enabled'; // interrupteur alertes on/off
|
||||
const HA_STATUS = 'input_text.concierge_status'; // libellé d'état (max 255)
|
||||
const HA_LASTRUN = 'input_datetime.concierge_last_run'; // battement de cœur (chaque cycle)
|
||||
|
||||
const load = (f, d) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return d; } };
|
||||
const save = (f, o) => { try { fs.writeFileSync(f, JSON.stringify(o, null, 2)); } catch { /* best-effort */ } };
|
||||
const haToken = () => {
|
||||
try { return fs.readFileSync(process.env.HA_TOKEN_FILE || path.join(__dirname, '.ha-token'), 'utf8').trim(); }
|
||||
catch { return null; }
|
||||
};
|
||||
|
||||
async function haGet(entity) {
|
||||
const tok = haToken(); if (!tok) return null;
|
||||
try {
|
||||
const r = await fetch(`${HA_BASE}/api/states/${entity}`, { headers: { Authorization: `Bearer ${tok}` } });
|
||||
return r.ok ? r.json() : null;
|
||||
} catch { return null; }
|
||||
}
|
||||
async function haCall(domain, service, data) {
|
||||
const tok = haToken(); if (!tok) return;
|
||||
try {
|
||||
await fetch(`${HA_BASE}/api/services/${domain}/${service}`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${tok}`, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
} catch { /* best-effort */ }
|
||||
}
|
||||
|
||||
// Publie la santé du cycle et renvoie le message de TRANSITION à envoyer par WhatsApp
|
||||
// (ou '' si pas de transition, ou si l'interrupteur est OFF). `ok` = le cycle detect a
|
||||
// réussi (lecture PostgreSQL comprise) ; `detail` = message d'erreur si !ok.
|
||||
async function publish({ ok, detail }) {
|
||||
const prev = load(STATE, { status: 'ok' });
|
||||
const status = ok ? 'ok' : 'alert';
|
||||
const hhmm = new Date().toLocaleTimeString('fr-FR',
|
||||
{ hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' });
|
||||
|
||||
// Battement de cœur À CHAQUE passage + statut live (best-effort, chemin indépendant de PG).
|
||||
await haCall('input_datetime', 'set_datetime',
|
||||
{ entity_id: HA_LASTRUN, timestamp: Math.floor(Date.now() / 1000) });
|
||||
const label = (ok ? 'OK' : `⚠️ ${detail || 'erreur détection'}`).slice(0, 240);
|
||||
await haCall('input_text', 'set_value', { entity_id: HA_STATUS, value: `${label} · ${hhmm}` });
|
||||
|
||||
// Alerte seulement sur la bascule d'état.
|
||||
let out = '';
|
||||
if (status === 'alert' && prev.status !== 'alert') {
|
||||
out = `⚠️ Concierge véhicule en panne : ${detail || 'erreur de détection'}. ` +
|
||||
`Il ne peut plus poser la question du véhicule tant que ce n'est pas corrigé.`;
|
||||
} else if (status === 'ok' && prev.status === 'alert') {
|
||||
out = '✅ Concierge véhicule de nouveau opérationnel.';
|
||||
}
|
||||
|
||||
save(STATE, { status, at: new Date().toISOString(), detail: ok ? null : (detail || null) });
|
||||
|
||||
if (!out) return '';
|
||||
const enabled = await haGet(HA_ENABLED);
|
||||
if (enabled && enabled.state === 'off') return ''; // muet : affichage à jour, pas de WhatsApp
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports = { publish, HA_ENABLED, HA_STATUS, HA_LASTRUN };
|
||||
Reference in New Issue
Block a user