Accueil / Articles

Exécuter l’agent Hermes localement dans Podman sans root avec Ollama sur une GPU AMD

Exécuter Hermes Agent localement et en toute sécurité : Podman sans accès root, une seule carte mémoire montée, réseau interne, Ollama sur une GPU AMD avec ROCm, ainsi qu’une unité systemd.

Éditeur : REACTAPP.TOP20 min de lecture4524 mots

Sources: Run Hermes Agent Locally (and securely) with Ollama and Rootless Podman on Arch Linux

Exécuter un agent IA sur des notes Markdown et Obsidian locales semble simple jusqu’à ce que la question de l’accès se pose. Hermes Agent de Nous Research peut exécuter des commandes de terminal, modifier des fichiers, exécuter du code et appeler d’autres outils. Fournir à cet agent un modèle qui ne doit accéder qu’à un seul dossier ne crée pas de barrière de sécurité, car une instruction n’est qu’un texte. La documentation officielle d’Hermes souligne le même point : sa configuration HERMES_WRITE_SAFE_ROOT protège les outils normaux de modification de fichiers, mais l’outil de terminal peut toujours accéder à tout ce à quoi le compte du système d’exploitation sur lequel fonctionne Hermes a accès.

La configuration présentée ci-dessous place Hermes directement à l’intérieur de Podman sans accès au système racine, à côté d’un serveur Ollama qui exécute un modèle local sur une GPU AMD. Hermes ne peut modifier qu’un seul dossier monté intentionnellement. Le reste du système de fichiers hôte est simplement absent de son espace de noms de conteneur, ce qui l’empêche d’y accéder. Hermes prend officiellement en charge son exécution dans un conteneur, conserve un état persistant sous /opt/data, et fonctionne avec des serveurs d’inférence auto-hébergés tels qu’Ollama via une interface compatible OpenAI.

Cette démarche a été réalisée sur EndeavourOS, une dérivée d’Arch. Adaptez les commandes de paquets à d’autres distributions.

Installation de Podman sans accès au système racine

Sur Arch ou EndeavourOS, installez Podman à l’aide de Pacman :

sudo pacman -Syu podman

Pacman peut demander quel environnement de exécution OCI utiliser, en proposant ces trois options :

1) crun
2) krun
3) runc

Le choix effectué ici est le premier :

1

Ce runtime est crun. Cela s’avère utile par la suite, car la fonction keep-groups de Podman, qui transmet des groupes hôtes supplémentaires à un conteneur sans racine, fonctionne avec crun.

Vérifiez l’installation en affichant la version et en demandant à Podman quel runtime il utilise :

podman --version
podman info --format '{{.Host.OCIRuntime.Name}}'

La deuxième commande doit répondre par le nom en temps de exécution :

crun

Les conteneurs sans racine ont également besoin de mappages UID et GID subordonnés pour l’utilisateur, donc vérifiez que des entrées existent dans les deux fichiers :

grep "^$USER:" /etc/subuid
grep "^$USER:" /etc/subgid

Exécutez à partir de maintenant tous les commandes Podman en tant qu’utilisateur normal. Ne les préfixez pas de sudo.

Réparation d’OverlayFS sans root en cas de besoin

Le premier téléchargement d’image sur cette machine a échoué en raison d’une erreur indiquant que le noyau ne prenait pas en charge Overlay sur un système de fichiers ext :

kernel does not support overlay fs:
'overlay' is not supported over extfs

Lorsque cela se produit, installez l’implémentation en espace utilisateur :

sudo pacman -S fuse-overlayfs

Podman recommande fuse-overlayfs pour les configurations sans root où l’overlay natif n’est pas disponible, et il peut être spécifiquement désigné comme le programme de montage sans root. Vérifiez qu’il est installé :

which fuse-overlayfs

Le chemin attendu est :

/usr/bin/fuse-overlayfs

La configuration est stockée dans un fichier par utilisateur :

~/.config/containers/storage.conf

Si il existe déjà, modifiez-le plutôt que de le remplacer aveuglément. La section de stockage doit sélectionner le pilote d’overlay :

[storage]
driver = "overlay"

Une deuxième section doit indiquer le nom du programme de montage :

