# Homelab IA — Architecture et plan de déploiement

> Document de référence consolidé pour le déploiement d'un homelab IA combinant
> un cluster Kubernetes (Talos) pour les services applicatifs et deux hôtes
> Linux dédiés pour l'inférence LLM (LM Studio) et l'agent autonome (Hermes Agent).
> Stack IaC complète : Ansible + talhelper + ArgoCD, GitOps de bout en bout.
> À utiliser comme contexte pour Claude Code.

---

## 1. Objectifs

- Faire tourner **Hermes Agent** (Nous Research) comme agent autonome
  auto-amélioré, alimenté par **LM Studio** comme serveur d'inférence local.
- Apprendre Kubernetes en environnement réaliste (cluster multi-nœuds, GitOps)
  pour les services applicatifs (Open WebUI, n8n, monitoring...).
- Apprendre Talos Linux (OS immuable, API-only, pensé pour Kubernetes).
- Maximiser l'utilisation du GPU AMD RX 6700XT pour l'inférence LLM.
- Tout gérer en Infrastructure as Code (versionné dans Git).

## 2. Priorités (par ordre)

1. Facilité d'administration
2. Apprentissage / montée en compétences
3. Performance brute (utiliser le 6700XT au max)
4. Haute disponibilité / résilience

## 3. Vision long terme

- Cible : ~10 machines à 2-3 ans, mix Talos (cluster k8s) + AlmaLinux (services natifs)
- Tout doit être reproductible depuis Git (réinstall complète possible en quelques heures)
- BlueBanquise reste une option à reconsidérer si on dépasse 25+ hôtes Linux classiques
  ou si on ajoute du HPC/Slurm

---

## 4. Inventaire matériel

| Machine | CPU | RAM | Stockage | Réseau | Rôle |
|---|---|---|---|---|---|
| **storage-01** | AMD Ryzen 3 3200G (Zen+, 2019) | 32 GB DDR4 | 1× SSD OS + 4×1 TB RAID5 (2.7 TB exploitables) | 1× Gigabit | Hors cluster — passerelle + admin + stockage + Hermes Agent |
| **compute-01** | AMD A10-9700E (Excavator, 2016) | 16 GB DDR3 | SSD SATA | 1× Gigabit (r8169) | k8s control-plane (Talos) |
| **compute-02** | AMD A10-9700E | 8 GB DDR3 | SSD SATA | 1× Gigabit (r8169) | k8s worker (Talos) |
| **compute-03** | AMD A10-9700E | 8 GB DDR3 | SSD SATA | 1× Gigabit (r8169) | k8s worker (Talos) |
| **gpu-01** | AMD Ryzen 9 5950X (16 cœurs) | 32 GB DDR4 | NVMe 1 TB | 1× Gigabit | **Hors cluster** — hôte LM Studio dédié — RX 6700XT 12 GB |
| **switch** | Netgear GS308EV4 (8 ports Gigabit, managé Plus, VLAN 802.1Q) | — | — | — | Switch principal |

**Validation Talos déjà effectuée** : compute-01 boote correctement avec
Talos v1.13.0 en mode maintenance, IP DHCP obtenue, chipset r8169 reconnu nativement.

---

## 5. Architecture réseau

### 5.1. Topologie physique

```
   Internet
       │
   ┌───────────┐
   │  Freebox  │ 192.168.1.254 (gateway + DHCP LAN domestique)
   └─────┬─────┘
         │
         │ VLAN 1 (port 1)
         │
   ┌─────┴────────────────────────────────────────┐
   │         Switch Netgear GS308EV4              │
   │  P1  P2  P3  P4  P5  P6  P7  P8             │
   │  V1  V1  V10 V10 V10 V10 V10 V1             │
   └──┬───┬───┬───┬───┬───┬───┬───┬──────────────┘
      │   │   │   │   │   │   │   └── PC perso (192.168.1.x)
      │   │   │   │   │   │   └────── storage-01 NIC2 (192.168.10.1) ← LAN cluster
      │   │   │   │   │   └────────── compute-03 (.10.13)
      │   │   │   │   └────────────── compute-02 (.10.12)
      │   │   │   └────────────────── compute-01 (.10.11)
      │   │   └────────────────────── gpu-01 (.10.20)
      │   └────────────────────────── storage-01 NIC1 (192.168.1.200) ← WAN
      └────────────────────────────── Freebox

   ┌──────────────────────────────┐
   │  storage-01 (AlmaLinux 9.7)  │
   │   - NIC1 (port 2) : 192.168.1.200   ← WAN / LAN domestique
   │   - NIC2 (port 7) : 192.168.10.1   ← LAN cluster + gateway + NAT
   └─────┬────────────────────────┘
         │ (routage + NAT entre les deux interfaces)
         │
    ┌────┴────┬──────────┬──────────┐
    │         │          │          │
 compute-01 compute-02 compute-03 gpu-01
 .10.11    .10.12     .10.13     .10.20
 (Talos)   (Talos)    (Talos)    (AlmaLinux + LM Studio)
```

