Chapitre 17 / 39
Le harness
Le harness est l’image Fedora bootc qui tourne sur le Framework Desktop dans la salle serveur de Gennevilliers. C’est l’artefact unique que Sodimo reçoit. Tout ce qui tourne sur la machine est décrit par des fichiers texte dans un seul dépôt : un répertoire de Podman Quadlets, un Caddyfile, quelques fichiers d’environnement. Reconstruisez la machine demain, pointez-la vers le dépôt, appuyez sur le bouton — même système, mêmes ports, mêmes données.
Statut : En cours — le schéma harness est éprouvé sur les machines personnelles de Thomas et est la source de chaque quadlet que Sodimo hérite. Il s’active sur la machine Sodimo dès que le Framework Desktop est installé en rack.
Ce qu’est le harness
Fedora bootc est le système d’exploitation — une image Linux qui se met à jour comme un conteneur plutôt que comme une distribution traditionnelle : un swap atomique, un rollback, aucun état partiellement patché. L’image OS est construite en CI, signée et livrée comme artefact OCI ; la machine la tire comme un conteneur tire sa propre image.
Podman Quadlets est le format pour décrire un service : un court fichier déclaratif qui dit à systemd comment faire tourner un conteneur, quels ports ouvrir, quels volumes monter, quels secrets injecter, et de quels autres services il dépend. Un répertoire de quadlets plus un ensemble de fichiers d’environnement décrit complètement un système en production.
Pas de Kubernetes. Pas de Helm. Pas de tableau de bord éditeur. Un dépôt de texte brut. Cockpit (l’interface web qui accompagne l’image de base) montre ce qui tourne, ce que les logs disent et ce qui a échoué — pour les moments où l’équipe veut regarder directement la machine plutôt que par une vitre.
Le dépôt est sodimo/harness. C’est un soft-fork de mecattaf/harness — le schéma upstream de Thomas, restreint selon le Principe 1 aux seuls services que Sodimo fait tourner.
Inventaire des quadlets
Chaque service du harness est un quadlet. L’inventaire est délibérément court.
Pile mail. Quatre quadlets, un seul rôle.
postfix— soumission et distribution SMTP. Prend le courrier sortant du service drain, reçoit le courrier entrant de l’opérateur, le remet à Dovecot pour stockage.dovecot— serveur IMAP et stockage des boîtes aux lettres. Adossé à un volume sur le LVM LUKS chiffré.rspamd— filtre anti-spam et signataire DKIM. Se place devant Postfix en entrée, signe en sortie.piler— l’archive email sans limite de durée, avec interface web de recherche. L’équipe y accède via Tailscale àpiler.sodimo.eu(interne). Rétention : tout conserver, cap à ~100 Go.
Inférence IA locale. Trois quadlets qui forment ensemble la surface de modèles locale.
llama.cpp— le runtime de modèles. Épinglé à une version upstream spécifique, backend AMD Vulkan pour le GPU du Framework.llama-swap— le routeur de modèles à la demande, placé devant llama.cpp. Charge un modèle au premier appel, le décharge après un TTL. Maintient les petits modèles de tâches en résidence permanente ; swap les modèles plus lourds à la demande.litellm— la passerelle compatible OpenAI. Un seul endpoint (localhost:4000) pour tous les consommateurs — OpenWebUI, l’ETL, tout skill — qu’il s’agisse d’un modèle local (via llama-swap) ou cloud (via l’API Anthropic). L’endroit unique où les clés API, les limites de débit et le routage par modèle sont définis.
La sélection du modèle est de la configuration, pas du code. Changer de modèle est une modification du YAML de configuration du quadlet llama-swap, pas une reconstruction.
Interface de chat. Un quadlet plus sa base de données.
openwebui— l’interface de chat d’équipe. Tourne en mode sans authentification derrière un accès Tailscale uniquement ; chaque membre de l’équipe qui atteint l’URL obtient une session de niveau administrateur. Parle àlitellmpour l’accès aux modèles. Voir le chapitre OpenWebUI pour les détails côté utilisateur.openwebui-db— Postgres pour l’état propre d’OpenWebUI (historique des chats, prompts partagés).
Agents planifiés. Deux quadlets.
paperclip— le runner d’agents planifiés. Réveille les agents sur cron, capture les exécutions dans son propre Postgres, synchronise les lignes d’usage dansrun_ledgersur D1 via un cron. Voir le chapitre Paperclip.paperclip-db— Postgres pour Paperclip.
Proxy inverse et réseau.
caddy— termine le TLS avec des certificats émis par Cloudflare, route les noms d’hôte internes*.sodimo.euvers le bon port de conteneur, valide sa configuration avant chaque rechargement. Rechargement sans interruption pour les changements de configuration.- Tailscale tourne comme service système (pas un quadlet), faisant rejoindre le harness au réseau overlay de l’équipe.
Drain email. Un petit service systemd, pas un conteneur.
sodimo-email-drain.service— un programme Python de trente lignes. Interroge la Cloudflare Queueemail_outboxvia HTTPS toutes les quelques secondes, valide chaque message contre la liste d’expéditeurs autorisés, passe àsendmail -t, acquitte en cas de succès. Relance avec backoff, route les échecs versemail_dlq. C’est la moitié on-prem du câblage du Principe 3 : le harness n’ouvre aucun port entrant ; tout le trafic Cloudflare-vers-harness est un pull de ce service.
ETL nocturne.
sodimo-etl.timeret.service— un timer systemd et un oneshot qui se déclenche à 03:00, lit le dump CSV du jour depuis le NAS, valide contre un contrat de données écrit, et charge D1 via l’API HTTP D1. Des contrôles de parité bloquent les données incorrectes. Paul reçoit un email en cas d’échec du chargement.
Administration.
cockpit— l’interface d’administration locale. Accessible via Tailscale àcockpit.sodimo.eu.
C’est l’inventaire complet. Il n’y a pas de serveur MCP sur le harness, pas de service de transcription vocale, pas de passerelle agrégateur. La surface MCP vit sur Cloudflare (chapitre Ce à quoi l’IA peut accéder) ; la voix est hors périmètre pour cette mission.
Quadlets sur le harness
À lire de haut en bas. La pile mail gère le SMTP entrant et sortant. La pile IA, c’est OpenWebUI et Paperclip qui parlent tous deux à LiteLLM, lequel relaie vers les modèles locaux via llama-swap ou directement vers les modèles cloud. Le service drain est le seul pont entre Cloudflare et la machine, et c’est un pull — aucun port entrant.
Ligne de commande noyau
L’iGPU Strix Halo (gfx1151) n’atteint la pleine capacité du pool unifié de 128 Go que quand le noyau démarre avec les flags validés par kyuz0. Le Framework Desktop de dev démarre actuellement avec amd_iommu=off ttm.pages_limit=33554432 ttm.page_pool_size=33554432 — la base pré-kyuz0. La cible est :
iommu=pt amdgpu.gttsize=126976 ttm.pages_limit=32505856
Justification : amdgpu.gttsize=126976 est le flag qui alloue ~124 Gio de GTT à l’iGPU ; sans lui, le slot local-heavy (gpt-oss-120b UD-Q8_K_XL à 65 k ctx) plante en mémoire au chargement. iommu=pt remplace amd_iommu=off — pass-through, pas désactivé. ttm.pages_limit=32505856 plafonne la mémoire épinglée à 124 Gio.
Persistance via l’image bootc (kargs intégrés en post-install, pas de réglage par machine). Suivi dans sodimo/harness#11.
Vérification sur la machine live :
cat /proc/cmdline
cat /sys/class/drm/card*/device/mem_info_gtt_total # expect ~124 GiB
Delta Fedora à surveiller : la bande host-config de kyuz0 est Fedora 42/43, kernel ≥ 6.18.6-200, firmware ≥ 20260110. Le harness est sur Fedora 44 (kernel 6.19.13-300.fc44) — une version en avance. Un smoke sur matériel réel est obligatoire avant de déclarer la machine en production ; revenir à F43 si gfx1151 régresse.
Dépendance de référence IA locale : kyuz0
Le runtime IA local de Sodimo suit github.com/kyuz0/amd-strix-halo-toolboxes comme référence faisant autorité pour ce silicium — flags d’invocation de llama-server, kargs noyau, épinglages firmware, heuristiques de sélection de backend. Traité comme référence, pas comme runtime : l’image gateway reste sur ghcr.io/mostlygeek/llama-swap:vulkan, et les entrées de kyuz0 sont copiées (pas tirées) dans les configs des quadlets.
Épinglage actuel : 1421e8706020e8d7e797f71b9f28cd3072e7f868 (capturé le 2026-04-22).
L’ensemble de flags Strix-Halo obligatoires est appliqué à chaque entrée de modèle dans llama-swap.yaml — voir Référence quadlets → llama-swap pour les blocs cmd: concrets. La documentation de support vit dans le dépôt dotfiles :
sodimo/dotfiles/docs/kyuz0-toolbox.md— carte d’interpolation (ce qui a été copié d’où), compromis RADV vs AMDVLK vs ROCm, questions ouvertes.sodimo/dotfiles/docs/resync-runbook.md— procédure pour rafraîchir depuis un commit kyuz0 plus récent.
Comment la machine est construite
L’image bootc est construite dans GitHub Actions depuis un Containerfile dans sodimo/harness. L’image embarque le Fedora de base, les services système (Tailscale, Cockpit), et une copie vendorisée du répertoire de quadlets. Chaque image est taguée avec une version calendaire et poussée dans le registre de conteneurs Sodimo. La machine tire l’image au redémarrage ; bootc upgrade ou bootc switch tire une version plus récente, bootc rollback revient en arrière. Les mises à jour sont délibérées — pas d’auto-update, pas de redémarrage surprise.
Déployer une nouvelle image, du début à la fin :
- Fusionner dans
sodimo/harnessmain. - La CI construit, signe et publie la nouvelle image avec un tag daté.
- Sur la machine :
bootc switch ghcr.io/sodimo/harness:YYYY-MM-DD-slug && systemctl reboot. - Au démarrage, la nouvelle image est la racine ; l’image précédente est en attente de rollback.
Si quelque chose ne va pas après le redémarrage : bootc rollback. Une commande, un redémarrage, retour à l’état précédent connu-bon.
Comment la machine est reprovisionnée de zéro
La procédure complète, documentée pour que Sodimo ne dépende pas d’un ingénieur pour l’exécuter :
- Flasher une clé USB d’installation Fedora bootc avec la dernière image du harness Sodimo.
- Démarrer depuis la clé USB, partitionner le disque de données avec LUKS + LVM selon le schéma documenté, installer.
- Coller la clé d’authentification Tailscale (depuis le papier) et rejoindre le tailnet.
- Coller les fichiers d’environnement de secrets (depuis le papier + sauvegarde) dans
/etc/sodimo/secrets/. systemctl start sodimo.target.- Restaurer les données de boîtes aux lettres Dovecot, le Postgres Paperclip et le Postgres OpenWebUI depuis le dernier snapshot NAS.
- Vérifier : email entrant/sortant, OpenWebUI accessible, agents Paperclip reprennent, service drain consomme la queue.
Durée cible de bout en bout : moins de deux heures.
Comment ça survive à la réalité opérationnelle
Redémarrages. Les quadlets sont des unités systemd. Au démarrage, la machine lit le répertoire et lance les services dans l’ordre des dépendances. Une coupure de courant à 3h du matin remet le mail en route avant que quelqu’un s’en aperçoive.
Mises à jour. Un échange de modèle, un nouveau service, une modification de configuration — tout s’exprime comme un commit git sur sodimo/harness. Le changement se déploie en construisant une nouvelle image bootc et en redémarrant dessus. Le rollback, c’est l’image précédente.
Secrets. Les identifiants vivent dans des fichiers d’environnement injectés au démarrage du service — jamais committés dans le dépôt. La séparation est documentée : papier pour les identifiants qu’un humain saisit, fichiers chiffrés sur la machine pour les tokens dont un service a besoin. Dans les deux cas, le quadlet référence un secret par son nom, pas par sa valeur.
Sauvegardes. Le harness sauvegarde chaque nuit sur le NAS (mailstore Dovecot, Postgres Paperclip, Postgres OpenWebUI, archive Piler, état système pertinent). Le NAS réplique hors site vers Cloudflare R2. Une restauration bare-metal complète s’effectue en moins de deux heures — parce que le harness est le schéma directeur, restaurer la machine revient à réinstaller Fedora bootc et le pointer vers sodimo/harness.
Passation. Quand la mission se termine, le harness est l’artefact que l’équipe hérite. Tout ce qui tourne sur la machine est décrit dans un seul dépôt avec un seul schéma — de sorte qu’un futur ingénieur peut le lire et le comprendre en une après-midi. Pas de couche propriétaire, pas de tableau de bord à apprendre, pas de consultant à appeler.
En cours : recâblage de la source chezmoi
L’image bootc du harness intègre actuellement mecattaf/dotfiles comme source chezmoi (sous-module dans subprojects/dotfiles, référencé par mkosi.conf.d/subprojects.conf et chezmoi-update.service). La migration vers sodimo/dotfiles est suivie dans sodimo/harness#10. Chemin recommandé en discussion : supprimer chezmoi-update.service entièrement et s’appuyer sur le snapshot /usr/share/harness/dotfiles intégré dans l’image bootc — évite d’avoir besoin d’un token de déploiement pour les pulls de dépôt privé, et correspond à la posture statique-à-la-passation. Les mises à jour arrivent alors via une nouvelle image bootc, pas via un chezmoi update quotidien.