# 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.

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)
- ✅ 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)
- ⏳ WOL compute nodes non configuré (BIOS) — allumage manuel requis

---

## Architecture des machines

| 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 |
| 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
- 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)
```

---

## Stack IaC — trois outils, trois cibles

| Outil | Cible | Déclenchement |
|---|---|---|
| **Ansible** | storage-01, gpu-01 (AlmaLinux) | Push manuel : `ansible-playbook` |
| **talhelper** | compute-01/02/03 (Talos) | Push manuel : `talosctl apply-config` |
| **ArgoCD** | Workloads k8s | Pull automatique depuis Git |

### Ansible

```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
```

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.

### talhelper (nœuds Talos)

```bash
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é — ne pas committer (mettre au `.gitignore`).

### ArgoCD (workloads k8s)

Pattern "App of Apps" : modifier un manifest → `git commit` + `git push` → ArgoCD applique automatiquement (poll toutes les ~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
```

---

## Réseaux internes Kubernetes

- 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`

---

## 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` :

```
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`).

---

## 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 |

```bash
# Vérifier l'état complet depuis le poste perso
funk-status
```

---

## 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
```