### 5.2. Vue logique des composants IA

```
┌──────────────────────────────────────────────────────────────────────┐
│                      Cluster Kubernetes (Talos)                      │
│                  3 nœuds : compute-01/02/03                          │
│                                                                      │
│   • Open WebUI          • Traefik (ingress)                          │
│   • LiteLLM (proxy)     • MetalLB (LB L2)                            │
│   • n8n (workflows)     • cert-manager                               │
│   • Prometheus/Grafana  • ArgoCD (GitOps)                            │
└──────────────────┬─────────────────────────┬─────────────────────────┘
                   │                         │
                   │ NFS / S3 / DB           │ HTTP /v1/chat/completions
                   ▼                         ▼
┌──────────────────────────────┐   ┌──────────────────────────────────┐
│  storage-01 (AlmaLinux 9.7)  │   │  gpu-01 (AlmaLinux 9.7)          │
│  192.168.10.1  HORS CLUSTER  │   │  192.168.10.20  HORS CLUSTER     │
│                              │   │                                  │
│  Données :                   │   │  • LM Studio (mode serveur)      │
│  • NFS (/srv/nfs/k8s)        │   │    écoute sur :1234              │
│  • PostgreSQL                │   │  • API OpenAI-compatible         │
│  • Qdrant (vectoriel)        │   │  • RX 6700XT via ROCm/Vulkan     │
│  • MinIO (S3)                │   │  • CLI `lms` pour pilotage       │
│                              │   │                                  │
│  Agent IA :                  │   │                                  │
│  • Hermes Agent ★            │◄──┤  Hermes appelle LM Studio        │
│    (service systemd)         │   │  via http://192.168.10.20:1234   │
│                              │   │                                  │
│  Infra :                     │   │                                  │
│  • Passerelle / NAT          │   │                                  │
│  • dnsmasq (lab.local)       │   │                                  │
│  • Bastion admin             │   │                                  │
└──────────────────────────────┘   └──────────────────────────────────┘
```

### 5.3. Plan d'adressage IP

**LAN domestique (existant, géré par la Freebox) — 192.168.1.0/24**

| IP | Hôte | Notes |
|---|---|---|
| `192.168.1.254` | Freebox | Gateway internet + DHCP |
| `192.168.1.10` | Machine 9950x3d (poste perso) | Déjà occupé |
| `192.168.1.200` | **storage-01** (interface WAN cluster) | IP statique réservée |

**LAN cluster (nouveau, isolé) — 192.168.10.0/24**

| IP | Hostname | Rôle |
|---|---|---|
| `192.168.10.1` | storage-01 | Gateway + admin + stockage + Hermes Agent (hors cluster k8s) |
| `192.168.10.11` | compute-01 | k8s control-plane (Talos, 16 GB) |
| `192.168.10.12` | compute-02 | k8s worker (Talos, 8 GB) |
| `192.168.10.13` | compute-03 | k8s worker (Talos, 8 GB) |
| `192.168.10.20` | gpu-01 | Hôte LM Studio dédié (AlmaLinux, hors cluster k8s) |
| `192.168.10.200-230` | (pool) | MetalLB — IP virtuelles des services k8s exposés |

**Ports applicatifs notables**

| Hôte | Port | Service | Notes |
|---|---|---|---|
| gpu-01 | 1234 | LM Studio API (OpenAI-compatible) | `http://192.168.10.20:1234/v1` |
| storage-01 | 22 | SSH (bastion + cible Ansible) | |
| storage-01 | 53 | dnsmasq | Résolution `*.lab.local` |
| storage-01 | 2049 | NFS | Export `/srv/nfs/k8s` |
| storage-01 | 5432 | PostgreSQL | |
| storage-01 | 6333 | Qdrant HTTP | |
| storage-01 | 9000 | MinIO S3 | |
| storage-01 | 9001 | MinIO Console | |
| MetalLB | 80/443 | Traefik | Tous les Ingress du cluster |
| MetalLB | 8080 | ArgoCD UI | `argocd.lab.local` |

**Réseaux internes Kubernetes (ne pas modifier sauf raison spécifique)**

- Pods (Flannel) : `10.42.0.0/16`
- Services : `10.43.0.0/16`

### 5.4. Câblage du switch GS308EV4

Tous les ports en mode **Access** (pas de trunk). storage-01 utilise deux NICs physiques — une par VLAN.