[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"

Ensuite, vérifiez les paramètres en vigueur en filtrant les sorties de débogage de Podman :

podman info --debug | grep -Ei 'graphDriverName|mount_program|overlay'

Déplacer le stockage vers un disque plus grand

Dans le cadre de ce projet, cette étape est devenue nécessaire presque immédiatement. L’image de conteneur Ollama seule occupe déjà plusieurs gigaoctets, les modèles ajoutent souvent entre 5 et 20 Go, voire plus, et Podman utilise également de l’espace disque temporaire lors du téléchargement et du déballage des couches. La première tentative de récupération d’Ollama a fini par échouer avec un message indiquant qu’il n’y avait plus d’espace dans un répertoire situé sous le dossier temporaire :

write /var/tmp/container_images_storage...:
no space left on device

Deux emplacements distincts sont impliqués, ce qui peut facilement être négligé. Le stockage permanent sans racine pour les images, les conteneurs et les volumes se trouve généralement sous le répertoire home :

$HOME/.local/share/containers/storage

Les couches téléchargées se trouvent temporairement dans :

/var/tmp

Changer la valeur de --tmpdir chez Podman ne déplace pas ce répertoire de téléchargement. La documentation de Podman indique d’utiliser plutôt la variable d’environnement TMPDIR.

Avec un système de fichiers local plus volumineux, créez une structure comprenant un répertoire pour le stockage et un autre pour les fichiers temporaires :

/path/to/large-drive/podman/
├── storage/
└── tmp/

Créer les deux et transférer la propriété à l’utilisateur actuel :

sudo mkdir -p /path/to/large-drive/podman/storage
sudo mkdir -p /path/to/large-drive/podman/tmp
sudo chown -R "$USER:$USER" /path/to/large-drive/podman

Ensuite, ouvrez la configuration du stockage pour la modifier :

mkdir -p ~/.config/containers
nano ~/.config/containers/storage.conf

Pointez la racine du graphique vers le nouveau répertoire de stockage tout en conservant le pilote d’overlay :

[storage]
driver = "overlay"
graphroot = "/path/to/large-drive/podman/storage"

Et conservez la configuration de montage fuse-overlayfs :

[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"

Choisissez un système de fichiers local pour cela. La documentation de Podman met en garde contre le fait que NFS et d’autres systèmes de fichiers réseau se comportent de manière incorrecte en tant que stockage pour des conteneurs sans root.

Les téléchargements temporaires doivent également être déplacés, il faut donc définir TMPDIR pour le shell actuel :

export TMPDIR=/path/to/large-drive/podman/tmp

Pour le rendre permanent dans Bash, ajoutez-le au profil et rechargez-le :

echo 'export TMPDIR=/path/to/large-drive/podman/tmp' >> ~/.bashrc
source ~/.bashrc

Vérifiez les nouvelles localisations avant de supprimer quoi que ce soit de l’ancienne. Imprimez la racine du graphe et filtrez les résultats de débogage relatifs au répertoire temporaire et au programme de montage :

podman info --format 'GraphRoot: {{.Store.GraphRoot}}'
podman info --debug | grep -Ei 'graphRoot|imageCopyTmpDir|mount_program'

Seul sur une installation fraîche de Podman, et uniquement lorsque la nouvelle racine du graphe est définitivement correcte, le stockage abandonné à l’ancienne emplacement devrait être envisagé pour suppression :

~/.local/share/containers/storage

Ne faites pas cela à la légère sur une installation existante. C’est aussi pour cette raison qu’il convient de déterminer l’emplacement de stockage avant de créer le volume du modèle Ollama.

Un dossier pour Hermes, deux volumes pour l’état

Sélectionnez le seul répertoire hôte que Hermes pourra modifier, et choisissez-le en connaissance de cause. Exportez son chemin dans une variable et créez le dossier :

export HERMES_WORKSPACE="$HOME/path/to/Hermes-Workspace"
mkdir -p "$HERMES_WORKSPACE"

Avec Obsidian, une bonne option consiste à utiliser un dossier dédié à l’intérieur d’un coffre-fort, comme un répertoire de recherche situé à côté des dossiers personnels et professionnels :

Obsidian Vault/
├── Personal/
├── Work/
├── Research/
└── Agent Workspace/       ← only this folder is exposed

Tout le reste dans le coffre-fort reste invisible pour l’agent.

Ensuite, créez deux volumes que Podman gérera lui-même :

podman volume create hermes-data
podman volume create ollama-models

Le premier contient la configuration, la mémoire et l’état persistant d’Hermes. Le second contient le modèle local. Aucun des deux n’exige que des dossiers hôtes supplémentaires soient mis à disposition de l’agent.

Donner accès à la GPU AMD au conteneur

Cette configuration vise AMD ROCm, et non NVIDIA CUDA. Sur une carte AMD, ne téléchargez pas nvidia-container-toolkit, solution de contournement utilisée précédemment avant que l’erreur ne soit détectée. Les instructions de conteneur d’Ollama utilisent l’image ollama/ollama:rocm pour les GPU AMD et transmettent deux nœuds de périphériques au conteneur :

/dev/kfd
/dev/dri

Vérifiez qu’ils existent sur l’hôte :

ls -l /dev/kfd
ls -l /dev/dri/render*

Ensuite, consultez les groupes du compte :

groups

Si le compte manque dans les groupes de vidéo et de rendu, ajoutez-le :

sudo usermod -aG video,render "$USER"

Ensuite, déconnectez-vous complètement puis reconnectez-vous. L’exécution à nouveau de la liste des groupes dans le même terminal ancien ne montrera pas les nouveaux membres, ce qui provoque des perplexités inutiles :

groups

Après un nouveau connexion, exécutez-le à nouveau :

groups

La sortie doit maintenant inclure les deux groupes :

video render

L’option --group-add keep-groups de Podman transmet ensuite ces groupes supplémentaires au conteneur sans racine afin qu’il puisse utiliser les dispositifs GPU.

Vérification du dispositif TUN

Un autre échec est survenu en raison du réseau sans racine. Pasta, le mécanisme de réseau sans racine par défaut de Podman, a indiqué qu’il ne pouvait pas ouvrir le dispositif TUN :

pasta failed with exit code 1:
Failed to open() /dev/net/tun: No such device

Recherchez le nœud du dispositif :

ls -l /dev/net/tun

Si il est manquant, chargez le module du noyau :

sudo modprobe tun

Vérifiez à nouveau. Si modprobe indique que le module n’existe pas pour le noyau en cours d’exécution, et que le système a été mis à jour récemment, comparez la version du noyau en cours d’exécution avec les répertoires des modules :

uname -r
ls /usr/lib/modules/

Dans une distribution itérative comme Arch, l’ordinateur peut encore exécuter un noyau plus ancien dont les modules ont été supprimés lors de la mise à niveau. Redémarrez et réessayez.

Choix d’un modèle local

Pour les cartes disposant d’environ 16 Go de VRAM, Ollama recommande actuellement gemma4 comme modèle local pour Hermes, tandis que les systèmes ayant près de 24 Go peuvent envisager des modèles tels que qwen3.6. L’exemple présenté ici utilise une version de gemma4 comptant 12 milliards de paramètres :

gemma4:12b

Stockez-le dans une variable, en ajustant le modèle pour le matériel réel :

export HERMES_MODEL="gemma4:12b"

Un autre paramètre important est la longueur du contexte. Par défaut, Ollama limite les GPU disposant de moins de 24 Go de VRAM à un contexte de 4K, tandis que les deux projets recommandent au moins 64 000 tokens lorsque des agents sont impliqués. Cette valeur peut être configurée explicitement plus loin.

Chargement d’images et du modèle

Trois images sont nécessaires : l’image Ollama standard, la version ROCm et l’image Hermes :

podman pull docker.io/ollama/ollama:latest
podman pull docker.io/ollama/ollama:rocm
podman pull docker.io/nousresearch/hermes-agent:latest

L’image Ollama standard gère le téléchargement unique du modèle. L’image ROCm devient le serveur d’inférence AMD permanent.

Téléchargez le modèle dans son volume persistant à l’aide d’un conteneur temporaire. Veuillez d’abord supprimer tout conteneur obsolète :

podman rm -f ollama-bootstrap 2>/dev/null

Ensuite, lancez le téléchargeur avec le volume du modèle monté, et rien d’autre :

podman run -d \
  --name ollama-bootstrap \
  -v ollama-models:/root/.ollama \
  docker.io/ollama/ollama:latest

Si le réseau de pâtes sans racine refuse toujours de fonctionner, le réseau hôte est acceptable uniquement pour ce téléchargeur temporaire. Supprimez à nouveau le conteneur :

podman rm -f ollama-bootstrap 2>/dev/null

Et commencez par le réseau hôte :

podman run -d \
  --network host \
  --name ollama-bootstrap \
  -v ollama-models:/root/.ollama \
  docker.io/ollama/ollama:latest

Aucun espace de travail Hermes n’est monté dans ce conteneur. Téléchargez le modèle à travers celui-ci :

podman exec -it ollama-bootstrap \
  ollama pull "$HERMES_MODEL"

Confirmer avec une fiche :

podman exec ollama-bootstrap ollama list

Le modèle sélectionné doit apparaître :

gemma4:12b

Retirez le conteneur temporaire. Le modèle reste dans le volume :

podman rm -f ollama-bootstrap

Si Podman signale que le nom du conteneur est déjà pris :

container name "ollama-bootstrap" is already in use

Ne continuez pas à inventer de nouveaux noms. Veuillez plutôt vider le conteneur obsolète :

podman rm -f ollama-bootstrap

ou ajoutez le flag --replace.

Un réseau interne et un pod

La première conception ne utilisait aucun réseau du tout :

--network none

Cela a été modifié pour utiliser un pont Podman interne dédié. Hermes et Ollama disposent d’un espace de noms réseau partagé leur permettant de communiquer entre eux, tandis que Podman restreint l’accès depuis l’extérieur. L’option --internal existe spécifiquement pour limiter l’accès externe sur les réseaux de type pont. Créez-le :

podman network create \
  --ignore \
  --internal \
  hermes-internal

Créez ensuite le pod sur ce réseau :

podman pod create \
  --name hermes-local \
  --network hermes-internal \
  --userns=keep-id:uid=10000,gid=10000

L’option keep-id place le compte hôte non privilégié dans l’espace de noms des utilisateurs du conteneur, et les nombres qui y apparaissent peuvent être choisis. Le conteneur Hermes s’exécute avec les identifiants 10000:10000, c’est pourquoi ces valeurs apparaissent.

Démarrer Ollama avec ROCm

L’un des problèmes moins évidents est apparu ici. La commande qui a finalement fonctionné ressemble à ceci :

podman run -d \
  --name ollama \
  --pod hermes-local \
  --device /dev/kfd \
  --device /dev/dri \
  --group-add keep-groups \
  -e HOME=/root \
  -e OLLAMA_MODELS=/root/.ollama/models \
  -e OLLAMA_CONTEXT_LENGTH=64000 \
  -v ollama-models:/root/.ollama \
  docker.io/ollama/ollama:rocm

Ses lignes spécifiques à AMD sont :

--device /dev/kfd
--device /dev/dri
--group-add keep-groups
ollama/ollama:rocm

Dans cette configuration Podman, deux variables d’environnement ne doivent pas être omises :

HOME=/root
OLLAMA_MODELS=/root/.ollama/models

La raison en est une défaillance énigmatique. Le modèle avait été téléchargé correctement, les fichiers et le manifeste existaient, et le volume était monté comme il se doit. Pourtant, la liste des modèles ne renvoyait absolument rien :

podman exec ollama ollama list

Le journal du conteneur racontait l’histoire :

podman logs ollama

Cela montrait Ollama utilisant un répertoire personnel situé à la racine du système de fichiers, ainsi qu’un chemin pour les modèles dérivé de celui-ci :

HOME=/
OLLAMA_MODELS=/.ollama/models

alors que le modèle vivait réellement à :

/root/.ollama/models

Ollama construit son chemin par défaut pour le modèle à partir du répertoire principal ainsi que de .ollama/models, à moins que OLLAMA_MODELS ne le modifie. Ainsi, le modèle n’a jamais manqué : Ollama cherchait simplement dans le mauvais endroit. En définissant explicitement les deux variables, le problème a été résolu sur-le-champ :

HOME=/root
OLLAMA_MODELS=/root/.ollama/models

Désormais, la fiche devrait afficher le modèle :

podman exec ollama ollama list

Vérifier que la GPU est utilisée

Recherchez dans le journal les lignes concernant ROCm et la GPU :

podman logs ollama 2>&1 | grep -Ei 'gpu|rocm|amd|gfx'

Une configuration AMD fonctionnelle identifie un dispositif de calcul ROCm. Ensuite, exécutez une commande de test rapide qui demande au modèle de fournir une réponse exacte :

podman exec -it ollama \
  ollama run gemma4:12b \
  "Reply with exactly: AMD GPU test successful"

Immédiatement après, consultez les modèles en cours d’exécution :

podman exec ollama ollama ps

Deux colonnes méritent une attention particulière :

PROCESSOR
CONTEXT

Idéalement, la colonne du processeur afficherait le GPU plutôt que le transfert de charge vers le CPU, et le contexte concerne :

64000

Ollama recommande lui-même de vérifier la liste de ses processus afin de voir à la fois le contexte alloué et le téléchargement externe. Un modèle qui affiche un contexte natif très important n’obtient pas nécessairement autant de mémoire allouée.

Démarrer Hermes avec un seul montage

Créez maintenant le conteneur Hermes à l’intérieur du même pod :

podman run -d \
  --name hermes \
  --pod hermes-local \
  --security-opt=no-new-privileges \
  --pids-limit 512 \
  -v hermes-data:/opt/data \
  -v "$HERMES_WORKSPACE:/opt/data/workspace:rw,nodev,nosuid" \
  -w /opt/data/workspace \
  docker.io/nousresearch/hermes-agent:latest \
  sleep infinity

Ce qui compte, c’est ce qui lui manque. Aucun de ces éléments n’apparaît :

/:/host
/home:/home
~/.ssh
~/.config
Docker socket
Podman socket

Le seul paramètre d’association d’hôte arbitraire est le dossier de travail, qui est mappé dans le répertoire de données du conteneur :

$HERMES_WORKSPACE
        ↓
/opt/data/workspace

L’état propre d’Hermes se trouve dans le volume nommé, qui est monté sur son chemin de données :

hermes-data
        ↓
/opt/data

Le conteneur officiel utilise /opt/data pour le stockage persistant et définit automatiquement HERMES_WRITE_SAFE_ROOT sur cette cheminée. Cette protection contre les écritures constitue une mesure de défense en profondeur, et non le sandbox principal, car la documentation d’Hermes met en garde contre le fait que les commandes de terminal peuvent contourner ses restrictions relatives aux outils de fichiers. La véritable limite est l’espace de noms du système de fichiers de Podman.

Vérification de la limite de montage

Examinez ce que l’hôte expose réellement à Hermes :

podman inspect hermes \
  --format '{{range .Mounts}}{{println .Type .Source "->" .Destination}}{{end}}'

Attendez deux lignes, une pour le volume de données et une pour le dossier autorisé :

Podman volume -> /opt/data
your allowed folder -> /opt/data/workspace

Le répertoire home et le socket de container-runtime ne doivent pas apparaître. Vérifiez explicitement le socket :

podman exec hermes sh -c \
  'test ! -S /var/run/docker.sock && echo "No Docker socket exposed"'

Les chemins sensibles à l’hôte peuvent également être vérifiés, par exemple en demandant si un répertoire SSH est visible :

podman exec hermes sh -lc \
  'test -e "$HOME/.ssh" && echo "SSH directory visible" || echo "Host SSH directory not visible"'

N’oubliez pas que le conteneur dispose de ses propres répertoires système. Il est normal de les voir, car ils appartiennent au conteneur et non à l’hôte.

Hermes atteint Ollama

Les deux conteneurs partagent l’espace de noms réseau du pod, ce qui leur permet de communiquer via un routage circulaire. Testez l’API compatible OpenAI d’Ollama depuis l’intérieur du conteneur Hermes :

podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("http://127.0.0.1:11434/v1/models").read().decode())'

La réponse doit être un JSON contenant le modèle local. Une première tentative a utilisé un heredoc en Python sans afficher rien :

podman exec hermes python - <<'PY'
...
PY

La cause était simple. Python lit son programme depuis stdin sous cette forme, mais podman exec n’avait pas reçu l’instruction d’associer stdin. L’ajout du flag interactif fait fonctionner la version heredoc :

podman exec -i hermes python - <<'PY'
import urllib.request
print(
    urllib.request.urlopen(
        "http://127.0.0.1:11434/v1/models"
    ).read().decode()
)
PY

Ce drapeau manquant a coûté quelques minutes supplémentaires.

Prouver que le monde extérieur est inatteignable

Essayez de récupérer un site web externe depuis l’intérieur du conteneur d’Hermes :

podman exec hermes python -c \
'import urllib.request; print(urllib.request.urlopen("https://example.com", timeout=5).read())'

Cela doit échouer, et c’est intentionnel. Hermes peut atteindre Ollama à l’intérieur du conteneur, mais le réseau ne permet aucun accès externe normal.

Ne paniquez pas si la liste des conteneurs affiche :

podman ps

affiche un port à côté d’Ollama :

11434/tcp

Cela ne signifie pas que le port a été publié sur l’hôte. Un port publié ressemblerait plutôt à ceci :

0.0.0.0:11434->11434/tcp

Aucune occurrence de -p 11434:11434 n’est utilisée dans cette configuration.

Diriger Hermes vers Ollama local

Démarrez la configuration de Hermes en tant qu’utilisateur du conteneur, dans l’espace de travail :

podman exec -it \
  --user 10000:10000 \
  -w /opt/data/workspace \
  hermes \
  hermes model

Le sélecteur de fournisseur liste de nombreux services cloud. Ne choisissez pas l’option cloud :

Ollama Cloud

Choisissez plutôt l’entrée d’endpoint manuel :

Custom endpoint (enter URL manually)

Pour l’URL de base, utilisez l’adresse de boucle locale et le chemin v1 :

http://127.0.0.1:11434/v1

Si une clé API est demandée, laissez-la vide. Les notes actuelles d’intégration d’Hermes d’Ollama utilisent précisément ce flux de point de terminaison personnalisé avec une clé vide pour un serveur local. Lorsqu’on vous demande à propos de la compatibilité API, choisissez l’option de complétion de chat compatible avec OpenAI. Pour le nom du modèle, saisissez :

gemma4:12b

Et pour la longueur du contexte :

64000

La documentation du fournisseur d’Hermes indique que l’Ollama local doit utiliser l’endpoint v1 et a besoin d’au moins 64 K de contexte pour les workflows d’agent qui utilisent des outils.

Utilisation du backend de terminal local, à l’intérieur du conteneur

Cela peut sembler étrange au premier abord. Hermes avertit normalement que sa configuration de backend de terminal local :

terminal:
  backend: local

exécute des commandes de terminal directement sur la même machine que Hermes, sans aucune isolation. Sur une installation directe de Hermes, cela serait dangereux. Ici, Hermes est déjà intégré à Podman, de sorte que le backend local fait référence au conteneur Hermes :

Hermes "local"
      ↓
Hermes container

et non à l’ordinateur de travail :

Hermes "local"
      ↓
your Arch workstation

Définir le backend :

podman exec --user 10000:10000 hermes \
  hermes config set terminal.backend local

Et le répertoire de travail :

podman exec --user 10000:10000 hermes \
  hermes config set terminal.cwd /opt/data/workspace

Il est également utile de définir une préférence pour un mode d’accueil basé sur le profil :

podman exec --user 10000:10000 hermes \
  hermes config set terminal.home_mode profile

Ne configurez pas le backend Docker d’Hermes pour cette conception. Cela nécessiterait de donner à Hermes un accès à un environnement d’exécution de conteneurs, ce qui annulerait l’une des principales décisions en matière d’isolation.

Démarrer Hermes

Une session interactive débute avec le même schéma d’exécution utilisé pour la configuration :

podman exec -it \
  --user 10000:10000 \
  -w /opt/data/workspace \
  hermes \
  hermes

Afin de nettoyer les notes, une instruction pourrait demander à Hermes d’examiner les notes Markdown présentes dans l’espace de travail, de mettre de l’ordre dans la structure et les titres, de corriger la mise en forme et la grammaire, de conserver tous les détails techniques, commandes, liens et liens wikilink d’Obsidian tels quels, et de ne jamais supprimer du contenu réel simplement pour raccourcir une note. Les modifications sont appliquées directement dans le dossier hôte monté.

Démarrer l’environnement au démarrage

Lorsque tout fonctionne de manière interactive, le pod devrait se réinitialiser automatiquement après un redémarrage, sans nécessiter de démarrer manuellement Ollama et Hermes. Créez une unité systemd utilisateur :

mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/hermes-local.service

La unité commence par une description et une ligne d’ordre :

[Unit]
Description=Hermes Local AI Pod
After=default.target

La section de service est une opération unique qui lance le pod, l’arrête avec une période de grâce de 30 secondes et laisse deux minutes pour qu’il redémarre :

[Service]
Type=oneshot
RemainAfterExit=yesExecStart=/usr/bin/podman pod start hermes-local
ExecStop=/usr/bin/podman pod stop -t 30 hermes-localTimeoutStartSec=120
TimeoutStopSec=60[Install]
WantedBy=default.target

Si la racine du graphe de Podman se trouve sur un disque séparé, ce disque doit être monté de manière fiable au démarrage, et non être quelque chose que le bureau monte de manière interactive après connexion. Une vérification de montage ExecStartPre optionnelle adaptée au système de fichiers peut être ajoutée.

Recharger systemd :

systemctl --user daemon-reload

Activer l’unité :

systemctl --user enable hermes-local.service

Démarrer :

systemctl --user start hermes-local.service

Et vérifiez :

systemctl --user status hermes-local.service

Puisqu’il s’agit d’un service à usage unique avec RemainAfterExit activé, cet état est normal :

Active: active (exited)

Un mécanisme Quadlet plus récent existe dans Podman pour définir des conteneurs de manière déclarative sous systemd. Cependant, avec un environnement déjà assemblé et testé manuellement, un petit service utilisateur qui lance le pod connu pour fonctionner perturbait le moins.

Pour permettre au service utilisateur sans compte root de démarrer au démarrage, même avant de se connecter au bureau, activez « lingering » :

sudo loginctl enable-linger "$USER"

Vérifiez-le :

loginctl show-user "$USER" -p Linger

Résultat attendu :

Linger=yes

La fonction linger de Systemd permet au gestionnaire de services de l’utilisateur de démarrer au démarrage de l’ordinateur et de rester disponible en dehors des sessions de connexion normales. Après un redémarrage, la liste des conteneurs :

podman ps

Le pod devrait déjà afficher Ollama et Hermes en cours d’exécution. Une session interactive avec Hermes démarre néanmoins avec la commande exec :

podman exec -it \
  --user 10000:10000 \
  -w /opt/data/workspace \
  hermes \
  hermes

Le service de démarrage lance l’environnement. Il n’ouvre pas de chat interactif.

Problèmes qui se sont réellement produits

Voici la liste condensée des pannes rencontrées, présentées chacune en tant que problème et sa solution.

Une erreur d’overlay provenant du noyau :

Problem:
kernel does not support overlay fs

Le problème a été résolu en installant fuse-overlayfs et en le définissant comme programme de montage overlay :

Fix:
Install fuse-overlayfs and configure it as the overlay mount program.

Espace insuffisant dans le répertoire temporaire :

Problem:
no space left on device under /var/tmp

Il fallait à la fois un répertoire racine du graphe déplacé le cas échéant et une valeur pour TMPDIR, car le paramètre tmpdir de Podman est une notion distincte :

Fix:
Move Podman's graphroot if needed AND set TMPDIR.
Changing Podman's --tmpdir is not the same thing.

Conflit de nom pour le conteneur bootstrap :

Problem:
ollama-bootstrap name already in use

Celui-ci est résolu en supprimant le conteneur obsolète :

Fix:
podman rm -f ollama-bootstrap

Pasta échoue à ouvrir le dispositif TUN :

Problem:
pasta cannot open /dev/net/tun

Celui-ci est résolu en chargeant le module tun, ou en redémarrant si les modules installés ne correspondent pas au noyau en cours d’exécution :

Fix:
sudo modprobe tun
If the installed modules don't match the running kernel, reboot.

Un outil NVIDIA installé sur une machine AMD :

Problem:
Installed nvidia-container-toolkit on an AMD machine

Cela ne devrait tout simplement pas être fait ; il faut plutôt utiliser l’image ROCm avec les dispositifs kfd et dri :

Fix:
Don't.
Use ollama/ollama:rocm with /dev/kfd and /dev/dri.

Nouveaux membres de groupes de vidéos et de rendus qui n’apparaissaient pas dans la liste des groupes :

Problem:
Added myself to video/render but `groups` still didn't show them

Il est nécessaire de se déconnecter puis de se reconnecter, car la session existante conserve ses groupes originaux :

Fix:
Log out and back in.
The existing login session retains its original supplementary groups.

Fichiers de modèle et manifeste présents tandis que la liste d’Ollama est vide :

Problem:
Ollama model files and manifest exist, but `ollama list` is empty

le diagnostic se fait en consultant le journal ; si les chemins home et models pointent vers la racine du système de fichiers tandis que le volume est monté dans le répertoire de l’utilisateur racine, il convient de définir explicitement les deux variables :

Fix:
Check:
podman logs ollamaIf Ollama is using:
OLLAMA_MODELS=/.ollama/modelswhile the volume is mounted under:
 /root/.ollamaset explicitly:
HOME=/root
OLLAMA_MODELS=/root/.ollama/models

Un test en Python de Hermes vers Ollama qui n’a rien affiché en silence :

Problem:
Hermes → Ollama Python test silently prints nothing

Celui-ci a été résolu en ajoutant le drapeau interactif lors de l’utilisation d’un heredoc, ou simplement en utilisant la forme de commande en ligne :

Fix:
If using `python -` with a heredoc, add `podman exec -i`.
Or simply use `python -c`.

Un menu des fournisseurs ne présentant aucune entrée locale Ollama évidente :

Problem:
The Hermes provider menu doesn't contain "local Ollama"

Le problème a été résolu en choisissant l’endpoint personnalisé manuel et en entrant l’URL locale v1 :

Fix:
Choose:
Custom endpoint (enter URL manually)Then:
http://127.0.0.1:11434/v1

Et Hermès se plaignant d’une fenêtre de contexte insuffisante, bien que tout le reste fonctionnât correctement :

Problem:
Everything works, but Hermes reports an inadequate context window

Le problème se résout en définissant la longueur du contexte du côté serveur à 64000, en configurant Hermes avec la même valeur et en vérifiant l’allocation réelle à l’aide de la liste des processus d’Ollama :

Fix:
Set OLLAMA_CONTEXT_LENGTH=64000 server-side and configure Hermes for the same value.
Verify the real allocation with:
ollama ps

Où se trouve la frontière de confiance

La leçon principale ici ne concerne pas vraiment Ollama ou ROCm. Il s’agit plutôt de décider où se situe la frontière de confiance. Une instruction demandant de ne jamais lire de fichiers en dehors d’un répertoire constitue une directive comportementale. Un conteneur Podman auquel on n’a jamais donné accès au reste du système de fichiers hôte représente une frontière de système d’exploitation. Le système de racine sécurisé contre les écritures d’Hermes est utile, mais Hermès le décrit lui-même comme une couche supplémentaire et non comme un véritable sandbox, puisque les commandes de shell peuvent contourner la protection mise en place par les outils de gestion des fichiers.

Le modèle de confiance résultant empile ces couches du plus faible au plus fort, en commençant par les restrictions imposées par les instructions et la protection contre les écritures de fichiers d’Hermes, pour se terminer par la frontière du conteneur :

Prompt restrictions
        ↓
Hermes file write protections
        ↓
Hermes container filesystem
        ↓
Rootless Podman user namespace
        ↓
Host filesystem permissions

Même si le modèle reçoit une instruction malveillante cachée dans un fichier Markdown, la question importante n’est pas de savoir s’il obéira à l’instruction initiale, mais plutôt ce à quoi l’agent compromis peut réellement accéder. Dans cette conception, la réponse est délibérément limitée.

Débogage par étapes

Hermes et Ollama évoluent rapidement. La documentation actuelle d’Hermes prend en charge des points de terminaison compatibles avec OpenAI hébergés localement, y compris Ollama, mais il existe également des rapports provenant de la communauté concernant le comportement des appels d’outils via des points de terminaison personnalisés d’Ollama. Si la conversation ordinaire fonctionne mais qu’Hermes semble décrire des appels d’outils sans les exécuter, vérifiez le suivi des problèmes actuel d’Hermes avant de supposer que le sandbox Podman est défectueux.

C’est pourquoi il convient de tester l’ensemble du système en tant que chaîne de questions : d’abord pour savoir si Podman détecte la GPU, ensuite si Ollama perçoit le modèle, et enfin si Hermes peut communiquer avec Ollama :

Does Podman see the GPU?
        ↓
Does Ollama see the model?
        ↓
Does Ollama actually use the GPU?
        ↓
Is the context really 64K?
        ↓
Can Hermes reach /v1/models?
        ↓
Can Hermes perform an actual file operation?
        ↓
Can Hermes reach anything it shouldn't?

Ne tentez pas de déboguer les six couches en même temps.

Ce que fournit la configuration finale

La configuration résultante permet l’inférence locale, l’accélération par GPU AMD, une mémoire Hermes persistante et un espace de travail hôte écrivable :

Local inference                YES
AMD GPU acceleration           YES
Hermes persistent memory       YES
One writable host workspace    YES

Et il n’a pas besoin de modèle cloud, ne met pas en exposition de système de fichiers hôte, ne met pas en exposition de socket Podman ou Docker, et ne dispose d’aucune sortie Internet normale :

Cloud LLM required             NO
Host filesystem exposed        NO
Podman/Docker socket exposed   NO
Normal Internet egress         NO
Entire Obsidian vault exposed  NO

C’est une manière bien plus confortable d’expérimenter avec un agent local performant. Hermes dispose encore d’un accès suffisant pour être utile. Il ne peut simplement pas décider par lui-même ce que signifie un accès suffisant.