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>
This commit is contained in:
alkatrazz 2026-05-17 16:46:42 +02:00
parent 24c0bf9e12
commit 8da92cb721
13 changed files with 552 additions and 145 deletions

290
CLAUDE.md
View file

@ -6,15 +6,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
**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.
Référence complète : `Funk/Funk-infra/HOMELAB.md`
**État actuel (2026-05-13)** :
- ✅ Hermes Agent opérationnel sur storage-01 (llama-server GPU via LiteLLM)
**État actuel (2026-05-17)** :
- ✅ Hermes Agent opérationnel — multi-profils (funk-ai/system/monitor/brain)
- ✅ RAG opérationnel — Qdrant 284 chunks, `rag-ingest` / `rag-query`, skill funk-ai
- ✅ Monitoring complet — Prometheus + Grafana + AlertManager → webhook Hermes
- ✅ storage-01 installé — `192.168.10.1` (LAN cluster), `192.168.1.200` (WAN)
- ✅ gpu-01 installé — llama-server GPU (:1234) + CPU (:1236/:1237) opérationnels
- ✅ LiteLLM sur storage-01 — proxy multi-modèles + ask-agent
- ✅ Cluster k8s Talos opérationnel (MetalLB + Traefik + wildcard DNS lab.local)
- ✅ postfix_relay — SMTP sortant via Brevo (aliyesilkaya93@gmail.com)
- ⏳ WOL compute nodes non configuré (BIOS) — allumage manuel requis
- ⏳ ArgoCD bootstrap + workloads IA (Open WebUI, n8n)
Journal de progression : `PROGRESS.md``progress/YYYY-MM-DD.md`
---
@ -22,42 +26,16 @@ Référence complète : `Funk/Funk-infra/HOMELAB.md`
| Machine | IP | OS | Rôle |
|---|---|---|---|
| storage-01 | 192.168.10.1 | AlmaLinux 9.7 | Passerelle + bastion admin + données + Hermes Agent — **hors cluster** |
| compute-01 | 192.168.10.11 | Talos v1.13+ | k8s control-plane |
| compute-02 | 192.168.10.12 | Talos v1.13+ | k8s worker |
| compute-03 | 192.168.10.13 | Talos v1.13+ | k8s worker |
| storage-01 | 192.168.10.1 / 192.168.1.200 | AlmaLinux 9.7 | Passerelle + bastion admin + données + Hermes Agent — **hors cluster** |
| compute-01 | 192.168.10.11 | Talos v1.13 | k8s control-plane |
| compute-02 | 192.168.10.12 | Talos v1.13 | k8s worker |
| compute-03 | 192.168.10.13 | Talos v1.13 | k8s worker |
| gpu-01 | 192.168.10.20 | AlmaLinux 9.7 | llama-server dédié (RX 6700XT, ROCm 7.x) — **hors cluster** |
**Points clés d'architecture :**
- `storage-01` et `gpu-01` sont **hors du cluster k8s** — ils sont consommés via des `ExternalName` services ou `Endpoints` manuels
- Le cluster n'a que 3 nœuds (compute-01/02/03), pas de GPU dans k8s
- `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
---
## Structure du repo lab-infra (cible)
```
lab-infra/
├── ansible/ # gère storage-01 + gpu-01 (AlmaLinux)
│ ├── inventory.yml
│ ├── playbooks/site.yml
│ ├── group_vars/
│ └── roles/ # common, gateway, dnsmasq, nfs_server, postgresql,
│ # qdrant, minio, rocm, llama_server, litellm, hermes_agent, hermes_user
├── talos/ # nœuds Talos via talhelper
│ ├── talconfig.yaml # source de vérité (lue par talhelper)
│ ├── talsecret.sops.yaml
│ ├── talenv.yaml
│ ├── patches/
│ └── clusterconfig/ # généré → au .gitignore, ne pas committer
└── k8s/ # workloads k8s (ArgoCD)
├── argocd-bootstrap/
├── apps-of-apps/root.yaml
├── infra/ # metallb, traefik, cert-manager, longhorn, nfs-provisioner
└── apps/ # litellm, open-webui, n8n, monitoring,
# external-services (ExternalName vers storage-01/gpu-01)
```
- Modèles LLM stockés sur NFS RAID5 de storage-01 (`/srv/data/models`), montés sur gpu-01 (`/mnt/models`)
---
@ -65,22 +43,67 @@ lab-infra/
| Outil | Cible | Déclenchement |
|---|---|---|
| **Ansible** | storage-01, gpu-01 (AlmaLinux) | Push manuel : `ansible-playbook` |
| **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
---
## Ansible
### Commandes via Makefile (depuis la racine du repo)
```bash
cd ansible/
ansible-playbook -i inventory.yml playbooks/site.yml --check # dry-run
ansible-playbook -i inventory.yml playbooks/site.yml # apply
ansible-playbook -i inventory.yml playbooks/site.yml --tags rocm # un rôle
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
```
Rôles : `common`, `gateway`, `dnsmasq`, `nfs_server`, `nfs_client`, `postgresql`, `qdrant`, `minio`, `rocm`, `llama_server`, `litellm`, `hermes_agent`, `hermes_user`. Tous idempotents. Secrets via Ansible Vault.
### Commandes directes (depuis `ansible/`)
### talhelper (nœuds Talos)
```bash
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`, `litellm`, `hermes_agent`, `rag`, `node_exporter`, `alertmanager_webhook`, `postfix_relay`
### Rôles — gpu-01
`hermes_user`, `common`, `rocm`, `nfs_client`, `llama_server`, `node_exporter`
> `lm_studio` est présent dans `ansible/roles/` mais n'est plus utilisé dans aucun playbook.
### 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.
### 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_postfix_relay_password` — SMTP Brevo
---
## talhelper (nœuds Talos)
```bash
cd talos/
@ -90,102 +113,151 @@ talhelper gencommand bootstrap --node compute-01 | bash # bootstrap (1ère fo
talhelper gencommand kubeconfig | bash # récupérer kubeconfig
```
`talos/clusterconfig/` est généré — ne pas committer (mettre au `.gitignore`).
`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 applique automatiquement (poll toutes les ~3 min).
## ArgoCD (workloads k8s)
Pattern "App of Apps" : modifier un manifest → `git commit` + `git push` → ArgoCD poll automatiquement (~3 min).
```bash
vim k8s/apps/open-webui/deployment.yaml
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`. Les deux namespaces gérés actuellement : `infra` (Traefik, MetalLB, NFS-provisioner) et `monitoring` (kube-prometheus-stack 85.0.2).
---
## Réseaux internes Kubernetes
## Stack IA — Hermes Agent
- CNI (Flannel) — Pods : `10.42.0.0/16`
### 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
```bash
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 :
```bash
# É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
Qdrant collection `funk-docs` indexée depuis `admin/` (284 chunks, score min 0.60).
```bash
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 `make apply-storage --tags rag` (exécute `rag-ingest`). 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
```
---
## Base de connaissance — `admin/`
Documentation admin versionnée, indexée dans le RAG :
```
admin/
├── ia/ # hermes, litellm, llama_server, rocm, rag, alertmanager-webhook
├── infra/ # dnsmasq, nfs, réseau, ssh, email
├── install/ # procédures installation storage-01, gpu-01, k8s
├── k8s/ # argocd, talos, monitoring
└── ops/ # ansible, cluster, système
```
---
## Réseaux
- CNI Flannel — Pods : `10.42.0.0/16`
- Services : `10.43.0.0/16`
- MetalLB pool (IP virtuelles Traefik/services) : `192.168.10.200-230`
- 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 `make apply-storage --tags gateway`.
---
## Contrainte GPU critique
La RX 6700XT (gfx1031) n'est **pas officiellement supportée** par ROCm. Toujours s'assurer que la variable est présente dans le service systemd `llama-server` :
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
```
ROCm tourne directement sur l'hôte AlmaLinux (pas dans des conteneurs). L'inférence est assurée par llama-server (llama.cpp compilé ROCm 7.x).
CLI `lms` disponible sur gpu-01 uniquement pour télécharger des modèles (`lms get <url-hf>`, `lms ls`).
Gérée automatiquement par le rôle `llama_server`. ROCm tourne directement sur l'hôte AlmaLinux.
---
## Deux setups IA
### Setup 1 — Poste perso (9950X3D) — dev local
| Composant | Endpoint |
|---|---|
| Ollama | `http://127.0.0.1:11434` |
| LM Studio (optionnel) | `http://127.0.0.1:1234/v1` |
| Hermes Agent | `base_url: http://127.0.0.1:1234/v1` ou Ollama |
### Setup 2 — Cluster Funk (production)
| Composant | Endpoint |
|---|---|
| llama-server GPU (gpu-01) | `http://192.168.10.20:1234/v1` |
| llama-server CPU system (gpu-01) | `http://192.168.10.20:1236/v1` |
| llama-server CPU monitor (gpu-01) | `http://192.168.10.20:1237/v1` |
| LiteLLM proxy (storage-01) | `http://127.0.0.1:4000/v1` (master_key: `lm-studio`) |
| Hermes Agent | service systemd sur storage-01, via LiteLLM |
## Gestion du cluster
```bash
# Vérifier l'état complet depuis le poste perso
funk-status
funk-cluster status # état de tous les services (storage-01 + gpu-01 + k8s)
funk-cluster start # WOL gpu-01 + démarrage services storage-01
funk-cluster start --k8s # idem + nœuds k8s (WOL → drain-aware boot)
funk-cluster stop # arrêt propre gpu-01 + services
funk-cluster stop --k8s # idem + drain k8s → NFS check → talosctl shutdown
```
**etcd backup** (single control-plane → critique) :
```bash
talosctl etcd snapshot /path/to/backup.db --nodes compute-01
```
---
## Conventions
- **Domaine** : `lab.local` — wildcard DNS via dnsmasq sur storage-01
- **Ingress** : `<service>.lab.local` → IP MetalLB de Traefik (`192.168.10.200-230`)
- **llama-server endpoint** : `http://192.168.10.20:1234/v1` (GPU) — accès via LiteLLM sur storage-01
- **Namespaces k8s** : `argocd`, `infra`, `storage`, `external`, `ai`, `workflows`
- **Git** : branches `main` (prod) / `feature/*` — commits en conventional commits (`feat:`, `fix:`, `chore:`, `docs:`)
- **Secrets** : Ansible Vault (Ansible), SOPS+age (Talos via talhelper), Sealed Secrets (k8s)
## Opérations cluster importantes
**etcd backup** (single control-plane → critique) :
```bash
talosctl etcd snapshot /path/to/backup.db --nodes compute-01
```
À planifier en cron Ansible vers le RAID5 de storage-01.
**Hermes Agent** — données persistantes dans `~/.hermes/` → doit résider sur le RAID5, pas sur le SSD OS.
**Ce qui reste toujours manuel** (GitOps ne peut pas bootstrapper lui-même) :
- Installation OS depuis USB (AlmaLinux ou Talos)
- Configuration IP/SSH minimale pour le premier Ansible
- Premier déploiement ArgoCD via Helm
---
## RAM serrée sur compute-02/03 (8 GB)
Toujours définir `resources.requests` et `resources.limits` sur les pods déployés sur ces nœuds — sans quoi l'OOM-killer peut tuer kubelet.
Réservations à configurer dans talhelper :
```
system-reserved: cpu=500m,memory=1Gi
kube-reserved: cpu=500m,memory=1Gi
```
- **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`, `external`
- **Hermes data** : `~hermes/` doit résider sur le RAID5 `/srv/data`, pas sur le SSD OS
- **RAM compute-02/03** : toujours définir `resources.requests` + `resources.limits` (8 GB RAM, OOM-killer actif)