| Port | Connecté à | VLAN | Mode |
|---|---|---|---|
| 1 | Freebox | 1 - Default | Access |
| 2 | storage-01 NIC1 (WAN, 192.168.1.200) | 1 - Default | Access |
| 3 | gpu-01 (192.168.10.20) | 10 - cluster-funk | Access |
| 4 | compute-01 (192.168.10.11) | 10 - cluster-funk | Access |
| 5 | compute-02 (192.168.10.12) | 10 - cluster-funk | Access |
| 6 | compute-03 (192.168.10.13) | 10 - cluster-funk | Access |
| 7 | storage-01 NIC2 (LAN cluster, 192.168.10.1) | 10 - cluster-funk | Access |
| 8 | PC perso (192.168.1.x) | 1 - Default | Access |

**Avantage vs trunk** : pas de subinterface `eno1.10` à configurer — deux interfaces physiques classiques dans NetworkManager. Plus simple, moins de risque d'erreur au démarrage.

### 5.5. Route statique sur la Freebox

À configurer dans *Paramètres Freebox > Mode avancé > Configuration de routage* :

```
Réseau de destination : 192.168.10.0
Masque                : 255.255.255.0
Passerelle            : 192.168.1.200
```

Cela permet à toutes les machines du LAN domestique de joindre directement le LAN cluster.

---

## 6. Choix d'OS

| Machine | OS | Justification |
|---|---|---|
| storage-01 | **AlmaLinux 9.7 minimal** | Stack RHEL maîtrisée, stabilité, support sécurité jusqu'en 2032, doc abondante NFS/nftables/MinIO/Postgres |
| compute-01 | **Talos Linux v1.13+** | Choix d'apprentissage assumé, validation matérielle OK |
| compute-02 | **Talos Linux v1.13+** | idem |
| compute-03 | **Talos Linux v1.13+** | idem |
| gpu-01 | **AlmaLinux 9.7 minimal** | Cohérence RHEL avec storage-01, ROCm via repo officiel AMD pour RHEL 9 |

**Note sur AlmaLinux + ROCm pour gpu-01** : la majorité de la doc communautaire
ROCm + RX 6700XT vise Ubuntu. Sur AlmaLinux 9, utiliser le repo officiel AMD
pour RHEL 9 (`https://repo.radeon.com/rocm/`). Adapter les commandes dnf au
lieu d'apt. En cas de blocage ROCm, Vulkan reste un fallback fonctionnel pour LM Studio.

---

## 7. Architecture applicative

### 7.1. Topologie cluster Kubernetes

- **Control-plane** : single (compute-01 uniquement)
- **Workers** : compute-02, compute-03
- **storage-01 et gpu-01 sont HORS du cluster**

### 7.2. Découpage des responsabilités

**gpu-01 — Hôte d'inférence LLM (hors cluster)**
- LM Studio en mode serveur headless
- API OpenAI-compatible exposée sur `:1234`

**storage-01 — Bastion admin + données + agent (hors cluster)**
- Passerelle réseau (NAT, routage VLAN)
- Bastion : `talosctl`, `kubectl`, `helm`, `argocd` CLI, `ansible`
- Services données : NFS, PostgreSQL, Qdrant, MinIO
- **Hermes Agent** en service systemd
- dnsmasq pour la résolution `*.lab.local`

**Cluster Kubernetes (Talos, 3 nœuds)**
- Couche infra : MetalLB, Traefik, cert-manager, Longhorn, nfs-subdir-external-provisioner
- Couche observabilité : kube-prometheus-stack, Loki
- Couche applicative IA : Open WebUI, LiteLLM, n8n
- **GitOps via ArgoCD**

### 7.3. Flux principal Hermes ↔ LM Studio

```
Utilisateur → CLI Hermes / Telegram / autre interface
              (sur storage-01)
                    │
                    ▼
         Hermes Agent (skills, mémoire, tools)
              [storage-01 :daemon systemd]
                    │
                    │ HTTP POST /v1/chat/completions
                    ▼
         LM Studio (modèle chargé en VRAM 6700XT)
              [gpu-01 :1234]
                    │
                    │ Réponse streaming
                    ▼
         Hermes Agent (parsing tool calls, exécution)
                    │
                    ▼
              Utilisateur
```

### 7.4. Deux setups IA — poste perso vs cluster Funk

#### Setup 1 — Poste perso (9950X3D) — dev local

**Matériel** : Ryzen 9 9950X3D · 32 GB DDR5 · RX 9070 XT 16 GB VRAM

| Composant | Config |
|---|---|
| LM Studio | `http://127.0.0.1:1234/v1` (bind localhost par défaut) |
| Ollama | `http://127.0.0.1:11434` (aussi installé) |
| Hermes Agent | config `base_url: http://127.0.0.1:1234/v1` |
| Goose agents | `Funk/Funk-infra/agents/` → pointent sur `http://localhost:1234/v1` |

