Commit graph

61 commits

Author SHA1 Message Date
080441ef41 feat(portal): accès Internet direct — kaya.freeboxos.fr:9080
Choix simplicité (2026-07-07) : exposition directe du portail plutôt que
le VPN (WireGuard reste déployé mais dormant, sans redirection Freebox).

- gateway : DNAT 9080/tcp → Traefik (192.168.10.200:80) + rate-limit
  30 connexions neuves/min sur le trafic Internet (LAN non concerné)
- IngressRoute guacamole : host kaya.freeboxos.fr accepté
- Guacamole : formulaire local en premier ("*, openid") — l'accès externe
  passe par des comptes locaux, Authentik n'est pas exposé (SSO = LAN)
- doc : section « Accès externe » dans admin/k8s/auth-portal.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:08:44 +02:00
78ab7ae311 feat(wireguard): accès distant au lab via VPN (Freebox 51820/udp → s01)
- rôle wireguard : serveur wg0 (10.99.0.0/24) sur storage-01, profils
  clients générés dans /etc/wireguard/clients/ (fichier .conf / QR code)
- gateway : filtrage nftables par pair — full (tout le lab) / portal
  (uniquement Traefik :80 = portail Guacamole), masquerade VPN→cluster
- dnsmasq : bind-dynamic + écoute wg0 → *.lab.local résolu dans le tunnel
- pairs initiaux : alkatrazz (full), invite-01 (portal)
- vault : clés WireGuard + reprise de l'archive Authentik (remplace #78)
- doc : admin/infra/wireguard.md (onboarding pair, pièges, exploitation)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 23:16:38 +02:00
87b786eb57
feat(auth): annuaire Authentik + portail consoles Guacamole (#76)
- k8s/apps/authentik : IdP OIDC (auth.lab.local), namespace auth —
  server + worker 2026.5.3, Redis in-cluster, PG sur storage-01, PVC media
- k8s/apps/guacamole : portail consoles SSH web s01/g01 (portail.lab.local) —
  guacd + webapp 1.6.0, SSO OIDC vers Authentik, Job initdb idempotent
- ansible : rôle console_user (user sans sudo pour les consoles, s01 + g01),
  bases PG authentik/guacamole, secrets vault (mdp PG + clé SSH console)
- doc : admin/k8s/auth-portal.md (déploiement, config initiale, pièges)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:24:46 +02:00
507f4822f0
feat(ansible): serveur dédié Satisfactory sur gpu-01 (#74)
* feat(ansible): serveur dédié Satisfactory sur gpu-01

- Nouveau rôle satisfactory_server : install steamcmd (login anonyme,
  app 1690800), service systemd avec update auto en ExecStartPre,
  MemoryMax=16G + Nice=5 (protège le llama-server), firewalld 7777 tcp+udp
- Retries steamcmd (« Missing configuration » rc=8 au premier run)
- Fcontext SELinux bin_t sur steamcmd/ et server/ (203/EXEC sinon)
- gateway : DNAT 192.168.1.200:7777 → gpu-01:7777 (TCP+UDP) + forward
- Doc admin/ops/satisfactory.md (accès, exploitation, pièges rencontrés)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(satisfactory): ouvrir le port 8888/tcp Reliable Messaging

Depuis la 1.1, le client ouvre un socket TCP :8888 en plus du jeu UDP
:7777. Bloqué par firewalld, le join réussissait puis le client était
déconnecté (~60 s) avec « connection to the host has been lost »
(log client : LogReliableMessaging: Failed to connect to server socket).

- firewalld gpu-01 : + 8888/tcp (variable satisfactory_reliable_port)
- gateway s01 : DNAT + forward étendus à 8888/tcp
- doc : tableau des 3 sockets + piège n°3

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(satisfactory): accès internet via IPv4 full-stack Free

Le remap de port Freebox (17777→7777) ne fonctionne pas : le serveur
annonce ses ports dans le protocole (externe = interne obligatoire).
Option IPv4 full-stack activée → redirections 1:1 7777 tcp+udp + 8888 tcp
vers funk-s01, DNAT existant inchangé.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 00:56:28 +02:00
de1cd6c407
fix(gpu): débloque make apply-gpu — repo amdgpu rhel9.7 + pin commit llama.cpp (#47)
Le `dnf upgrade` du rôle common avait bumpé gpu-01 en AlmaLinux 9.8, mais
repo.radeon.com ne publie pas latest/rhel/9.8 (ni 9.5) — seulement 9.4/9.6/9.7.
`make apply-gpu` échouait alors dès `common` sur « Cannot download repomd.xml »
pour le repo amdgpu.

- rocm_rhel_version 9.4 → 9.7 (dernière mineure publiée par radeon) + piège
  documenté dans le README rocm (avec workaround live /etc/dnf/vars/amdgpudistro).
- Pin llama_server_commit au commit construit sur g01 : sans pin le rôle clonait
  HEAD → rebuild (~15 min) + restart GPU non déterministe à chaque apply.
- Réconciliation du drop-in llama-embed (ubatch 2048) dans l'unit rendu par le
  rôle (apply ciblé) ; drop-in temporaire supprimé. RAG validé (rag-query OK).
- Doc : etat-cluster + journal 2026-06-22 mis à jour.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:19:02 +02:00
af98b47018
docs(cluster): aligne la doc sur l'état réel + re-ingest RAG + fix ubatch embed (#44)
* fix(rag): ubatch=2048 pour llama-embed + retry rag-ingest + nettoie defaults rag

L'instance llama-embed (:1238) tournait avec l'ubatch llama.cpp par défaut (512).
En mode --embeddings + pooling, toute l'entrée doit tenir dans un seul ubatch →
tout chunk > 512 tokens échouait en HTTP 500 « input too large to process.
increase the physical batch size ». ~46 chunks du RAG (CHUNK_MAX=2000 chars ≈
700 tokens) n'étaient pas indexés.

- llama_server : ajoute llama_embed_ubatch_size (2048 = ctx) + flags
  --batch-size/--ubatch-size dans le template llama-embed.service.
- rag-ingest : retry (5×, backoff) sur erreurs transitoires 5xx/réseau de l'embed.
- rag/defaults : aligne embed_url/embed_model sur nomic :1238 (étaient des vars
  mortes pointant encore qwen3-8b :1234 — trompeuses).

Résultat : ré-ingestion 436/436 chunks, 0 erreur.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(cluster): aligne la doc sur l'état réel (relevé 2026-06-21) + snapshot

Passe de vérification terrain (SSH s01/g01 + kubectl) pour corriger les écarts
doc↔réalité, et ajout d'un document de référence vérifié.

- admin/ops/etat-cluster.md (nouveau) : snapshot daté — machines, services+ports
  par hôte, workloads k8s, namespaces, IngressRoute, bases PG, modèles LiteLLM,
  RAG/Qdrant, + procédure pour régénérer le relevé.
- OS AlmaLinux 9.7 → 9.8 (s01/g01) ; précise k8s v1.33.1 (CLAUDE/README/install/talos).
- RAG funk-docs : retire le warning périmé « supprimée 2026-06-17 / à re-ingérer » →
  reconstruite (436 chunks, nomic-embed-text :1238 dim 768, CPU). Maj rag.md,
  incidents.md, stt.md.
- Namespace sacrifice (hors-repo, hors ArgoCD) + bases PG grafana/sacrifice +
  services ai via IngressRoute + registry in-cluster (CLAUDE.md, k9s.md).
- Services de résilience (llm-heartbeat, llama-watchdog/embed) et instances CPU
  manuelles :1236/:1237 (souvent éteintes) documentés.
- Journal progress/2026-06-21.md : section relevé + RAG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 23:29:05 +02:00
f0e33c1121
feat(stt): active les actions via Hermes (STT_ACTIONS_ENABLED=true) (#43)
* feat(stt): active les actions via Hermes (STT_ACTIONS_ENABLED=true)

hermes-exec déployé sur storage-01 (:9096) + secret k8s stt-server-secrets/hermes-exec-token
en place → on active le contexte « agent ». Après déploiement ArgoCD, le contexte
🤖 Agent apparaît dans le sélecteur et les actions (avec confirmation) sont opérationnelles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: journal 2026-06-21 + état STT (portail, contextes, intents, actions Hermes)

- progress/2026-06-21.md : récap complet de la journée (portail + santé live, contextes
  visualiseur + presets live, intents vocaux, jeton Ghostfolio unifié, actions via Hermes,
  ASR Parakeet) + l'incident de merge des PR empilées et sa leçon.
- PROGRESS.md : ligne du jour.
- CLAUDE.md : « État actuel » daté 2026-06-21, ligne STT enrichie.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(vault): ajoute vault_hermes_exec_token (jeton actions hermes-exec, chiffré)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 04:09:35 +02:00
9a46f6cbcb
feat(stt): actions cluster via Hermes — contexte « agent » + exécuteur hermes-exec (#42)
Asa peut AGIR sur le homelab (l'autre moitié de la vision), via le vrai agent Hermes,
profil par défaut, tous ses outils — avec confirmation et jeton.

Découverte : Hermes n'a pas d'API HTTP (appli TUI), mais un mode one-shot `hermes -z
"<prompt>" --yolo`. On s'appuie dessus.

storage-01 — exécuteur :
- tools/hermes-exec/server.py : service HTTP qui lance `hermes -z --yolo` en user hermes,
  derrière un jeton Bearer (compare_digest), timeout + audit, une action à la fois.
- rôle Ansible hermes_exec : systemd (User=hermes, env hermes-agent), jeton via
  EnvironmentFile 0640 (Vault vault_hermes_exec_token) ; ajouté au playbook storage-01.

STT-server (0.5.0 → 0.6.0) :
- agent.py : pont vers hermes-exec (jeton) + détection confirme/annule.
- contexts.py : contexte « agent » (court-circuite le LLM).
- app.py : flux dédié — handshake 2 temps par session (pending action) → sur « confirme »,
  appelle hermes-exec ; « annule » annule. /v1/contexts masque « agent » si désactivé.
- config : STT_ACTIONS_ENABLED (opt-in, défaut false) + URL + jeton (secret k8s).
- deployment : env actions + secret stt-server-secrets/hermes-exec-token (optionnel).

Sécurité : opt-in désactivé par défaut ; jeton obligatoire (sinon contexte caché + exécuteur
refuse tout) ; --yolo atteint seulement jeton+confirmation en main ; audit storage-01.
Client : AUCUN changement (le contexte « agent » apparaît tout seul dans le sélecteur).

Validé en local : exécuteur (401 sans/mauvais jeton, exec avec bon jeton via echo),
handshake serveur (TestClient : confirme→exécute, annule→annule, agent masqué si OFF).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 04:00:14 +02:00
470c69881c
chore(gpu-01): retire les serveurs llama CPU system/monitor/dev (inutilisés) (#31)
Trois instances llama-server CPU manuelles (hors Ansible) tournaient sur gpu-01
sans servir : déjà orphelines de LiteLLM (alias absents de config.yaml, d'où le
bruit "Invalid model name"). Suppression complète :

gpu-01 (opérationnel, déjà fait) :
- systemctl disable --now + rm des units llama-server-{system,monitor,dev}
- ports firewall 1235/1236/1237 fermés

Repo :
- monitoring : scrape jobs (values.yaml) + alertes LlamaServer{System,Monitor}Down
  (alerts-ai.yaml) retirés → plus de "target down"/alertes fantômes ; panneaux de
  statut dashboard-infrastructure recyclés (system→llama-embed, monitor supprimé).
- docs admin/ + hermes-skills (souls funk-ai/brain, SKILL agent-delegation) mises à
  jour : inventaire modèles, profils Hermès system/monitor sans backend (à repointer
  sur qwen3-8b), litellm, ops, monitoring.

Les profils Hermès system/monitor existent toujours mais sans backend dédié.
Dashboards Grafana dashboard-ai (panneaux graphiques CPU) laissés tels quels
(cosmétique, séries vides, aucune alerte).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 13:25:50 +02:00
ef15ba25e0
fix(llama_server): embed nomic sur CPU — supprime la contention GPU (docs=0) (#30)
Le heartbeat embed (#28) gardait le slot :1238 chargé mais docs=0 revenait :
latence bimodale 16ms / 9-12s. Cause = contention GPU, pas cold-start. nomic
(:1238, --n-gpu-layers 99) partageait la RX 6700XT avec le chat qwen3-8b (:1234) ;
ROCm sérialise → un embed lancé pendant une génération chat attend ~10s → dépasse
le budget recall 4s du STT-server → docs=0 intermittent.

- llama_embed_n_gpu_layers: 99 → 0 (CPU). nomic = 137M → ~100ms déterministe,
  découplé du slot chat. Le template lit déjà la variable.
- heartbeat embed conservé comme sonde de vivacité (commentaire ajusté).
- incidents-llm-gpu.md : section dédiée + README rôle mis à jour.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 12:38:08 +02:00
1b85d4ee65
feat(llama_server): heartbeat embed — garde chaud le slot nomic :1238 (#28)
Le grounding RAG du STT ratait par intermittence (docs=0 → réponses génériques)
parce que l'instance d'embedding :1238 partait à froid après une pause : la 1ʳᵉ
requête mettait 5-8 s et dépassait le timeout 4 s du STT-server (recall + RAG doc).

llama-embed-heartbeat : service systemd local sur gpu-01 qui envoie un petit embed
à :1238 toutes les 20 s → le slot reste résident/chaud → la 1ʳᵉ vraie requête est
rapide → docs>0 fiable. Pendant du llm-heartbeat (slot chat :1234), pour le slot embed.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 22:17:23 +02:00
5855bd0b29
feat(rag): embedder dédié nomic-embed-text (768) au lieu de qwen3-8b (#25)
funk-docs utilisait le modèle de CHAT qwen3-8b (:1234, 4096 dim) comme
embedder → similarités quasi indiscernables (tous les scores ~0.96, ranking
médiocre). Bascule sur l'instance dédiée nomic-embed-text (:1238, 768 dim) —
la même que la mémoire STT — déjà identifiée comme roadmap dans le README.

rag-ingest ET rag-query alignés (même modèle/dim). Seuil rag-query abaissé
0.60→0.40 (nomic étale les scores plus bas). Collection recréée en 768.

Mesure : scores désormais étalés 0.61-0.74, et les bons docs ressortent en
tête (dnsmasq→dnsmasq.md, nftables→phase gateway, wedge→llama_server.md).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:52:52 +02:00
9feb02ed70
feat(stt): boutons stop/mute (HUD) + stt --stop + RAG funk-docs nettoyé (#24)
* fix(rag): exclure hermes/builtin de l'index funk-docs

Les rapports auto-générés par hermes-auto-improve (admin/hermes/builtin/,
"ne pas éditer") représentaient ~84% des points de la collection et noyaient
la vraie doc → rag-query remontait du bruit. On les élague à l'ingestion
(os.walk), surchargeable via RAG_EXCLUDE. Collection re-bâtie : 403 points
propres (0 builtin), rag-query remonte de nouveau les bons documents.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(stt-client): boutons stop réponse + mute micro (HUD) + `stt --stop`

HUD : deux contrôles dans la barre du haut.
- « stop » coupe la réponse en cours — interrompt la lecture TTS (kill aplay)
  et saute la synthèse si pressé pendant la génération (Event _interrupt).
- « micro » coupe/réactive l'entrée audio (la boucle VAD ignore les trames) ;
  l'état fait foi côté backend, renvoyé au HUD via {"type":"mic"} (ré-émis à
  la connexion d'un nouveau client).

Protocole WS : nouveau message {"type":"control","action":"stop|mute|unmute"}.

CLI : `stt --stop` éteint le service — systemd --user (stt.service) si actif,
sinon SIGTERM aux process vocaux trouvés via /proc (s'exclut + ignore les
sous-commandes utilitaires).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:17:32 +02:00
577d61ebaa
feat: anti-502 LLM — heartbeat (litellm) + watchdog auto-réparation (llama_server) (#23)
* feat(litellm): heartbeat anti-502 + request_timeout 20s

Cause racine du 502 « 1ʳᵉ demande après une pause » : la connexion keep-alive
LiteLLM↔llama-server devient inactive → llama-server la ferme → LiteLLM garde le
socket mort → la requête suivante part dans le vide → timeout → 502.

- llm-heartbeat : service systemd qui appelle hermes-default toutes les 15s
  (max_tokens:1, /no_think → ~10ms GPU) → la connexion n'est jamais inactive,
  jamais périmée. Logge les échecs → sert aussi de sonde (vraie génération).
- request_timeout 60→20s : un socket périmé échoue vite, dans la fenêtre où
  num_retries:2 peut rejouer sur une connexion neuve (sinon le client abandonnait
  avant le retry → 502 sec).
- Doc : admin/incidents-llm-gpu.md (fix racine) + README rôle.

⚠️ heartbeat appelle hermes-default en continu → si bascule sur Claude (facturé),
mettre llm_heartbeat_enabled: false. request_timeout global → remonter si Claude.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(llama_server): watchdog auto-réparation du wedge ROCm

Service systemd local sur gpu-01 qui sonde une vraie génération sur :1234
(pas juste /health, qui ment quand le slot d'inférence est figé). Sur N
échecs consécutifs → systemctl restart llama-server en local (root, sans
SSH/sudo distant). Gère le 503 "Loading model" post-restart sans le compter
comme échec.

Complète le llm-heartbeat (rôle litellm) : le heartbeat empêche la péremption
par inactivité de la connexion ; le watchdog répare le figeage du serveur lui-même.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:52:29 +02:00
d6ec83af9d
fix(litellm): request_timeout 600→60s + num_retries 2 (anti-connexion périmée) (#22)
Cause du burst de 502 « cold-start » : après un restart de llama-server, LiteLLM
garde des connexions keep-alive mortes dans son pool → la requête suivante hang
jusqu'au timeout. Or request_timeout valait 600s → hang jusqu'à 10 min, sans log
(LiteLLM attendait encore). Fix :
- request_timeout 60s : borne le hang d'une connexion morte
- num_retries 2 : rejoue → évince le socket périmé, repart sur une connexion neuve

Doc : section dédiée dans admin/incidents-llm-gpu.md (mécanisme + application +
reste à faire = watchdog qui bounce llama-server ET litellm).

Application : ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags litellm
(redémarre LiteLLM → applique le fix ET purge le pool d'un coup).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 16:00:29 +02:00
9fef555cc3
feat(stt): embeddings dédiés nomic-embed-text sur gpu-01 (:1238) + migration auto (#12)
* feat(stt): mémoire long-terme sémantique via Qdrant (5b)

Serveur : longterm.py — collection Qdrant stt-memory (embeddings Qwen3 gpu-01, dim auto,
Cosine), recall top-k injecté au prompt, remember des tours user. Tout dégrade proprement
si Qdrant/embeddings injoignables (la mémoire court-terme tient). Env STT_MEMORY_LONGTERM,
STT_QDRANT_URL, STT_EMBED_URL, STT_MEMORY_TOPK.

Testé en process : dégradation OK (Qdrant down → mem=0, pas de crash, court-terme tient).
Qdrant réparé le 17/06 (5c). Recherche sémantique réelle à valider sur cluster.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

* feat(stt): endpoint /v1/memory/health + upsert Qdrant synchrone

- /v1/memory/health sonde activement embeddings + Qdrant + collection et
  expose les erreurs (recall/remember dégradent en silence → indébogables).
  Permet de diagnostiquer la mémoire long-terme sans kubectl exec.
- remember() : upsert avec ?wait=true → le souvenir est immédiatement
  cherchable (sans wait, Qdrant met l'écriture en file → un recall
  cross-session immédiat pouvait le rater).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

* docs(stt): 5b mémoire long-terme validée en prod + backlog nomic-embed-text

- Rappel cross-session confirmé (« Felix » retrouvé dans une nouvelle session),
  points_count vérifié via /v1/memory/health.
- Note du fix upsert ?wait=true et de l'endpoint de diagnostic.
- Roadmap : 5d (nomic-embed-text dim 768) en backlog qualité ; états haut/bas
  du doc mis à jour (déployé + validé sur cible).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

* feat(stt): embeddings dédiés nomic-embed-text sur gpu-01 (:1238) + migration auto

Remplace Qwen3 (chat réutilisé, dim 4096, peu discriminant) par un modèle
d'embedding spécialisé nomic-embed-text (dim 768) pour la mémoire long-terme.

Ansible (rôle llama_server) :
- nouvelle instance optionnelle `llama-embed` (llama_embed_enabled) servant un
  modèle d'embedding dédié sur :1238, GPU ; télécharge le GGUF si absent.
- activée sur gpu-01 (host_vars) : nomic-embed-text-v1.5 f16.

STT-server :
- STT_EMBED_URL → :1238, STT_EMBED_MODEL → nomic-embed-text (deployment + config).
- _ensure_collection détecte le changement de dimension (4096→768) et recrée
  automatiquement la collection stt-memory (anciens vecteurs incomparables) —
  pas de drop manuel.

Docs : llama_server README, rag.md, stt.md (5d ), CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-17 21:54:41 +02:00
4f1c03df08 feat(ghostfolio): suivi de portefeuille sur k8s (PG s01 + Redis)
Nouvel app ArgoCD ghostfolio (namespace ai) : suivi/analyse de portefeuille
boursier. Base PostgreSQL dédiée sur storage-01, Redis in-cluster pour le cache.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:04:19 +02:00
0aa4c3650d docs(ansible): README par rôle (19) + gaps IaC découverts à l'audit
Audit complet des rôles vs état réel du cluster. Chaque rôle a maintenant
un README.md : but, variables (défauts + surcharges host_vars/vault),
handlers, vérifications, caveats.

Gaps découverts et documentés :
- minio : rôle VIDE (tasks/main.yml sans aucune tâche), service inactif —
  placeholder à implémenter ou retirer (ports 9000/9001 ouverts pour rien)
- llama_server : ne gère que l'instance GPU :1234 — les services CPU
  llama-server-system :1236 et llama-server-monitor :1237 sont créés à la
  main sur gpu-01, non reproductibles par make apply-gpu (+ un
  llama-server-dev.service non documenté présent sur l'hôte)
- hermes_auto_improve : defaults port/repo/branch/log non consommés
  (valeurs en dur dans les scripts), défaut branch obsolète
- lm_studio : README de dépréciation, candidat suppression

Tables admin/ops/ansible.md, README.md et CLAUDE.md corrigées en conséquence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:21:24 +02:00
40fd7f0868 docs: synchroniser la doc avec l'état réel du cluster (revue Ansible + incident Qdrant)
- ops/ansible.md : tableau complet des rôles par playbook (15 storage-01, 6 gpu-01),
  caveat hermes_agent, note lm_studio inutilisé
- ia/hermes-auto-improve.md : NOUVEAU — doc du worker déployé (trigger :9095,
  modes du script, endpoints, PR squash, pièges connus)
- k8s/n8n.md : workflow 3 "Hermes Doc Worker" documenté (22h, /trigger/all,
  boucle de polling), clé API → vault
- incidents.md : Qdrant crash-loop depuis 2026-06-05 (segment funk-docs corrompu,
  invisible dans --state=failed) + procédure de réparation
- ia/rag.md : bandeau RAG HS
- CLAUDE.md : état 2026-06-10, rôle hermes_auto_improve, 3 workflows n8n,
  vault complet (n8n_api_key, Gmail au lieu de Brevo), section auto-improve
- README.md : lien admin/ops/cluster.md corrigé, arborescence complète
  (tools/, admin/, progress/, skills), roadmap à jour
- PROGRESS.md : entrée 2026-05-29 (migration NVMe) qui existait sans être référencée
- ansible : suppression effective des templates daily timer (cleanup b9be63c)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:11:53 +02:00
ddff71a0fb chore(vault+ansible): clé API n8n dans vault + cleanup rôle hermes_auto_improve
- vault.yml : ajout vault_n8n_api_key (clé API n8n "claude", label n8n UI)
- hermes_auto_improve tasks : retirer déploiement timer/service daily (scheduling via n8n)
- hermes_auto_improve handlers : retirer handler Reload systemd daemon (inutilisé)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 14:01:46 +02:00
b9be63cd59 refactor(hermes-auto-improve): scheduling via n8n — supprimer timer systemd
Le scheduling est géré par n8n (workflow "Hermes Doc Worker — Funk-lab") :
- Schedule 30min désactivé dans n8n
- Schedule 22h → POST /trigger/all (tous les docs, timeout 2h)
- Attendre run 5min → 120min dans le workflow n8n

Ansible : retirer les templates daily service/timer (plus nécessaires)
auto-improve.py : flag --all conservé (utilisé par le trigger /trigger/all)
trigger-server.py : timeout 7200s conservé pour les runs --all

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 13:58:00 +02:00
7d02efd9fa feat(hermes-auto-improve): passe quotidienne complète à 22h (tous les docs)
- Ajout systemd timer hermes-auto-improve-daily.timer (22h00, Persistent=true)
- Service oneshot : --all puis --daily-pr (TimeoutSec=7200 pour 28 fichiers)
- auto-improve.py : flag --all qui bypass MAX_FILES et traite tout admin/
- trigger-server.py : timeout 600s → 7200s pour les runs --all via HTTP

La rotation par batch (MAX_FILES=5) reste active pour les triggers n8n
à la demande. Le workflow n8n 30-min doit être désactivé manuellement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 13:46:14 +02:00
alkatrazz
5d76ff1f4b fix(nftables): ajouter port 9095 dans le template gateway, supprimer lineinfile cassé
Les règles ajoutées par lineinfile se retrouvaient hors du bloc table/chain.
Port 9095 (hermes-auto-improve) maintenant géré proprement dans le template gateway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:25:24 +02:00
alkatrazz
3f9772c9d1 fix(hermes-auto-improve): ajouter HOME=/root au service pour que gh trouve ses credentials
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:17:20 +02:00
alkatrazz
cf2c8ecc81 feat(hermes-auto-improve): remplacer API urllib par gh CLI pour les PR
Plus besoin de GITHUB_TOKEN dans le vault ni dans le service systemd.
gh auth login root une seule fois sur s01 suffit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 00:14:09 +02:00
alkatrazz
4e0b683cbd fix(auto-improve): corriger chemin src Ansible (playbook_dir remonte 2 niveaux) 2026-06-02 23:59:38 +02:00
alkatrazz
4a7a5ac9fc feat(auto-improve): workflow Hermes d'analyse et amélioration de la doc
Pipeline complet dans feature/hermes-auto-improve :
- auto-improve.py : analyse admin/ avec hermes --profile funk-ai, applique
  les améliorations, committe dans la branche, ré-ingère dans Qdrant
- trigger-server.py : HTTP 9095 (POST /trigger, /trigger/all, /trigger/subdir,
  /trigger/dry-run) pour déclenchement depuis n8n ou manuellement
- Ansible role hermes_auto_improve : déploie scripts + service systemd sur s01
- n8n workflow "Hermes Auto-Improve" (ID: CX72UUOempE1YtNL) : schedule
  hebdo dimanche 09h + webhook manuel → analyse → résumé LLM → email

Tout reste dans cette branche — rien ne touche main avant review.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 23:53:53 +02:00
alkatrazz
91e648f55e feat(monitoring): métriques conso électrique CPU+GPU (RAPL + ROCm power)
- node_exporter : activer --collector.rapl (CPU power package, storage-01 + gpu-01 + nœuds k8s)
- rocm_scraper : ajouter rocm_gpu_power_watts + rocm_gpu_power_cap_watts via sysfs hwmon
- gpu-01 : activer rocm_exporter_enabled
- kube-prometheus-stack : propager --collector.rapl aux DaemonSet Talos via prometheus-node-exporter.extraArgs

Déployé sur storage-01 et gpu-01. ArgoCD prend en charge les nœuds Talos.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 22:54:51 +02:00
alkatrazz
a3ba3089e2 feat(voice): améliorer hermes-voice + fix hermes-agent
hermes-voice :
- mode SSH : appel `hermes -z` via storage-01 (soul + skills + RAG + mémoire)
- mode litellm : fallback avec historique de session (10 échanges)
- Whisper large-v3 : meilleure reconnaissance FR (distil-large-v3 dérivait vers l'anglais)
- forcer français dans les requêtes SSH
- historique conversationnel + reset vocal ("oublie tout")

hermes-agent :
- fix boucle de redémarrage (510 restarts) : ajouter --replace à ExecStart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 16:24:21 +02:00
alkatrazz
6fc5b5984e feat(voice): ajouter client vocal Hermes (hermes-voice)
- tools/hermes-voice/ : pipeline VAD → faster-whisper → LiteLLM → piper TTS
- mot-clé "hermes" détecté dans la transcription Whisper (pas de wake word model)
- modes : VAD continu + push-to-talk + --no-tts pour debug
- nftables : ouvrir port 4000 LiteLLM vers LAN domestique (192.168.1.0/24)
- admin/ia/hermes-voice.md : documentation installation et utilisation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 15:12:44 +02:00
alkatrazz
144c522096 chore: mettre à jour vault Gmail + RAG 339 chunks
- vault: App Password Gmail mis à jour pour postfix_relay
- CLAUDE.md: RAG 284 → 339 chunks (ajout docs n8n + open-webui)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:17:08 +02:00
alkatrazz
e644c1ae1b fix(nftables): ouvrir port 25 SMTP pour pods k8s (10.42/16)
n8n tourne dans un pod k8s — le port 25 de postfix n'était ouvert
que pour 192.168.10.0/24 (nœuds cluster), pas pour 10.42.0.0/16
(pods). Connexion ETIMEDOUT au nœud Send Email du workflow alertes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:27:08 +02:00
alkatrazz
8c94936467 fix(postfix): basculer relay de Brevo vers Gmail SMTP
Brevo rejetait les mails envoyés depuis aliyesilkaya93@gmail.com
(domaine non vérifié côté Brevo). Gmail SMTP signe lui-même les mails
→ livraison garantie vers le compte Gmail destinataire.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:13:24 +02:00
alkatrazz
ad49b129a1 fix(litellm): exposer port 4000 au cluster k8s
LiteLLM écoutait sur 127.0.0.1 uniquement — les pods k8s (10.42.x.x)
ne pouvaient pas atteindre 192.168.10.1:4000, Open WebUI affichait
"Aucun modèle disponible". Binding changé en 0.0.0.0 + nftables ouvert
pour 192.168.10.0/24 et 10.42.0.0/16.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 16:15:37 +02:00
alkatrazz
f9609e48dd fix(k8s/open-webui): memory 1Gi→2Gi (OOMKill HuggingFace download au boot)
fix(ansible/postgresql): GRANT ALL ON SCHEMA public (PostgreSQL 15+ default)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:42:54 +02:00
alkatrazz
bdd36d7188 fix(ansible/postgresql): no_log sur Grant + alias db déprécié → database
- Grant user privileges : ajout no_log:true (mot de passe visible en clair
  dans les logs Ansible en cas d'échec via item loop)
- Alias 'db' déprécié par community.postgresql → renommé 'database'
- litellm DB recrée manuellement (corruption pg_attribute copiée depuis RAID)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:25:42 +02:00
alkatrazz
77c4fe99c2 fix(ansible/nfs_server): supprimer le montage RAID — /srv/data est NVMe bind-mount
La tâche "Mount RAID5 array (fstab + immediate)" écrasait l'entrée fstab
bind-mount (/home/data → /srv/data) et remontait le RAID à chaque apply.

- Suppression de ansible.posix.mount state=mounted pour le RAID
- La tâche mdadm.conf devient conditionnelle (when: stdout non vide)
  et failed_when: false pour ne pas bloquer si le RAID n'est pas assemblé
- Ajout d'un sanity-check : vérifie que /srv/data est un répertoire
- Commentaires explicatifs dans tasks/ et defaults/ pour prévenir toute
  régression future

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:21:26 +02:00
alkatrazz
bc137d13b4 feat(k8s): déployer Open WebUI + n8n sur namespace ai via ArgoCD
Open WebUI (openwebui.lab.local) :
- Connecté à LiteLLM (192.168.10.1:4000/v1) comme backend OpenAI-compatible
- PostgreSQL via storage-01 pour l'historique des conversations
- PVC 5Gi NFS pour les uploads, Traefik IngressRoute

n8n (n8n.lab.local) :
- PostgreSQL via storage-01, chiffrement des credentials
- PVC 2Gi NFS, Traefik IngressRoute
- Webhook URL interne : http://n8n.lab.local

Ansible : ajoute openwebui et n8n dans postgresql_databases/users
(vault_pg_openwebui_password + vault_pg_n8n_password à ajouter au vault)

Prérequis avant 1er déploiement :
  make vault-edit   # ajouter les 2 passwords
  make apply-storage --tags postgresql
  kubectl create secret generic open-webui-secret -n ai ...
  kubectl create secret generic n8n-secret -n ai ...

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:08:41 +02:00
alkatrazz
8da92cb721 feat(postfix): relay SMTP Gmail + suppression règles Conan nftables
- postfix_relay: switch Brevo → Gmail SMTP (smtp.gmail.com:587, App Password)
- smtp_generic_maps: réécriture expéditeur root@lab.localaliyesilkaya93@gmail.com
- nftables: suppression DNAT/NAT Conan Exiles (ports 7777/7778/27015)
- gateway handler: reloaded → restarted (flush complet des règles nft)
- hermes_agent: pip → uv pour installation qdrant-client (venv sans pip binaire)
- CLAUDE.md: corrections funk-cluster, hermes-switch, RAID5, postfix
- docs: admin/infra/email.md — procédure complète relay Gmail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:46:42 +02:00
alkatrazz
eae6e76645 feat(rag): RAG Hermes — Qdrant + rag-ingest/rag-query + skill funk-ai
Indexation de admin/ (284 chunks) dans Qdrant via embeddings Qwen3-8B.
rag-query utilisable en CLI et depuis le profil funk-ai de Hermes.
Note: modèle d'embedding générique — qualité limitée, voir admin/ia/rag.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 22:16:06 +02:00
alkatrazz
3a013834aa fix(webhook): réponse HTTP 200 immédiate + ask-agent asynchrone
BrokenPipeError corrigé : send_response(200) envoyé avant subprocess.
ask-agent lancé en threading.Thread daemon — AlertManager n'attend plus.
NO_COLOR=1 + TERM=dumb pour éviter les codes ANSI dans les logs.

docs: alertmanager-webhook.md — architecture, flux, test, dépannage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 21:58:39 +02:00
alkatrazz
c97e895b87 fix(nftables): ouvre port 9093 pour AlertManager webhook → Hermes monitor
Les pods AlertManager (10.42.0.0/16) ne pouvaient pas joindre le webhook
sur storage-01:9093 — alertes Prometheus reçues mais jamais transmises à
ask-agent monitor. Port 9093 ajouté pour pods k8s + nœuds cluster.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 21:37:48 +02:00
alkatrazz
0498d770f2 fix(monitoring): dashboard infra nodata — nftables pod CIDR + mountpoint
nftables.conf.j2 :
  - Ajoute 10.42.0.0/16 (CIDR pods k8s) pour port 9100 (node_exporter)
    et port 8080 (hermes webhook AlertManager)
  - Appliqué live sur storage-01 + persisté dans /etc/sysconfig/nftables.conf
  - storage-01 était up=0 (timeout) car pods Prometheus ne pouvaient pas
    l'atteindre malgré node_exporter actif

dashboard-infrastructure.yaml :
  - Corrige mountpoint /srv → /srv/data (RAID5 monté sur /srv/data)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 18:42:11 +02:00
alkatrazz
f937c2589d fix(funk-cluster): grep -c bug + uncordon automatique au démarrage
- check_k8s_pods : grep -c retournait "0\n0" (|| echo 0 inutile quand
  grep-c affiche 0 même en cas d'absence de match) — utilise un cache
  intermédiaire pods_output + running=$(grep -c) || running=0
- cmd_start : uncordon automatique si nœuds SchedulingDisabled détectés
  (séquelle d'un funk-stop --k8s), suivi d'une attente pod infra avant
  le check — évite de devoir passer --k8s juste pour débloquer le cluster

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:57:12 +02:00
alkatrazz
494184b025 feat(funk-cluster): check k8s pod health in funk-start et funk-status
Ajoute check_k8s_pods / check_k8s_workloads (argocd, infra, monitoring).
funk-start vérifie désormais les pods k8s si les nœuds sont joignables,
même sans --k8s. En mode --k8s : attente ArgoCD + infra avant le check.
funk-status montre aussi l'état des pods si cluster up.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:54:18 +02:00
alkatrazz
a4a5fb2383 fix(funk-cluster): SIGKILL process fils hermes avant umount /srv/data
hermes-agent spawne des process Python qui survivent hors du cgroup
systemd après stop_service. Ils gardent des file descriptors ouverts sur
/srv/data (logs, state.db-shm) et bloquent le umount.

Ajout d'un lsof + kill -9 sur tous les process tenant /srv/data avant
sync + umount — garantit un démontage propre sans dépendre de systemd.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 01:09:53 +02:00
alkatrazz
0c37635802 fix(funk-cluster): umount propre /srv/data avant extinction storage-01
Avant, shutdown -h now était appelé directement après stop qdrant/postgresql.
systemd démontait /srv/data pendant la séquence d'extinction sans garantie
que tous les buffers aient été flushés.

Séquence ajoutée avant shutdown :
  1. stop nfs-server (était oublié dans le script)
  2. sync — flush tous les buffers disque
  3. umount /srv/data — démontage explicite du RAID5
  Si umount échoue, lsof affiche le processus bloquant avant d'abandonner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 01:06:53 +02:00
alkatrazz
83c10feae7 fix: monitoring — collecte métriques externes (nftables, llama --metrics, ROCm via 9100)
- nftables storage-01 : ports 9100 (node_exporter) + 9093 (webhook) ouverts pour 192.168.10.0/24
- llama-server : ajout --metrics pour exposer /metrics sur les 3 instances
- values.yaml : suppression job gpu-01-rocm:9101 inexistant (ROCm passe par node_exporter 9100 textfile_collector)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 22:41:19 +02:00
alkatrazz
c9fd0d2812 feat: funk-cluster start --k8s — uncordon + attente ArgoCD
Après funk-stop --k8s, les nœuds reviennent Ready mais SchedulingDisabled
(cordon stocké dans etcd). Sans uncordon, tous les pods restent Pending.

Ajouts dans funk-start --k8s :
- kubectl uncordon sur les nœuds SchedulingDisabled uniquement (idempotent)
- Attente ArgoCD Running (jusqu'à 3 min) — confirme que le GitOps est opérationnel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 22:22:12 +02:00
alkatrazz
a631a4ab6c feat: funk-cluster — arrêt k8s propre avec drain + vérification NFS
Remplace le talosctl shutdown immédiat par une séquence en 6 étapes :
1. kubectl cordon tous les nœuds (empêche rescheduling)
2. kubectl drain compute-02/03 (SIGTERM pods, 30s grace, PVCs NFS libérés)
3. kubectl drain compute-01 (ArgoCD, system pods)
4. Vérification NFS : ss confirme plus de sessions :2049 actives
5. talosctl shutdown (halt propre, plus rien ne tourne)
6. Attente extinction ping

Prometheus et Grafana ont 30s pour flusher WAL/SQLite avant terminaison.
storage-01 (NFS server) est éteint en dernier une fois la vérification passée.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 22:20:14 +02:00