Relevé live 2026-06-23 → mise à jour des écarts depuis le 21/06 : - admin/ops/etat-cluster.md : SearXNG ajouté (ns ai deploy/svc/IngressRoute, Application ArgoCD), date + note « STT devenu agentique ». - CLAUDE.md : date d'état → 23/06 ; bullet STT réécrit (Asa agentique : contexte asa par défaut, function calling, état live enrichi GPU, web_search, admin_action, TTS Piper|Kokoro) ; nouveau bullet SearXNG ; apps-of-apps + apps + namespaces incluent searxng. - admin/ia/stt.md : corrige les mentions « agir via Hermes = à venir » (désormais FAIT via admin_action/hermes-exec) ; bandeau daté complété (21→23/06). (Re-ingestion RAG à faire après merge.) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
18 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Présentation du projet
Funk — homelab IA composé d'un cluster Kubernetes (Talos) pour les services applicatifs, d'un hôte d'inférence LLM dédié (gpu-01 / llama-server ROCm), et d'un agent autonome (Hermes Agent) sur storage-01. Stack IaC complète : Ansible + talhelper + ArgoCD.
État actuel (2026-06-23) :
- ✅ Hermes Agent opérationnel — multi-profils (funk-ai/system/monitor/brain)
- ✅ Qdrant réparé (2026-06-17) — fin du crash-loop (segment
funk-docscorrompu, voiradmin/incidents.md). La collection RAGfunk-docsa depuis été reconstruite (436 chunks, 🟢 green, embeddings nomic-embed-text dim 768) —rag-queryopérationnel - ✅ Monitoring complet — Prometheus + Grafana + AlertManager → webhook Hermes + n8n
- ✅ storage-01 installé —
192.168.10.1(LAN cluster),192.168.1.200(WAN) - ✅ gpu-01 installé — llama-server GPU (:1234, qwen3-8b) + embeddings dédiés nomic-embed-text (:1238, CPU — découplé du slot chat GPU) + watchdog/heartbeat de résilience, kernel 5.14.0-687.5.3. ⚠️ Les instances CPU (:1236/:1237 — profils Hermes
system/monitor) sont manuelles et souvent éteintes (gap IaC) - ✅ LiteLLM sur storage-01 — proxy multi-modèles + ask-agent (écoute
0.0.0.0:4000) - ✅ Cluster k8s Talos opérationnel (MetalLB + Traefik + wildcard DNS lab.local)
- ✅ postfix_relay — SMTP sortant via Gmail SMTP + App Password (aliyesilkaya93@gmail.com)
- ✅ Open WebUI opérationnel —
openwebui.lab.local, namespaceai, backend LiteLLM - ✅ n8n opérationnel —
n8n.lab.local, namespaceai, 3 workflows actifs (alertes, rapport horaire, Hermes Doc Worker) - ✅ STT / Asa (assistant vocal « Jarvis ») — STT-server in-cluster (
stt.lab.local, namespaceai) + clientstt(pipx) : HUD avancé, transcription partielle live, veille/réveil vocal (« Asa » / « Asa stop »), auto-start systemd--user, mémoire long-terme Qdrantstt-memory. Asa est agentique (2026-06-22) : contexteasapar défaut = boucle d'outils (function calling, Qwen3-8B local) où le modèle choisit ses outils —search_docs(RAG),host_health/cluster_status/prometheus_query(état live enrichi : CPU/RAM/disque/temp/uptime + GPU via rocm_scraper),web_search(SearXNG), etadmin_action(agir viahermes-exec, confirmation vocale obligatoire + jeton). Contextes présélectionnables conservés (funk/ghostfolio/grafana/alerting/cluster) + visualiseur + intents vocaux. TTS enfichable (piper|kokoro) :stt --voices/--install-voice/--install-kokoro. Voiradmin/ia/stt.mdetdocs/stt-presentation.md - ✅ SearXNG opérationnel —
searxng.lab.local, namespaceai, méta-moteur de recherche web self-host (privé, in-cluster) consommé par l'outilweb_searchd'Asa (API JSON). Manifestsk8s/apps/searxng/ - ✅ Ghostfolio opérationnel —
ghostfolio.lab.local, namespaceai, suivi de portefeuille (PostgreSQL s01 + Redis in-cluster) - ✅ hermes-auto-improve — analyse quotidienne de
admin/par Hermes à 22h (n8n → trigger :9095), PR surhermes/daily-work - ⏳ WOL compute nodes non configuré (BIOS) — allumage manuel requis
Journal de progression : PROGRESS.md → progress/YYYY-MM-DD.md
Architecture des machines
| Machine | IP | OS | Rôle |
|---|---|---|---|
| storage-01 | 192.168.10.1 / 192.168.1.200 | AlmaLinux 9.8 | Passerelle + bastion admin + données + Hermes Agent — hors cluster |
| compute-01 | 192.168.10.11 | Talos v1.13 (k8s v1.33.1) | k8s control-plane |
| compute-02 | 192.168.10.12 | Talos v1.13 (k8s v1.33.1) | k8s worker |
| compute-03 | 192.168.10.13 | Talos v1.13 (k8s v1.33.1) | k8s worker |
| gpu-01 | 192.168.10.20 | AlmaLinux 9.8 | llama-server dédié (RX 6700XT, ROCm 7.x) — hors cluster |
📸 État live vérifié (services, ports, workloads, bases, RAG) :
admin/ops/etat-cluster.md(snapshot 2026-06-21).
Points clés d'architecture :
storage-01etgpu-01sont hors du cluster k8s — consommés viaExternalNameservices ouEndpointsmanuels- Single control-plane assumé : si compute-01 tombe,
kubectl applyne fonctionne plus mais les workloads continuent - Modèles LLM sur storage-01 (
/srv/data/models, NVMe depuis 2026-05-29), montés sur gpu-01 via NFS (/mnt/models)
Stack IaC — trois outils, trois cibles
| Outil | Cible | Déclenchement |
|---|---|---|
| Ansible | storage-01, gpu-01 (AlmaLinux) | Push manuel : make apply-storage / make apply-gpu |
| talhelper | compute-01/02/03 (Talos) | Push manuel : talosctl apply-config |
| ArgoCD | Workloads k8s | Pull automatique depuis Git |
Ansible
Commandes via Makefile (depuis la racine du repo)
python3 -m venv .venv # créer le venv (une seule fois, avant make install)
make install # pip install + ansible-galaxy (à faire une fois)
make ping # vérifier la connectivité vers tous les hôtes
make check # dry-run site.yml (storage-01 + gpu-01)
make apply # appliquer site.yml complet
make apply-storage # appliquer uniquement storage-01.yml
make apply-gpu # appliquer uniquement gpu-01.yml
make vault-view # lire le vault chiffré
make vault-edit # éditer le vault (ouvre $EDITOR)
make vault-encrypt # (ré)chiffrer vault.yml
Commandes directes (depuis ansible/)
ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags litellm
ansible-playbook -i inventory.yml playbooks/gpu-01.yml --tags llama_server
Les tags correspondent aux noms de rôles. Tous les rôles sont idempotents.
Inventaire et connectivité
storage-01: accès direct SSH via192.168.1.200(WAN)gpu-01: accès via ProxyJump surstorage-01(configuré dansinventory.yml)- Vault password dans
.vault_pass(ignoré par git, chargé automatiquement viaansible.cfg)
Rôles — storage-01
hermes_user, common, gateway (nftables + NAT), dnsmasq, nfs_server, qdrant, postgresql, minio (⚠️ rôle vide, service non déployé), litellm, hermes_agent, rag, node_exporter, alertmanager_webhook, postfix_relay, hermes_auto_improve (worker doc Hermes + trigger server :9095), hermes_exec (exécuteur d'actions vocales hermes -z --yolo derrière jeton, :9096)
Rôles — gpu-01
hermes_user, common, rocm, nfs_client, llama_server, node_exporter
Chaque rôle a un
README.md(variables, caveats, gaps IaC).lm_studioest présent dansansible/roles/mais n'est plus utilisé dans aucun playbook (déprécié, voir son README). ⚠️ Gap IaC : les instances llama-server CPU:1236/:1237sur gpu-01 sont des services manuels, non gérés par le rôlellama_server(voiransible/roles/llama_server/README.md).
Caveat premier déploiement — hermes_agent
Le rôle hermes_agent installe le binaire via curl | bash seulement si le binaire est absent. La configuration (.env, config.yaml) est ensuite conditionnelle à hermes_binary.stat.exists. Sur un hôte vierge, il faut deux runs consécutifs : le premier installe, le second configure.
Variables par hôte
ansible/host_vars/<hostname>/vars.yml contient les variables spécifiques à chaque machine :
storage-01: UUID RAID5, exports NFS, chemins des services sous/srv/datagpu-01: chemin du modèle GGUF (llama_model_path), alias modèle,llama_ctx_size, montages NFS
Variables globales (non-secrètes) dans ansible/group_vars/all/vars.yml : admin_user, dns_domain, ntp_servers, cluster_network, wan_gateway.
Secrets Ansible Vault
Variables chiffrées dans ansible/group_vars/all/vault.yml :
vault_anthropic_api_key— clé Anthropic pour le profil brainvault_pg_hermes_password,vault_pg_litellm_password,vault_pg_n8n_password,vault_pg_openwebui_password,vault_pg_ghostfolio_passwordvault_postfix_relay_password— App Password Gmail (relay SMTP)vault_n8n_api_key— clé API n8n (label "claude") pour gérer les workflows via/api/v1vault_hermes_exec_token— jeton d'auth dehermes-exec(actions vocales via Hermes ; même valeur dans le secret k8sstt-server-secrets/hermes-exec-token)
talhelper (nœuds Talos)
cd talos/
talhelper genconfig # régénérer configs nœuds
talhelper gencommand apply --node compute-02 | bash # appliquer sur un nœud
talhelper gencommand bootstrap --node compute-01 | bash # bootstrap (1ère fois seulement)
talhelper gencommand kubeconfig | bash # récupérer kubeconfig
talos/clusterconfig/ est généré — gitignored, ne pas committer.
talos/talsecret.sops.yaml chiffré avec SOPS+age (clé dans .config/sops/, ignorée par git).
ArgoCD (workloads k8s)
Pattern "App of Apps" : modifier un manifest → git commit + git push → ArgoCD poll automatiquement (~3 min).
git add k8s/apps/open-webui/
git commit -m "open-webui: bump to v0.4.0"
git push
ArgoCD utilise le repo git@github.com:Alkatrazz24/Funk-lab.git branche main.
Structure k8s/
k8s/
├── apps-of-apps/
│ ├── root.yaml # Application ArgoCD racine (pointe vers apps-of-apps/apps/)
│ └── apps/ # Un fichier Application par workload — ArgoCD les crée automatiquement
│ ├── n8n.yaml
│ ├── open-webui.yaml
│ ├── stt.yaml
│ ├── searxng.yaml
│ ├── ghostfolio.yaml
│ ├── monitoring.yaml
│ └── nfs-provisioner.yaml
├── apps/ # Manifests raw k8s par application
│ ├── n8n/ # namespace: ai
│ ├── open-webui/ # namespace: ai
│ ├── stt/ # namespace: ai — STT-server (assistant vocal Asa, stt.lab.local)
│ ├── searxng/ # namespace: ai — méta-moteur recherche web (outil web_search d'Asa)
│ ├── ghostfolio/ # namespace: ai — suivi de portefeuille (ghostfolio.lab.local)
│ └── {external-services}/ # ExternalName/Endpoints pointant vers storage-01 ou gpu-01
└── infra/ # Helm releases (HelmRelease + values.yaml)
├── monitoring/ # kube-prometheus-stack 85.0.2
└── nfs-provisioner/
# ⚠️ Traefik + MetalLB ne sont PAS dans k8s/infra/ : ils sont installés via `helm install`
# lors du bootstrap Talos (voir admin/k8s/talos.md), hors pattern App-of-Apps ArgoCD.
Ajouter un nouveau workload k8s
- Créer
k8s/apps/<nom>/avec les manifests (Deployment, Service, Ingress, PVC…) - Créer
k8s/apps-of-apps/apps/<nom>.yaml(ArgoCD Application CRD) — copiern8n.yamlcomme modèle git commit+git push→ ArgoCD prend en charge automatiquement
Namespaces actifs : argocd, infra (Traefik, MetalLB, NFS-provisioner, registry in-cluster), monitoring (Prometheus/Grafana), ai (n8n, open-webui, stt, searxng, ghostfolio), sacrifice (jeu FPS — déployé hors de ce repo et hors ArgoCD, cf. admin/ops/etat-cluster.md).
Les services du namespace
aisont exposés via des IngressRoute Traefik (CRD), pas desIngressstandards.
Stack IA — Hermes Agent
Profils
| Profil | Modèle | Port | Usage |
|---|---|---|---|
funk-ai |
Qwen3-8B Q4_K_M | GPU :1234 | Agent principal (~35 tok/s) |
system |
Qwen3-1.7B Q4_K_M | CPU :1236 | Calculs, formatage, résumés |
monitor |
Qwen3-1.7B Q4_K_M | CPU :1237 | Analyse logs, métriques, état services |
brain |
Claude Sonnet 4.6 API | Anthropic | Raisonnement complexe — facturé |
Hermes écoute sur le port 8080 (gateway). Dashboard web sur le port 9119 (accessible uniquement depuis 192.168.1.10).
Switcher le modèle de Hermes à chaud
hermes-switch status # modèle actuel de hermes-default
hermes-switch qwen # → Qwen3-8B sur gpu-01 (local, gratuit)
hermes-switch claude # → Claude Sonnet 4.6 via API Anthropic (facturé)
hermes-switch modifie /etc/litellm/config.yaml puis redémarre litellm. L'alias hermes-default dans LiteLLM est ce que Hermes consomme — changer cet alias ne nécessite pas de reconfigurer Hermes.
ask-agent (délégation inter-agents)
Binaire dans /usr/local/bin/ask-agent sur storage-01. Appel LiteLLM direct (:4000), réponse en ~2-5s.
Règle : ask-agent reçoit du texte, jamais des commandes shell. Toujours 2 étapes :
# Étape 1 — collecter les données
journalctl -u litellm -n 20 --no-pager --output=cat
# Étape 2 — passer le texte à ask-agent
ask-agent monitor "logs litellm : <résultat étape 1> — erreurs ?"
RAG — Documentation Funk
✅
funk-docsreconstruite et opérationnelle (relevé 2026-06-21 : 436 chunks, 🟢 green). Le warning « collection supprimée le 2026-06-17, re-ingest requis » est périmé : elle a été ré-indexée depuis. Qdrant et la mémoire long-terme STT (stt-memory) sont opérationnels.
Qdrant collection funk-docs indexée depuis admin/ (436 chunks, embeddings nomic-embed-text :1238 dim 768 — plus qwen3-8b ; score min ajusté pour nomic dans rag-query).
rag-query "comment relancer dnsmasq ?"
rag-query "nftables port cluster configuration"
rag-query "hermes profils litellm modèles" --top 3
Scripts installés sur storage-01 : /usr/local/bin/rag-query, /usr/local/bin/rag-ingest
Après modification de admin/, re-déployer via la commande directe (voir section Ansible) :
ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags rag
Le script admin/index_knowledge.py est l'ingesteur Python brut.
Hermes Skills — hermes-skills/
Versionnés dans ce repo, déployés par le rôle hermes_agent vers ~hermes/.
hermes-skills/
├── funk/
│ ├── agent-delegation/SKILL.md # usage de ask-agent (2 étapes, agents dispo)
│ └── rag-docs/SKILL.md # usage de rag-query
└── souls/ # prompts système par profil
├── funk-ai.md # profil principal (agent sur storage-01)
├── system.md # profil calculs/formatage
├── monitor.md # profil analyse logs
└── brain.md # profil Claude API
hermes-auto-improve — analyse quotidienne de la doc
Worker qui fait analyser admin/ par Hermes et pousse les rapports sur la branche hermes/daily-work (PR squash-only vers main). Source dans tools/hermes-auto-improve/, déployé par le rôle hermes_auto_improve :
/usr/local/bin/hermes-auto-improve— script d'analyse (--all= tous les docs, sinon batch de 5 en rotation ;--daily-pr= crée/met à jour la PR GitHub)/usr/local/bin/hermes-auto-improve-server— trigger HTTP:9095(POST /trigger/all,/trigger/daily-pr,GET /status)- Scheduling : workflow n8n « Hermes Doc Worker — Funk-lab » à 22h (run complet → boucle de polling
/status→ email + PR). Pas de timer systemd. - Rapports écrits dans
admin/hermes/builtin/, état dans/var/lib/hermes-auto-improve/, log/var/log/hermes-auto-improve.log
Doc détaillée : admin/ia/hermes-auto-improve.md
Base de connaissance — admin/
Documentation admin versionnée, indexée dans le RAG :
admin/
├── ia/ # hermes, litellm, llama_server, rocm, rag, alertmanager-webhook, hermes-auto-improve
├── infra/ # dnsmasq, nfs, réseau, ssh, email
├── install/ # procédures installation storage-01, gpu-01, k8s
├── k8s/ # argocd, talos, monitoring, n8n, open-webui, k9s
├── ops/ # ansible, cluster, système
├── hermes/builtin/ # rapports générés par hermes-auto-improve (ne pas éditer)
└── incidents.md # historique des incidents matériel/service avec résolutions
Réseaux
- CNI Flannel — Pods :
10.42.0.0/16 - Services :
10.43.0.0/16 - MetalLB pool :
192.168.10.200-230(IP virtuelles Traefik/services) - DNS wildcard
*.lab.localvia dnsmasq → Traefik
nftables géré par le rôle gateway sur storage-01. Modifier ansible/roles/gateway/templates/nftables.conf.j2 puis :
ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags gateway
Contrainte GPU critique
La RX 6700XT (gfx1031) n'est pas officiellement supportée par ROCm. Variable obligatoire dans tous les services GPU :
HSA_OVERRIDE_GFX_VERSION=10.3.0
Gérée automatiquement par le rôle llama_server. ROCm tourne directement sur l'hôte AlmaLinux.
Gestion du cluster
Ces commandes sont des fonctions bash sur le poste perso (configurées dans ~/.bashrc) qui SSH sur storage-01 et invoquent /usr/local/bin/funk-cluster.
funk-status # état de tous les services (storage-01 + gpu-01 + k8s)
funk-start # WOL gpu-01 + démarrage services storage-01
funk-start --k8s # idem + nœuds k8s (WOL → drain-aware boot)
funk-stop # arrêt propre gpu-01 + services
funk-stop --k8s # idem + drain k8s → NFS check → talosctl shutdown
etcd backup (single control-plane → critique) :
talosctl etcd snapshot /path/to/backup.db --nodes compute-01
Conventions
- Git : branches
main(prod) /feature/*— conventional commits (feat:,fix:,chore:,docs:) - Secrets : Ansible Vault (Ansible), SOPS+age (Talos), Sealed Secrets (k8s)
- Namespaces k8s :
argocd,infra,monitoring,ai,external(+sacrifice, hors-repo) - Hermes data : profils/données dans
/srv/data/hermes(NVMe, bind-mount/home/data), code dans/opt/hermes/(SSD OS) - RAM compute-02/03 : toujours définir
resources.requests+resources.limits(8 GB RAM, OOM-killer actif)