**Démarrage LM Studio :**
```bash
# Charger le modèle avec un context suffisant (système Hermes ≈ 15 000 tokens)
lms load <modele> --context-length 16384
lms server start --port 1234
```

**Config Hermes (`~/.hermes/config.yaml`) :**
```yaml
model:
  default: <nom-modele>
  provider: lmstudio
  base_url: http://127.0.0.1:1234/v1
  context_length: 16384
```

**Goose agents :**
```bash
cd Funk/Funk-infra/agents/<nom-agent>
goose session --config goose-config.yaml
```

| Agent Goose | Modèle cible | Usage |
|---|---|---|
| `global` | qwen2.5-14b | Raisonnement général |
| `infra` | qwen2.5-coder-14b | k8s, Talos, Helm, ArgoCD |
| `sysadmin` | qwen2.5-7b | Admin système, diagnostics |
| `monitoring` | llama3.2-3b | Logs, métriques |
| `data` | qwen2.5-14b | Analyse de données |

---

#### Setup 2 — Cluster Funk (production)

**Matériel GPU** : gpu-01, Ryzen 9 5950X · 32 GB DDR4 · RX 6700XT 12 GB VRAM

| Composant | Config |
|---|---|
| LM Studio | `http://192.168.10.20:1234/v1` (bind `0.0.0.0`) |
| Hermes Agent | service systemd sur storage-01 |
| Hermes `base_url` | `http://192.168.10.20:1234/v1` |

**Démarrage LM Studio sur gpu-01 :**
```bash
HSA_OVERRIDE_GFX_VERSION=10.3.0 lms load <modele> --context-length 16384
lms server start --port 1234 --host 0.0.0.0
```

**Config Hermes sur storage-01 (`~/.hermes/config.yaml`) :**
```yaml
model:
  default: <nom-modele>
  provider: lmstudio
  base_url: http://192.168.10.20:1234/v1
  context_length: 16384
```

**Différence clé avec setup 1** : `HSA_OVERRIDE_GFX_VERSION=10.3.0` obligatoire pour la RX 6700XT (gfx1031 non officiellement supporté par ROCm). Sur la RX 9070 XT du poste perso, cette variable n'est pas nécessaire.

---

## 8. Stack IaC / Automatisation

### 8.1. Principe : un seul repo Git, trois flux de déploiement

Toute l'infrastructure est gérée en GitOps depuis un repo Git unique. Trois outils
se partagent les responsabilités selon la cible :

```
┌──────────────────────────────────────────────────────────────────┐
│  Repo Git du lab (sur poste admin + remote Gitea/GitHub privé)   │
│                                                                  │
│  lab-infra/                                                      │
│  ├── ansible/              ← gère storage-01 + gpu-01            │
│  │   ├── inventory.yml                                           │
│  │   ├── playbooks/                                              │
│  │   ├── roles/                                                  │
│  │   └── group_vars/                                             │
│  ├── talos/                ← configs Talos via talhelper         │
│  │   ├── talconfig.yaml                                          │
│  │   ├── talsecret.sops.yaml                                     │
│  │   └── clusterconfig/    (généré par talhelper)                │
│  ├── k8s/                  ← manifests / Helm values             │
│  │   ├── apps/             ← apps déployées par ArgoCD           │
│  │   ├── infra/            ← MetalLB, Traefik, cert-manager...   │
│  │   └── argocd-bootstrap/ ← Application of Applications         │
│  └── docs/                                                       │
│      └── HOMELAB.md        ← ce document                         │
└────────┬─────────────────────────┬────────────────────┬──────────┘
         │                         │                    │
         │ ansible-playbook        │ talosctl           │ git push
         │ (push manuel SSH)       │ apply-config       │ → ArgoCD
         │                         │ (push manuel)      │   pull auto
         ▼                         ▼                    ▼
   storage-01,           compute-01/02/03         Workloads dans
   gpu-01                (Talos)                  le cluster k8s
   (AlmaLinux)
```

### 8.2. Outil 1 — Ansible (vanilla) pour les hôtes Linux classiques

**Cible** : storage-01 et gpu-01 (et futures machines AlmaLinux/Debian).

**Choix** : Ansible vanilla, **rôles écrits from scratch**. Pas de framework type
BlueBanquise — surdimensionné pour 5-10 hôtes Linux à terme. À reconsidérer si
on dépasse 25+ hôtes ou si on ajoute du HPC/Slurm.

**Structure des rôles prévus** :

