Funk-lab/CLAUDE.md
alkatrazz 914666d71d chore(forgejo): archive le mot de passe admin break-glass au vault + doc
- vault_forgejo_admin_password (compte local forgejo-admin, accès de secours)
- doc : note DISABLE_REGISTRATION, section break-glass, su-exec git obligatoire

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:48:39 +02:00

22 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-07-07) :

  • Hermes Agent opérationnel — multi-profils (funk-ai/system/monitor/brain)
  • Qdrant réparé (2026-06-17) — fin du crash-loop (segment funk-docs corrompu, voir admin/incidents.md). La collection RAG funk-docs a depuis été reconstruite (436 chunks, 🟢 green, embeddings nomic-embed-text dim 768) — rag-query opé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, namespace ai, backend LiteLLM
  • n8n opérationnel — n8n.lab.local, namespace ai, 3 workflows actifs (alertes, rapport horaire, Hermes Doc Worker)
  • STT / Asa (assistant vocal « Jarvis ») — STT-server in-cluster (stt.lab.local, namespace ai) + client stt (pipx) : HUD avancé, transcription partielle live, veille/réveil vocal (« Asa » / « Asa stop »), auto-start systemd --user, mémoire long-terme Qdrant stt-memory. Asa est agentique (2026-06-22) : contexte asa par 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), et admin_action (agir via hermes-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. Voir admin/ia/stt.md et docs/stt-presentation.md
  • SearXNG opérationnel — searxng.lab.local, namespace ai, méta-moteur de recherche web self-host (privé, in-cluster) consommé par l'outil web_search d'Asa (API JSON). Manifests k8s/apps/searxng/
  • Ghostfolio opérationnel — ghostfolio.lab.local, namespace ai, suivi de portefeuille (PostgreSQL s01 + Redis in-cluster)
  • hermes-auto-improve — analyse quotidienne de admin/ par Hermes à 22h (n8n → trigger :9095), PR sur hermes/daily-work
  • Hub SSO Authentik + domaine public funklab.online (HTTPS) (2026-07-07) — Authentik (auth.funklab.online, IdP OIDC, ns auth) est le hub : page « My applications » → tuiles → SSO. Apps exposées en HTTPS (cert Let's Encrypt par sous-domaine, *.funklab.online) : Guacamole (portail., consoles SSH s01/g01 via user console sans sudo), Grafana (grafana., lab-admins→Admin), Open WebUI (openwebui.), Argo CD (argocd., lab-admins→admin) — tous OIDC ; n8n (n8n., login natif). Groupe transverse lab-admins = admin partout. Chaîne : DNS OVH *.funklab.online → 82.67.223.17 → Freebox 80/443 → s01 (DNAT nftables + rate-limit) → Traefik (Let's Encrypt HTTP-01, helm hors ArgoCD) ; dnsmasq split-horizon interne. Les ports historiques kaya.freeboxos.fr:9080/9081 restent un chemin réseau. Docs : admin/k8s/public-domain.md (domaine/TLS), admin/k8s/auth-portal.md (archi/pièges), admin/k8s/auth-portal-admin.md (admin : users, apps, lab-admins), docs/portail-guide-utilisateur.md (guide users)
  • 🆕 WireGuard (2026-07-06) — serveur VPN prêt sur s01 (pairs full/portal, filtrage nftables) mais dormant : pas de redirection Freebox 51820/udp (l'accès distant passe par le domaine public HTTPS). Voir admin/infra/wireguard.md
  • WOL compute nodes non configuré (BIOS) — allumage manuel requis

Journal de progression : PROGRESS.mdprogress/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-01 et gpu-01 sont hors du cluster k8s — consommés via ExternalName services ou Endpoints manuels
  • Single control-plane assumé : si compute-01 tombe, kubectl apply ne 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 via 192.168.1.200 (WAN)
  • gpu-01 : accès via ProxyJump sur storage-01 (configuré dans inventory.yml)
  • Vault password dans .vault_pass (ignoré par git, chargé automatiquement via ansible.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), console_user (user console sans sudo pour le portail Guacamole), wireguard (accès distant 51820/udp, pairs full/portal — voir admin/infra/wireguard.md)

Rôles — gpu-01

hermes_user, common, rocm, nfs_client, llama_server, node_exporter, satisfactory_server (serveur dédié Satisfactory :7777, steamcmd + systemd), console_user (idem storage-01)

Chaque rôle a un README.md (variables, caveats, gaps IaC). lm_studio est présent dans ansible/roles/ mais n'est plus utilisé dans aucun playbook (déprécié, voir son README). ⚠️ Gap IaC : les instances llama-server CPU :1236/:1237 sur gpu-01 sont des services manuels, non gérés par le rôle llama_server (voir ansible/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/data
  • gpu-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 brain
  • vault_pg_hermes_password, vault_pg_litellm_password, vault_pg_n8n_password, vault_pg_openwebui_password, vault_pg_ghostfolio_password
  • vault_postfix_relay_password — App Password Gmail (relay SMTP)
  • vault_n8n_api_key — clé API n8n (label "claude") pour gérer les workflows via /api/v1
  • vault_hermes_exec_token — jeton d'auth de hermes-exec (actions vocales via Hermes ; même valeur dans le secret k8s stt-server-secrets/hermes-exec-token)
  • vault_pg_authentik_password, vault_pg_guacamole_password — bases PG du portail (mêmes valeurs dans les secrets k8s auth/authentik-secret et auth/guacamole-secret)
  • vault_console_ssh_private_key — clé privée SSH des connexions Guacamole (user console sur s01/g01)
  • vault_authentik_secret_key, vault_authentik_redis_password — copies de sûreté des valeurs du secret k8s auth/authentik-secret
  • vault_wireguard_server_private_key, vault_wireguard_peer_private_keys — clés WireGuard (serveur + dict par pair)
  • vault_grafana_oauth_client_secret, vault_openwebui_oauth_client_secret, vault_argocd_oidc_client_secret — secrets clients OIDC des apps du hub (= secrets k8s monitoring/grafana-oauth-secret, ai/open-webui-oauth, argocd/argocd-oidc)
  • vault_pg_forgejo_password, vault_forgejo_oauth_client_secret, vault_forgejo_admin_password — Forgejo (Git self-host) : base PG forgejo (= ai/forgejo-secret), client secret OIDC Authentik, mot de passe admin local break-glass (forgejo-admin)

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
│       ├── openalice.yaml
│       ├── finlab.yaml
│       ├── forgejo.yaml
│       ├── authentik.yaml
│       ├── guacamole.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)
│   ├── openalice/           # namespace: ai — agent IA financier (openalice.lab.local, back-end finance d'Asa) — ⏸️ SUSPENDU
│   ├── finlab/              # namespace: ai — dashboard finance + console Claude Code (finance.lab.local, /console) + toolkit
│   ├── forgejo/             # namespace: ai — Git self-host, miroir GitHub (git.funklab.online, SSO)
│   ├── authentik/           # namespace: auth — annuaire/IdP OIDC (auth.lab.local)
│   ├── guacamole/           # namespace: auth — portail consoles SSH web s01/g01 (portail.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 (+ ingressroute-public.yaml grafana)
    ├── traefik/              # values.yaml (Let's Encrypt) + redirect.yaml — appliqué par helm upgrade
    └── nfs-provisioner/
# ⚠️ Traefik + MetalLB ne sont PAS gérés par ArgoCD : installés via `helm install/upgrade`
#    (Traefik : values versionnées dans k8s/infra/traefik/, cert Let's Encrypt HTTP-01 ;
#    MetalLB : bootstrap Talos). Idem ArgoCD lui-même (k8s/argocd-bootstrap/values.yaml).
#    Voir admin/k8s/talos.md et admin/k8s/public-domain.md.

Ajouter un nouveau workload k8s

  1. Créer k8s/apps/<nom>/ avec les manifests (Deployment, Service, Ingress, PVC…)
  2. Créer k8s/apps-of-apps/apps/<nom>.yaml (ArgoCD Application CRD) — copier n8n.yaml comme modèle
  3. 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, openalice, finlab, forgejo), auth (Authentik, Guacamole), sacrifice (jeu FPS — déployé hors de ce repo et hors ArgoCD, cf. admin/ops/etat-cluster.md).

Les services du namespace ai sont exposés via des IngressRoute Traefik (CRD), pas des Ingress standards.


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-docs reconstruite 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.local via 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, auth, 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)