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:
@@ -18,28 +18,52 @@ const path = require('path');
|
||||
const { Client } = require('pg'); // dépendance à ajouter : npm install pg
|
||||
|
||||
// --- Paramètres -------------------------------------------------------------
|
||||
const PG = {
|
||||
// Connexion PG sans mot de passe ici : il est résolu PARESSEUSEMENT dans makeClient()
|
||||
// (voir readPgPassword), pour qu'un secret absent LÈVE une erreur rattrapable au moment de
|
||||
// la détection, et non un process.exit au chargement du module. L'étage live la transforme
|
||||
// alors en alerte HA/WhatsApp — jamais en mort silencieuse.
|
||||
const PG_BASE = {
|
||||
host: process.env.PGHOST || '10.10.10.22',
|
||||
port: Number(process.env.PGPORT || 5433),
|
||||
database: process.env.PGDATABASE || 'evcs',
|
||||
user: process.env.PGUSER || 'airtria_ro',
|
||||
// mot de passe lu depuis un fichier protégé, jamais en dur ni en argument
|
||||
password: readSecret(process.env.PG_PASS_FILE ||
|
||||
path.join(__dirname, '.pg-airtria')),
|
||||
};
|
||||
|
||||
// Règle invité (voir SPEC, sens unique) : conclusif SEULEMENT au-dessus du seuil.
|
||||
const CONSIGNE_MIN_A = 20; // en dessous : aucune information → demander
|
||||
const P_INVITE_W = 4200; // VW/JEEP ~3,6-3,8 kW ; Megane 6,36 kW à 28 A
|
||||
|
||||
function readSecret(file) {
|
||||
try {
|
||||
return fs.readFileSync(file, 'utf8').trim();
|
||||
} catch (e) {
|
||||
console.error(`[concierge] Mot de passe introuvable (${file}). ` +
|
||||
`Dépose-le en lecture seule (chmod 600) avant de lancer.`);
|
||||
process.exit(2);
|
||||
// Mot de passe airtria_ro — source, dans l'ordre :
|
||||
// 1. variable d'environnement AIRTRIA_RO_PASSWORD (si un jour injectée par le compose
|
||||
// via env_file — un ajout au .env du compose exige alors de RECRÉER le conteneur) ;
|
||||
// 2. lecture du .env monté dans le conteneur : /home/node/.openclaw/.env, soit côté hôte
|
||||
// /mnt/ARCH3/docker/openclaw-data/.env. C'EST la source réelle aujourd'hui : le
|
||||
// propriétaire y met AIRTRIA_RO_PASSWORD, et comme ce fichier est monté, l'éditer
|
||||
// suffit (aucune recréation). OPENCLAW_ENV_FILE peut désigner un autre fichier (tests).
|
||||
// Pas de repli sur un fichier de secret dédié : EVCS possède le rôle et RENOUVELLE le mot
|
||||
// de passe ; un fichier figé (.pg-airtria) donnerait un secret périmé et ferait échouer
|
||||
// l'auth EN SILENCE. Si rien n'est trouvé, on LÈVE (rattrapé par l'étage live → alerte).
|
||||
function envValue(file) {
|
||||
const txt = fs.readFileSync(file, 'utf8');
|
||||
for (const line of txt.split('\n')) {
|
||||
const m = line.match(/^\s*(?:export\s+)?AIRTRIA_RO_PASSWORD\s*=\s*(.*)$/);
|
||||
if (m) {
|
||||
let v = m[1].trim();
|
||||
if ((v.startsWith('"') && v.endsWith('"')) ||
|
||||
(v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
|
||||
if (v) return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readPgPassword() {
|
||||
if (process.env.AIRTRIA_RO_PASSWORD) return process.env.AIRTRIA_RO_PASSWORD;
|
||||
const envFile = process.env.OPENCLAW_ENV_FILE || path.join(__dirname, '..', '.env');
|
||||
try { const v = envValue(envFile); if (v) return v; } catch { /* .env absent/illisible ici */ }
|
||||
throw new Error('AIRTRIA_RO_PASSWORD introuvable (ni variable d\'environnement, ni ' +
|
||||
`${envFile}) — ajouter la clé au .env puis relancer ` +
|
||||
'(si c\'est le .env du compose, recréer le conteneur)');
|
||||
}
|
||||
|
||||
// --- Requêtes (lecture seule) ----------------------------------------------
|
||||
@@ -129,7 +153,7 @@ async function main() {
|
||||
const since = process.env.SINCE || '2026-09-08T19:00:00+02:00';
|
||||
const until = process.env.UNTIL || '2026-09-13T00:00:00+02:00';
|
||||
|
||||
const client = new Client(PG);
|
||||
const client = makeClient();
|
||||
await client.connect();
|
||||
try {
|
||||
const tr = await client.query(SQL_TRANSITIONS, [since, until]);
|
||||
@@ -183,7 +207,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
function makeClient() { return new Client(PG); }
|
||||
function makeClient() { return new Client({ ...PG_BASE, password: readPgPassword() }); }
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch(e => { console.error('[concierge]', e.message); process.exit(1); });
|
||||
|
||||
Reference in New Issue
Block a user