```
ansible/
├── ansible.cfg
├── inventory.yml
├── group_vars/
│   ├── all.yml                  # variables communes (DNS, NTP, users)
│   ├── gateway.yml              # variables passerelle
│   └── gpu_hosts.yml            # variables hôtes GPU
├── host_vars/
│   ├── storage-01.yml
│   └── gpu-01.yml
├── playbooks/
│   ├── site.yml                 # playbook racine
│   ├── storage-01.yml
│   └── gpu-01.yml
└── roles/
    ├── common/                  # paquets de base, sshd, sudoers, motd
    ├── gateway/                 # NetworkManager VLAN, IP forwarding, nftables, NAT
    ├── dnsmasq/                 # DNS local *.lab.local
    ├── nfs_server/              # NFS server + exports
    ├── postgresql/              # Postgres + DB init
    ├── qdrant/                  # Qdrant via Podman
    ├── minio/                   # MinIO via Podman
    ├── rocm/                    # repo AMD + drivers + HSA_OVERRIDE
    ├── lm_studio/               # install + service systemd
    └── hermes_agent/            # install + config + systemd unit
```

**Inventaire** :

```yaml
# ansible/inventory.yml
all:
  children:
    gateway:
      hosts:
        storage-01:
          ansible_host: 192.168.10.1
    gpu_hosts:
      hosts:
        gpu-01:
          ansible_host: 192.168.10.20
    almalinux:
      children:
        gateway:
        gpu_hosts:
```

**Lancement type** :

```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 # cibler 1 rôle
```

**Bonnes pratiques** :
- Tous les rôles **idempotents** (peuvent être ré-exécutés sans casse).
- Variables sensibles dans **Ansible Vault** (mot de passe Postgres, clés API).
- Un playbook = une machine ou un groupe, pas de "playbook fourre-tout".
- Tags systématiques pour cibler un rôle spécifique.

### 8.3. Outil 2 — talhelper pour les nœuds Talos

**Cible** : compute-01, compute-02, compute-03.

**Pourquoi talhelper** : `talosctl gen config` brut produit des configs longues
et difficiles à maintenir. `talhelper` lit un seul fichier `talconfig.yaml`
clair et génère les configs YAML par nœud, avec gestion des secrets (chiffrement
SOPS) et patches par nœud.

**Structure** :

```
talos/
├── talconfig.yaml          # config déclarative (lue par talhelper)
├── talsecret.sops.yaml     # secrets chiffrés (token bootstrap, certs CA)
├── talenv.yaml             # version Talos, version k8s
├── patches/                # patches YAML par nœud ou groupe
│   ├── compute-01.yaml
│   └── all-controlplane.yaml
└── clusterconfig/          # généré : ne PAS commit (au .gitignore)
    ├── lab-compute-01.yaml
    ├── lab-compute-02.yaml
    └── lab-compute-03.yaml
```

**Workflow** :

```bash
cd talos/

# 1. Éditer talconfig.yaml (ajout d'un nœud, changement de version k8s, etc.)
vim talconfig.yaml

# 2. Régénérer les configs nœud
talhelper genconfig

# 3. Appliquer sur un nœud spécifique
talhelper gencommand apply --node compute-02 | bash

# 4. Bootstrap (uniquement la première fois, sur le control-plane)
talhelper gencommand bootstrap --node compute-01 | bash

# 5. Récupérer le kubeconfig
talhelper gencommand kubeconfig | bash
```

**Avantage GitOps** : le `talconfig.yaml` est ta source de vérité. N'importe qui
(toi dans 6 mois) peut lire ce fichier et comprendre l'état du cluster Talos
sans avoir à inspecter les nœuds.

### 8.4. Outil 3 — ArgoCD pour les workloads Kubernetes

**Cible** : tout ce qui tourne dans le cluster k8s (apps + infra k8s).

**Principe** : ArgoCD surveille en continu un dossier Git et garantit que l'état
réel du cluster correspond à ce qui est déclaré dans Git.

**Pattern recommandé : "App of Apps"**

```
k8s/
├── argocd-bootstrap/                # bootstrap ArgoCD lui-même
│   └── argocd-install.yaml
├── apps-of-apps/
│   └── root.yaml                    # une Application qui pointe sur apps/
├── infra/                           # déployé d'abord
│   ├── metallb/
│   │   ├── application.yaml
│   │   └── values.yaml
│   ├── traefik/
│   ├── cert-manager/
│   ├── longhorn/
│   └── nfs-provisioner/
└── apps/                            # déployé ensuite
    ├── litellm/
    │   ├── application.yaml
    │   ├── deployment.yaml
    │   ├── service.yaml
    │   └── ingress.yaml
    ├── open-webui/
    ├── n8n/
    ├── monitoring/                  # kube-prometheus-stack
    └── external-services/           # ExternalName vers storage-01
        ├── postgresql-external.yaml
        ├── qdrant-external.yaml
        └── lm-studio-external.yaml  # ExternalName vers gpu-01:1234
```

**Workflow d'utilisation** :

```bash
# 1. Modifier un manifest
vim k8s/apps/open-webui/deployment.yaml

# 2. Commit + push
git add k8s/apps/open-webui/
git commit -m "open-webui: bump to v0.4.0, add OIDC config"
git push

# 3. ArgoCD détecte le changement (poll par défaut toutes les 3 min)
#    → applique automatiquement dans le cluster
#    → visible dans l'UI argocd.lab.local
```

**Bénéfices** :
- Source de vérité unique (Git)
- Historique complet via `git log`
- Rollback en `git revert` + push
- Recréation totale du cluster en cas de désastre : install Talos via talhelper,
  install ArgoCD via le bootstrap, ArgoCD redéploie tout le reste tout seul

### 8.5. Récapitulatif des responsabilités

| Outil | Cible | Mode de déploiement | Stockage source |
|---|---|---|---|
| **Ansible** | Hôtes Linux classiques (storage-01, gpu-01, futures Alma) | Push manuel (`ansible-playbook`) | `lab-infra/ansible/` |
| **talhelper** | Nœuds Talos (compute-01/02/03) | Push manuel (`talosctl apply-config`) | `lab-infra/talos/` |
| **ArgoCD** | Workloads Kubernetes (apps + infra k8s) | Pull automatique depuis Git | `lab-infra/k8s/` |

### 8.6. Repo Git : où l'héberger ?

**Court terme** : repo local sur ton poste + remote sur **GitHub** (privé) ou
**GitLab** (privé) → simple et fiable.

**Moyen terme (recommandé)** : héberger un **Gitea** ou **Forgejo** sur
storage-01 (rôle Ansible dédié) + miroir GitHub pour backup. Avantages :
- Indépendance totale (pas besoin d'Internet pour déployer)
- Apprentissage de l'auto-hébergement Git
- ArgoCD pointe sur `git.lab.local/admin/lab-infra.git`

### 8.7. Gestion des secrets

**Pas de secrets en clair dans Git, jamais.** Trois mécanismes selon la couche :

- **Ansible** : Ansible Vault pour les secrets (`ansible-vault encrypt_string`)
- **Talos** : SOPS + age (intégré nativement à talhelper)
- **Kubernetes** : Sealed Secrets (Bitnami) ou External Secrets Operator
  pointant sur un Vault — pour démarrer, Sealed Secrets est plus simple

---

## 9. Feuille de route par phases

Chaque phase est validable indépendamment. Ne passer à la suivante que quand
la précédente fonctionne.

### Phase 0 — Préparation
- [x] Validation matérielle Talos sur 1 ThinkCentre (compute-01) ✅ effectué
- [ ] Vérifier que tous les ThinkCentre ont des SSD SATA opérationnels
- [ ] Préparer 1 clé USB pour Talos + 1 clé USB pour AlmaLinux
- [ ] Initialiser le repo Git `lab-infra/` sur le poste admin

### Phase 1 — Infra réseau (storage-01 manuel + bootstrap Ansible)
- [x] Installation AlmaLinux sur storage-01 ✅
- [x] Configuration IP de base — storage-01 joignable (192.168.10.1 LAN cluster) ✅
- [x] IP WAN storage-01 fixée à 192.168.1.200 ✅
- [x] Configuration VLAN sur le GS308EV4 ✅ — tous ports Access, storage-01 sur deux NICs physiques
- [ ] Initialiser `lab-infra/ansible/` avec inventory + rôle `common` + rôle `gateway`
- [ ] Premier `ansible-playbook` qui configure VLAN/nftables/NAT/IP forwarding
- [ ] Route statique sur la Freebox
- [ ] Rôle `dnsmasq` pour résolution `*.lab.local`
- [ ] Validation : ping bidirectionnel LAN domestique ↔ LAN cluster

### Phase 2 — Hôte gpu-01 + LM Studio (Ansible)
- [x] Installation AlmaLinux sur gpu-01 ✅ — ping OK depuis storage-01
- [ ] Ajout de gpu-01 à l'inventaire Ansible
- [ ] Rôle `rocm` : repo AMD + drivers + `HSA_OVERRIDE_GFX_VERSION=10.3.0`
- [ ] Validation ROCm : `rocminfo` doit lister la RX 6700XT
- [ ] Rôle `lm_studio` : install + service systemd avec bind `0.0.0.0:1234`
- [ ] Téléchargement d'un modèle test (Hermes 4, Qwen 2.5 7B...)
- [ ] Validation : `curl http://192.168.10.20:1234/v1/models` depuis storage-01

### Phase 3 — Hermes Agent sur storage-01 (Ansible)
- [ ] Rôle `hermes_agent` : install via script officiel
- [ ] Configuration provider custom endpoint (LM Studio gpu-01)
- [ ] Premier `hermes chat` interactif
- [ ] (Optionnel) Service systemd pour daemon background
- [ ] Données persistantes (`~/.hermes/`) sur le RAID5

### Phase 4 — Stack données sur storage-01 (Ansible)
- [ ] Rôle `nfs_server` : export `/srv/nfs/k8s` vers `192.168.10.0/24`
- [ ] Rôle `postgresql` : install + DB init
- [ ] Rôle `qdrant` : Podman + service systemd
- [ ] Rôle `minio` : Podman + service systemd
- [ ] Validation : tous les services accessibles depuis le LAN cluster

### Phase 5 — Cluster Talos (talhelper)
- [ ] Génération image Talos via factory.talos.dev (extensions `iscsi-tools`)
- [ ] Init `lab-infra/talos/` avec `talconfig.yaml` (3 nœuds)
- [ ] `talhelper genconfig`
- [ ] Apply sur compute-01 (control-plane) puis bootstrap etcd
- [ ] Apply sur compute-02 et compute-03 (workers)
- [ ] Validation : `kubectl get nodes` montre 3 nœuds Ready

### Phase 6 — Bootstrap ArgoCD + infra k8s (GitOps)
- [ ] Install ArgoCD manuel via Helm (une seule fois, l'install ArgoCD reste manuelle)
- [ ] Init `lab-infra/k8s/` avec App of Apps pattern
- [ ] Création des Applications ArgoCD pour : MetalLB, Traefik, cert-manager
- [ ] Validation : pod nginx test exposé via Ingress, accessible depuis le LAN

### Phase 7 — Stockage k8s (ArgoCD)
- [ ] Application ArgoCD : `nfs-subdir-external-provisioner` pointant sur storage-01
- [ ] Application ArgoCD : Longhorn (avec `iscsi-tools` extension Talos)
- [ ] Validation : 1 PVC NFS + 1 PVC Longhorn créés et montés

### Phase 8 — Apps IA (ArgoCD)
- [ ] Applications ArgoCD : ExternalName vers Postgres, Qdrant, LM Studio
- [ ] Application ArgoCD : LiteLLM (proxy vers LM Studio)
- [ ] Application ArgoCD : Open WebUI (front, pointe sur LiteLLM)
- [ ] Application ArgoCD : n8n
- [ ] Application ArgoCD : kube-prometheus-stack + Loki
- [ ] Validation finale : workflow n8n → Open WebUI → LiteLLM → LM Studio

### Phase 9 — Optionnel : Gitea local + miroir
- [ ] Rôle Ansible `gitea` sur storage-01
- [ ] Migration du repo `lab-infra` vers Gitea local
- [ ] Reconfiguration ArgoCD pour pointer sur `git.lab.local`
- [ ] Miroir push vers GitHub privé (sauvegarde)

---

## 10. Points d'attention spécifiques

### 10.1. RX 6700XT et ROCm sur AlmaLinux 9
- gfx1031 non officiellement supporté par ROCm
- **Variable obligatoire** : `HSA_OVERRIDE_GFX_VERSION=10.3.0`
- ROCm s'exécute directement sur l'OS hôte (gpu-01), pas dans des conteneurs
- Fallback Vulkan possible dans LM Studio si ROCm coince
- Doc ROCm pour RHEL 9 : https://rocm.docs.amd.com/projects/install-on-linux/

### 10.2. LM Studio en mode serveur
- Par défaut LM Studio bind sur `127.0.0.1` → inaccessible depuis le réseau
- Forcer `0.0.0.0` via CLI : `lms server start --port 1234 --host 0.0.0.0`
- LM Studio CLI : `lms ls`, `lms load <model>`, `lms unload`, `lms server status`

### 10.3. Hermes Agent
- Daemon : possible via systemd unit
- Mémoire/skills stockés dans `~/.hermes/` → **à mettre sur le RAID5**
- Variables `.env` dans `~/.hermes/.env`

### 10.4. RAM serrée sur ThinkCentre 8 GB
- Réserver système+kubelet : `system-reserved=cpu=500m,memory=1Gi`,
  `kube-reserved=cpu=500m,memory=1Gi`
- Toujours définir `resources.requests/limits` sur les pods
- Sinon risque OOM-killer sur le nœud entier

### 10.5. Single control-plane
- Si compute-01 tombe, plus de `kubectl apply` mais les workloads continuent
- Snapshots etcd réguliers : `talosctl etcd snapshot` vers storage-01 (cron Ansible)

### 10.6. RAID5 sur storage-01
- 4×1 TB = 2.7 TB exploitables
- Sauvegarder : configs k8s (Git), bases de données (dump périodique
  vers MinIO ou NAS externe), mémoire/skills Hermes (`~/.hermes/`)
- Pas les modèles LM Studio (re-téléchargeables)

### 10.7. Sécurité passerelle = bastion admin
- storage-01 cumule passerelle + bastion + Hermes + données → SPOF assumé
- gpu-01 sans rôle réseau critique : si tombe, perte inférence uniquement

### 10.8. Bootstrap : ce qui reste manuel
GitOps ne peut pas faire le bootstrap absolu. Restent toujours manuels :
- Installation OS sur chaque machine (AlmaLinux ou Talos depuis USB)
- Configuration IP/SSH minimale pour qu'Ansible puisse se connecter
- Premier déploiement ArgoCD via Helm
- Tout le reste est ensuite géré par Ansible / talhelper / ArgoCD

---

## 11. Outils à installer sur storage-01 (bastion admin)

```bash
# Outils Kubernetes / Talos
- talosctl   (admin Talos)
- talhelper  (génération configs Talos)
- kubectl    (admin Kubernetes)
- helm       (charts)
- argocd CLI
- k9s        (TUI Kubernetes)

# Outils IaC
- ansible-core
- sops + age (secrets Talos)

# Outils IA
- hermes     (Hermes Agent)
- curl/jq    (debug API LM Studio)

# Outils système
- nftables, dnsmasq, nfs-utils, postgresql-server, podman
- minio, qdrant (binaires ou conteneurs Podman)

# Optionnel
- gitea ou forgejo (Git local)
- restic ou borgbackup (sauvegardes)
```

Tous installés via les rôles Ansible — pas de installation manuelle au-delà
du bootstrap initial.

---

## 12. Conventions et nommage

- Hostnames : `storage-01`, `compute-01`, `compute-02`, `compute-03`, `gpu-01`
- Domaine local : `lab.local`
- Services exposés via cluster : `<service>.lab.local` → IP MetalLB de Traefik
- Endpoint LM Studio : `http://192.168.10.20:1234/v1` (ou `lms.lab.local`)
- Namespaces k8s :
  - `argocd` (ArgoCD lui-même)
  - `infra` (MetalLB, Traefik, cert-manager, monitoring)
  - `storage` (Longhorn, NFS provisioner)
  - `external` (ExternalName vers services storage-01/gpu-01)
  - `ai` (Open WebUI, LiteLLM)
  - `workflows` (n8n)
- Git :
  - Branches : `main` (= prod), `feature/*` (changements en cours)
  - Commits : conventional commits (`feat:`, `fix:`, `chore:`, `docs:`)

---

## 13. État actuel (2026-05-09)

- ✅ Architecture validée
- ✅ Test Talos sur compute-01 effectué (boot OK, IP DHCP, talosctl répond)
- ✅ Stack IaC choisie : Ansible vanilla + talhelper + ArgoCD
- ✅ Hermes Agent configuré sur le poste perso (Setup 1 — 9950X3D + LM Studio local)
- ✅ AlmaLinux installé sur storage-01 — ping OK (`192.168.1.200` WAN, `192.168.10.1` LAN cluster)
- ✅ AlmaLinux installé sur gpu-01 — ping OK depuis storage-01 (`192.168.10.20`)
- ✅ Connectivité LAN cluster (VLAN 10) : storage-01 ↔ gpu-01 fonctionnelle
- ⏳ Phase 1 en cours : Ansible + rôles + dnsmasq + NAT à configurer
- ⏳ Phase 2 en cours : ROCm + LM Studio sur gpu-01 à installer
- ❌ Ansible pas encore exécuté (repo `lab-infra/` à initialiser)
- ❌ VLAN switch GS308EV4 à confirmer / finaliser
- ❌ Route statique Freebox à configurer
- ❌ Aucun cluster k8s monté
- ❌ LM Studio pas encore installé sur gpu-01
- ❌ Hermes Agent pas encore installé sur storage-01

---

## 14. Annexes — Liens utiles

**Infrastructure**
- Talos Linux : https://www.talos.dev/
- Image Factory Talos : https://factory.talos.dev/
- talhelper : https://github.com/budimanjojo/talhelper
- AlmaLinux 9 : https://almalinux.org/
- ROCm install (RHEL 9) : https://rocm.docs.amd.com/projects/install-on-linux/

**Kubernetes / GitOps**
- ArgoCD : https://argo-cd.readthedocs.io/
- ArgoCD App of Apps pattern : https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/
- Sealed Secrets : https://github.com/bitnami-labs/sealed-secrets
- Longhorn : https://longhorn.io/
- MetalLB : https://metallb.universe.tf/

**IaC**
- Ansible docs : https://docs.ansible.com/
- Ansible Vault : https://docs.ansible.com/ansible/latest/vault_guide/index.html
- SOPS : https://github.com/getsops/sops

**IA**
- LM Studio : https://lmstudio.ai/
- LM Studio CLI : https://lmstudio.ai/docs/cli
- Hermes Agent : https://github.com/NousResearch/hermes-agent
- LiteLLM : https://docs.litellm.ai/
- Open WebUI : https://docs.openwebui.com/
