Strona główna / Artykuły

Uruchamianie Hermes Agent lokalnie w Rootless Podman z Ollamą na karcie graficznej AMD

Uruchamianie Hermes Agent lokalnie i bezpiecznie: Podman bez uprawnień root, jedna zamontowana folder, sieć wewnętrzna, Ollama na karcie graficznej AMD z ROCm oraz jednostka systemd.

Wydawca: REACTAPP.TOP20 min czytania4524 słów

Źródła: Run Hermes Agent Locally (and securely) with Ollama and Rootless Podman on Arch Linux

Uruchamianie agenta AI na lokalnych notatkach w formacie Markdown i Obsidian wydaje się proste, dopóki nie pojawia się kwestia dostępu. Hermes Agent od Nous Research może wykonywać polecenia w terminalu, edytować pliki, uruchamiać kod oraz korzystać z innych narzędzi. Przekazanie mu modelu z instrukcją dotykania tylko jednej folderu nie tworzy bariery bezpieczeństwa, ponieważ instrukcja to po prostu tekst. Dokumentacja samego Hermesa wskazuje to samo: ustawienie HERMES_WRITE_SAFE_ROOT chroni standardowe narzędzia do edycji plików, ale narzędzie terminalowe nadal może uzyskać dostęp do wszystkiego, do czego może uzyskać dostęp konto systemu operacyjnego uruchamiającego Hermesa.

Przedstawiona poniżej konfiguracja umieszcza sam Hermes wewnątrz bezkorzeniowego Podman, obok serwera Ollama uruchamiającego lokalny model na karcie graficznej AMD. Hermes może edytować dokładnie jedną folder, który został specjalnie zamontowany. Reszta systemu plików hosta po prostu nie istnieje w przestrzeni nazw kontenera, więc nie może tam trafić. Hermes oficjalnie obsługuje działanie w kontenerze, przechowuje trwały stan w katalogu /opt/data i współpracuje z samodzielnie hostowanymi serwerami inferencji, takimi jak Ollama, za pośrednictwem punktu końcowego kompatybilnego z OpenAI.

Przejście krok po kroku zostało przeprowadzone na EndeavourOS, który jest odmianą Arch. Komendy dotyczące pakietów należy dostosować do innych dystrybucji.

Instalacja bezkorzeniowego Podman

W systemach Arch lub EndeavourOS zainstaluj Podman za pomocą Pacmana:

sudo pacman -Syu podman

Pacman może zapytać, który silnik OCI użyć, oferując te trzy opcje:

1) crun
2) krun
3) runc

Wybrana tutaj opcja jest pierwszą:

1

Ten silnik jest crun. Przynosi korzyści później, ponieważ funkcja keep-groups w Podmanie, która przekazuje dodatkowe grupy hosta do kontenera bez roota, działa z crunem.

Zatwierdź instalację, wydrukowując wersję i pytając Podman, jaki silnik jest używany:

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

Drugi polecenie powinno odpowiedzieć nazwą w czasie wykonywania:

crun

Kontenery bez korzenia również wymagają podporządkowanych map UID i GID dla użytkownika, więc sprawdź, czy takie wpisy istnieją w obu plikach:

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

Od teraz wykonywaj wszystkie polecenia Podman jako zwykły użytkownik. Nie dodawaj przed nimi sudo.

Naprawa rootless OverlayFS w razie potrzeby

Pierwsze pobranie obrazu na tym komputerze zakończyło się niepowodzeniem z błędem informującym, że jądro nie obsługuje funkcji overlay na systemie plików ext:

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

Gdy to się stanie, zainstaluj implementację w przestrzeni użytkownika:

sudo pacman -S fuse-overlayfs

Podman zaleca użycie fuse-overlayfs w konfiguracjach bez roota, gdy wbudowany overlay nie jest dostępny, i można je wyraźnie wskazać jako program montowania w trybie bez roota. Sprawdź, czy jest zainstalowane:

which fuse-overlayfs

Oczekiwana ścieżka to:

/usr/bin/fuse-overlayfs

Konfiguracja znajduje się w pliku przypisanym do każdego użytkownika:

~/.config/containers/storage.conf

Jeśli już istnieje, edytuj go zamiast go ślepo zastępować. W sekcji przechowywania należy wybrać sterownik nakładki:

[storage]
driver = "overlay"

A drugi rozdział powinien podać nazwę programu montowania:

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

Następnie sprawdź efektywne ustawienia, filtrowując wyjście debugowe Podmana:

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

Przenoszenie plików do większego dysku

W tym projekcie krok ten okazał się konieczny niemal od razu. Sam obraz kontenera Ollama zajmuje kilka gigabajtów, modele często dodają kolejne 5 do 20 GB lub więcej, a Podman również wykorzystuje tymczasową przestrzeń dyskową podczas pobierania i rozpakowywania warstw. Pierwsza próba pobrania Ollamy zakończyła się błędem informującym o braku miejsca na ścieżce wewnątrz katalogu tymczasowego:

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

W grę wchodzą dwa oddzielne miejsca, które łatwo przeoczyć. Trwałe przechowywanie bez korzenia dla obrazów, kontenerów i woluminów zazwyczaj znajduje się pod katalogiem domowym:

$HOME/.local/share/containers/storage

Pobrane warstwy trafiają tymczasowo do:

/var/tmp

Zmiana wartości --tmpdir w Podmanie nie przenosi katalogu pobierania plików. W dokumentacji Podman zaleca się zamiast tego użycie zmiennej środowiskowej TMPDIR.

Gdy masz do dyspozycji większy lokalny system plików, utwórz strukturę składającą się z jednego katalogu do przechowywania plików i jednego do plików tymczasowych:

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

Stwórz oba i przekaż prawa własności bieżącemu użytkownikowi:

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

Następnie otwórz konfigurację przechowywania w celu edycji:

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

Skieruj korzeń wykresu na nowy katalog przechowywania, zachowując jednocześnie sterownik warstwy nadrukowanej:

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

I zachowaj ustawienie programu montowania fuse-overlayfs:

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

Wybierz dla tego lokalny system plików. Dokumentacja Podmana ostrzega, że NFS oraz inne systemy plików sieciowych mogą zachowywać się niewłaściwie jako magazyn plików w kontenerach bez uprawnień root.

Pliki pobrane tymczasowo również wymagają przeniesienia, więc ustaw TMPDIR dla bieżącego shella:

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

Aby uczynić to trwałym w Bash, dodaj to do profilu i załaduj go ponownie:

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

Zanim usuniesz cokolwiek ze starego miejsca, sprawdź nowe lokalizacje. Wydrukuj korzeń drzewa i przefiltruj wyniki debugowania dla katalogu tymczasowego oraz programu montowania:

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

Tylko przy świeżej instalacji Podman, i tylko wtedy, gdy nowy korzeń grafu jest bez wątpienia poprawny, należy rozważyć usunięcie porzuconych zasobów przechowywania z starej lokalizacji:

~/.local/share/containers/storage

Nie rób tego przypadkowo na istniejącej instalacji. To także powód, dla którego lokalizacja przechowywania powinna zostać ustalona przed utworzeniem wolumenu modelu Ollama.

Jeden folder dla Hermesa, dwa wolumeny dla stanu

Wybierz jeden katalog hosta, do którego może uzyskać dostęp Hermes, i wybierz go celowo. Eksportuj jego ścieżkę do zmiennej i utwórz folder:

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

W przypadku Obsidian dobrym wyborem jest dedykowana folder wewnątrz sejfu, na przykład katalog badawczy umieszczony obok folderów osobistych i służbowych:

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

Wszystko inne w sejfie pozostaje niewidoczne dla agenta.

Następnie utwórz dwa woluminy, którymi samodzielnie zarządza Podman:

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

Pierwszy przechowuje konfigurację, pamięć oraz stan trwały Hermesa. Drugi przechowuje lokalny model. Żaden z nich nie wymaga udostępniania dodatkowych katalogów hosta agentowi.

Udostępnianie kontenerowi dostępu do karty graficznej AMD

To rozwiązanie jest przeznaczone dla AMD ROCm, a nie dla NVIDIA CUDA. Na karcie AMD nie należy instalować nvidia-container-toolkit – to obejście, którego użyto wcześniej, zanim zauważono błąd. Instrukcje dotyczące kontenerów Ollamy wykorzystują obraz ollama/ollama:rocm dla kart graficznych AMD i przekazują do kontenera dwa węzły urządzeń:

/dev/kfd
/dev/dri

Sprawdź, czy istnieją na serwerze:

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

Następnie sprawdź grupy konta:

groups

Jeśli konto nie występuje w grupach wideo i renderowania, dodaj je:

sudo usermod -aG video,render "$USER"

Następnie wyloguj się całkowicie i zaloguj ponownie. Ponowne uruchomienie listy grup w tym samym terminalu nie pokaże nowych członków, co powoduje niepotrzebne zakłopotanie:

groups

Po ponownym zalogowaniu uruchom to jeszcze raz:

groups

Wynik powinien teraz zawierać obie grupy:

video render

Opcja --group-add keep-groups w Podmanie przekazuje później te dodatkowe grupy do kontenera bez roota, aby mógł on korzystać z urządzeń GPU.

Sprawdzanie urządzenia TUN

Kolejny problem pojawił się w związku z sieciowaniem bez roota. Pasta, domyślny mechanizm sieciowy Podmana bez roota, zgłosiła błąd, że nie może otworzyć urządzenia TUN:

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

Poszukaj węzła urządzenia:

ls -l /dev/net/tun

Jeśli jest brakujący, załaduj moduł jądra:

sudo modprobe tun

Następnie sprawdź ponownie. Jeśli modprobe informuje, że moduł nie istnieje dla aktualnie uruchomionego jądra, a system został niedawno zaktualizowany, porównaj wersję aktualnie uruchomionego jądra z katalogami modułów:

uname -r
ls /usr/lib/modules/

W dystrybucji typu rolling, takiej jak Arch, komputer może nadal używać starszego jądra, którego moduły zostały usunięte podczas aktualizacji. Uruchom ponownie komputer i spróbuj jeszcze raz.

Wybór modelu lokalnego

Dla kart z około 16 GB pamięci VRAM Ollama zaleca obecnie gemma4 jako model lokalny dla Hermes, natomiast systemy z około 24 GB mogą rozważyć modele takie jak qwen3.6. Przykład tutaj przedstawia wariant gemma4 z 12 miliardami parametrów:

gemma4:12b

Zachowaj to w zmiennej, dostosowując model do rzeczywistego sprzętu:

export HERMES_MODEL="gemma4:12b"

Drugą ważną wartością jest długość kontekstu. Ollama domyślnie przydziela karty graficzne z mniej niż 24 GiB pamięci VRAM tylko kontekstowi 4K, podczas gdy oba projekty zalecają co najmniej 64 000 tokenów, gdy zaangażowane są agenci. To zostanie dokładniej skonfigurowane później.

Ładowanie obrazów i modelu

Potrzebne są trzy obrazy: zwykły obraz Ollamy, wersja ROCm oraz obraz Hermes:

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

Zwykły obraz Ollamy zajmuje się jednorazowym pobieraniem modelu. Obraz ROCm staje się trwałym serwerem inferencji AMD.

Pobierz model do jego wolumenu trwałego za pomocą tymczasowego kontenera. Najpierw usuń wszelkie przestarzałe obrazy:

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

Następnie uruchom program pobierania z zamontowanym wolumenem modelu i niczym więcej:

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

Jeśli sieć z użyciem bezkorzeniowych plików pasta nadal nie działa, dołączenie do sieci hostowej jest akceptowalne wyłącznie dla tego tymczasowego programu do pobierania. Usuń ponownie kontener:

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

I zacznij od sieci hosta:

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

W tym kontenerze nie ma zamontowanego żadnego stanowiska pracy Hermes. Pobierz model przez niego:

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

Potwierdź za pomocą listingu:

podman exec ollama-bootstrap ollama list

Wybrany model powinien się pojawić:

gemma4:12b

Usuń tymczasowy pojemnik. Model pozostaje w tym obszarze:

podman rm -f ollama-bootstrap

Jeśli Podman skarży się, że nazwa kontenera jest już zajęta:

container name "ollama-bootstrap" is already in use

Nie wymyślaj już nowych nazw. Lepiej wyczyść stary kontener:

podman rm -f ollama-bootstrap

lub dodaj flagę --replace.

Sieć wewnętrzna i pod

Pierwszy projekt w ogóle nie wykorzystywał sieci:

--network none

To zostało zmienione na dedykowany wewnętrzny most Podman. Hermes i Ollama korzystają z wspólnego przestrzeni nazw sieciowej, w której mogą ze sobą komunikować się, podczas gdy Podman ogranicza dostęp z zewnątrz. Opcja --internal istnieje właśnie po to, aby ograniczyć dostęp z zewnątrz w sieciach mostowych. Stwórz ją:

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

Następnie utwórz pod w tej sieci:

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

Opcja keep-id umieszcza konto hosta bez uprawnień w przestrzeni nazw użytkownika kontenera, a liczby, które się tam pojawiają, można wybrać. Kontener Hermes działa pod identyfikatorem 10000:10000, dlatego właśnie pojawiają się te wartości.

Rozpoczynanie Ollamy z ROCm

Pojawił się tu jeden z mniej oczywistych problemów. Komenda, która w końcu zadziałała, wygląda tak:

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

Jego linie specyficzne dla AMD to:

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

W tym układzie Podman nie należy pominąć dwóch zmiennych środowiskowych:

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

Powodem jest zagadkowa awaria. Model został poprawnie pobraany, pliki blob oraz manifest istniały, a wolumen został prawidłowo zamontowany. Mimo to wyświetlenie listy modeli nie przyniosło żadnych wyników:

podman exec ollama ollama list

Log kontenera opowiadał tę historię:

podman logs ollama

Pokazało to Ollamę używającego katalogu domowego u góry systemu plików oraz ścieżki do modeli wyprowadzonej z niego:

HOME=/
OLLAMA_MODELS=/.ollama/models

chociaż model faktycznie mieszkał w:

/root/.ollama/models

Ollama buduje swoją domyślną ścieżkę do modelu na podstawie katalogu domowego plus .ollama/models, chyba że OLLAMA_MODELS ją zmienia. Dlatego model nigdy nie był nieobecny – Ollama szukał w niewłaściwym miejscu. Jasne ustawienie obu zmiennych naprawiło to od razu:

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

Teraz opis powinien pokazywać model:

podman exec ollama ollama list

Potwierdzenie użycia karty graficznej

Poszukaj w dzienniku linii dotyczących ROCm i karty graficznej:

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

Pracujące rozwiązanie AMD identyfikuje urządzenie obliczeniowe ROCm. Następnie uruchamia szybki test, który prosi model o dokładną odpowiedź:

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

Bezpośrednio potem sprawdź działające modele:

podman exec ollama ollama ps

Dwie kolumny wymagają uwagi:

PROCESSOR
CONTEXT

W idealnym przypadku kolumna z procesorem powinna pokazywać GPU, a nie przeniesienie obciążenia na CPU, przy czym kontekst dotyczy następującego:

64000

Sama Ollama zaleca sprawdzenie listy procesów, aby zobaczyć zarówno przydzielony kontekst, jak i proces przenoszenia danych. Model, który deklaruje bardzo duży lokalny kontekst, niekoniecznie otrzymuje aż tyle zasobów.

Rozpoczynanie działania Hermes z jednym mountem

Następnie utwórz kontener Hermes wewnątrz tego samego poda:

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

Ważne jest to, czego w nim brakuje. Żadne z nich się nie pojawia:

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

Jedynym dowolnym powiązaniem hosta jest folder przestrzeni roboczej, mapowany do katalogu danych kontenera:

$HERMES_WORKSPACE
        ↓
/opt/data/workspace

Własny stan Hermesa znajduje się w wymienionym wolumenie, który jest zamontowany na jego ścieżce danych:

hermes-data
        ↓
/opt/data

Oficjalny kontener używa /opt/data do przechowywania trwałych danych i automatycznie ustawia w tym miejscu wartość HERMES_WRITE_SAFE_ROOT. Ten mechanizm ochrony przed zapisem stanowi element zasady obrony na wielu poziomach, a nie główną przegrodę bezpieczeństwa, ponieważ dokumentacja Hermes ostrzega, że polecenia terminalowe mogą obejść ograniczenia związane z narzędziami do pracy z plikami. Prawdziwą granicą jest przestrzeń nazw systemu plików Podmana.

Weryfikacja granicy montowania

Zbadaj, co host faktycznie udostępnia Hermesowi:

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

Oczekuj dwóch wierszy: jednego z informacją o pojemności danych, a drugiego o dozwolonej folderze:

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

Katalog domowy oraz sokiet container-runtime nie powinny się pojawiać. Sprawdź sokiet wyraźnie:

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

Można również sprawdzić ścieżki wrażliwe na hosta, na przykład pytając, czy katalog SSH jest widoczny:

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

Pamiętaj, że kontener ma swoje własne katalogi systemowe. Ich widoczność jest normalna, ponieważ należą one do kontenera, a nie do hosta.

Hermes łączy się z Ollamą

Oba kontenery dzielą się przestrzenią nazw sieciową poda, więc komunikują się za pośrednictwem loopback. Sprawdź API Ollamy kompatybilne z OpenAI z wnętrza kontenera Hermes:

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

Odpowiedź powinna być plikiem JSON zawierającym lokalny model. Pierwsza próba wykorzystała heredoc w Pythonie i nic nie wyświetliła:

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

Powód był prosty. Python odczytuje swój program z stdin w tej formie, ale nie poinstruowano podman exec o podłączeniu stdin. Dodanie flagi interaktywnej sprawia, że wersja z heredoc działa:

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

Brakująca flaga kosztowała kilka dodatkowych minut.

Dowodzenie, że świat zewnętrzny jest niedostępny

Spróbuj pobrać stronę internetową z zewnątrz z wnętrza kontenera Hermesa:

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

Powinno to zawieść, i to jest zamierzone. Hermes może dotrzeć do Ollamy wewnątrz kontenera, ale sieć nie zapewnia normalnego dostępu z zewnątrz.

Nie panikuj, jeśli lista kontenerów wygląda w ten sposób:

podman ps

pokazuje port obok Ollamy:

11434/tcp

To nie oznacza, że port został opublikowany na hostingu. Opublikowany port wyglądałby mniej więcej tak:

0.0.0.0:11434->11434/tcp

W tym ustawieniu nigdzie nie użyto -p 11434:11434.

Skierowanie Hermesa na lokalnego Ollamę

Rozpocznij konfigurację Hermesa jako użytkownik kontenera, pracując w przestrzeni roboczej:

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

Wybieracz dostawców pokazuje wiele usług chmurowych. Nie wybieraj opcji chmury:

Ollama Cloud

Wybierz zamiast tego ręczne wprowadzenie punktu końcowego:

Custom endpoint (enter URL manually)

Dla adresu bazowego użyj adresu loopback oraz ścieżki v1:

http://127.0.0.1:11434/v1

Jeśli zostanie poproszona o klucz API, pozostaw go pusty. Obecne notatki dotyczące integracji Hermes w Ollamie używają dokładnie tego schematu dostępu przez specjalny punkt końcowy z pustym kluczem dla serwera lokalnego. Gdy zostanie zadaane pytanie o kompatybilność API, wybierz opcję chat-completions kompatybilną z OpenAI. Jako nazwę modelu wpisz:

gemma4:12b

a jeśli chodzi o długość kontekstu:

64000

Dokumentacja dostawcy Hermes mówi, że lokalny Ollama powinien używać punktu końcowego v1 i wymaga co najmniej 64K kontekstu dla procesów agenta wykorzystujących narzędzia.

Używanie lokalnego backendu terminala wewnątrz kontenera

Na początku może to wydawać się dziwne. Hermes zazwyczaj ostrzega przed ustawieniem lokalnego backendu terminala:

terminal:
  backend: local

wykonuje polecenia terminala bezpośrednio na tej samej maszynie co Hermes, bez żadnej izolacji. W przypadku bezpośrednio zainstalowanego Hermes byłoby to niebezpieczne. Tutaj sam Hermes znajduje się już wewnątrz Podman, więc lokalny backend odwołuje się do kontenera Hermes:

Hermes "local"
      ↓
Hermes container

a nie do stanowiska roboczego:

Hermes "local"
      ↓
your Arch workstation

Ustaw backend:

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

A katalog roboczy:

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

Warto również ustawić preferencję dotyczącą trybu domowego opartego na profilu:

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

Nie konfiguruj backendu Docker dla Hermes w tym projekcie. Wymagałoby to udzielenia Hermes dostępu do środowiska uruchamiania kontenerów, co unieważnia jedną z głównych decyzji dotyczących izolacji.

Uruchamianie Hermes

Interaktywna sesja rozpoczyna się przy użyciu tego samego wzorca exec, który jest stosowany podczas konfiguracji:

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

Aby uporządkować notatki, prompt może poprosić Hermesa o przejrzenie notatek w formacie Markdown w przestrzeni roboczej, uporządkowanie struktury i nagłówków, poprawienie formatowania i gramatyki, zachowanie każdego szczegółu technicznego, polecenia, linku oraz odnośnika wikilink w Obsidian, oraz o nieusuwanie rzeczywistej treści tylko po to, by skrócić notatkę. Zmiany trafiają bezpośrednio do pojedynczej folderu hosta.

Rozpoczynanie działania środowiska przy uruchamianiu

Gdy wszystko będzie działać interaktywnie, pod powinien zostać uruchomiony ponownie po restartowaniu bez konieczności ręcznego uruchamiania Ollamy i Hermesa. Należy stworzyć jednostkę systemd dla użytkownika:

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

Jednostka zaczyna się od opisu oraz linii z numeracją:

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

Część dotycząca usług to operacja jednorazowa, która uruchamia pod, zatrzymuje go z 30-sekundowym okresem przejściowym oraz daje dwa minuty na ponowne uruchomienie:

[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

Jeśli korzeń grafu Podmana znajduje się na oddzielnym dysku, ten dysk musi być niezawodnie montowany podczas uruchamiania systemu, a nie coś, co pulpit montuje interaktywnie po zalogowaniu. Można dodać opcjonalną weryfikację montażu ExecStartPre dostosowaną do systemu plików.

Przeładowaj systemd:

systemctl --user daemon-reload

Włącz jednostkę:

systemctl --user enable hermes-local.service

Rozpocznij:

systemctl --user start hermes-local.service

I sprawdź to:

systemctl --user status hermes-local.service

Ponieważ jest to usługa typu oneshot z ustawionym RemainAfterExit, ten stan jest normalny:

Active: active (exited)

W Podman istnieje nowszy mechanizm Quadlet do deklaratywnego definiowania kontenerów w środowisku systemd. Jednak gdy środowisko zostało już przygotowane i przetestowane ręcznie, niewielki serwis użytkownika, który uruchamia pod i który okazał się skuteczny, przeszkadzał najmniej.

Aby serwis użytkownika bez uprawnień roota mógł uruchomić się przy starcie systemu, jeszcze przed zalogowaniem się do pulpitu, należy włączyć opcję lingering:

sudo loginctl enable-linger "$USER"

Sprawdź to:

loginctl show-user "$USER" -p Linger

Oczekiwany wynik:

Linger=yes

Cecha linger w Systemd sprawia, że menedżer usług użytkownika uruchamia się przy starcie systemu i pozostaje dostępny poza normalnymi sesjami logowania. Po ponownym uruchomieniu lista kontenerów wygląda następująco:

podman ps

powinien już pokazywać kontener z działającymi Ollamą i Hermesem. Interaktywna sesja Hermesa nadal rozpoczyna się od polecenia exec:

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

Służba uruchamiania startuje środowisko. Nie otwiera interaktywnego czatu.

Problemy, które rzeczywiście wystąpiły

Oto skrócona lista błędów napotkanych w trakcie, z podaniem każdego problemu oraz sposobu jego rozwiązania.

Błąd związany z nakładką w jądrze:

Problem:
kernel does not support overlay fs

Problem został rozwiązany poprzez zainstalowanie fuse-overlayfs i ustawienie go jako program montowania typu overlay:

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

Brakuje miejsca w katalogu tymczasowym:

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

Było konieczne zarówno przeniesienie korzenia grafu w odpowiednich miejscach, jak i ustawienie TMPDIR, ponieważ flaga tmpdir w Podman to coś zupełnie innego:

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

Konflikt nazw dla kontenera bootstrap:

Problem:
ollama-bootstrap name already in use

Problem zostaje rozwiązany poprzez usunięcie przestarzałego kontenera:

Fix:
podman rm -f ollama-bootstrap

Pasta nie może otworzyć urządzenia TUN:

Problem:
pasta cannot open /dev/net/tun

Problem można rozwiązać poprzez załadowanie modułu tun lub ponowne uruchomienie systemu, jeśli zainstalowane moduły nie są zgodne z działającym jądrem:

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

Narzędzie NVIDIA zainstalowane na komputerze AMD:

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

Nie powinno się tego robić, zamiast tego należy użyć obrazu ROCm z urządzeniami kfd i dri:

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

Nowe członkostwa w grupach wideo i renderingu, które nie pojawiły się na liście grup:

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

potrzebne jest wylogowanie i ponowne logowanie, ponieważ istniejąca sesja zachowuje swoje pierwotne grupy:

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

Pliki modeli i manifest są obecne, podczas gdy lista Ollamy jest pusta:

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

diagnozuje się to poprzez przeczytanie dziennika, a jeśli ścieżki home i models wskazują na korzeń systemu plików, podczas gdy wolumen jest zamontowany w katalogu użytkownika root, należy wyraźnie ustawić obie zmienne:

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

Test w Pythonie od Hermes do Ollamy, który w milczeniu nic nie wydrukował:

Problem:
Hermes → Ollama Python test silently prints nothing

Problem został rozwiązany poprzez dodanie flagi interaktywnej przy użyciu heredoc, lub po prostu poprzez zastosowanie formy polecenia wewnątrz tekstu:

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

Menu dostawcy bez oczywistej lokalnej pozycji Ollama:

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

Problem został rozwiązany poprzez wybranie ręcznego, dostosowanego punktu końcowego oraz wprowadzenie lokalnej adresacji v1:

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

A Hermes narzeka na niewystarczająco dużą przestrzeń kontekstową, mimo że wszystko inne działało poprawnie:

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

Problem ten rozwiązuje się poprzez ustawienie długości kontekstu po stronie serwera na 64000, skonfigurowanie Hermesa na tę samą wartość oraz sprawdzenie rzeczywistego przydziału z listą procesów Ollamy:

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

Gdzie powinna znajdować się granica zaufania

Główna lekcja tutaj nie dotyczy tak naprawdę Ollamy czy ROCm. Chodzi o decyzję o tym, gdzie powinna znajdować się granica zaufania. Polecenie, które mówi, by nigdy nie czytać niczego poza określonym katalogiem, stanowi instrukcję behawioralną. Kontener Podman, któremu nigdy nie udostępniono reszty systemu plików hosta, stanowi granicę systemu operacyjnego. Bezpieczny do zapisu katalog root w Hermesie jest przydatny, ale sam Hermes opisuje go jako dodatkową warstwę, a nie prawdziwą przestrzeń izolowaną, ponieważ polecenia w shellu mogą obejść zabezpieczenia narzędzi do pracy z plikami.

Wynikowy model zaufania układa warstwy od najslabszej do najsilniejszej, zaczynając od ograniczeń wynikających z poleceń oraz ochrony przed zapisem plików w Hermesie, a kończąc na granicy kontenera:

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

Nawet jeśli model otrzyma złośliwą instrukcję ukrytą w pliku Markdown, istotne pytanie nie brzmi, czy będzie przestrzegać pierwotnego polecenia, lecz co faktycznie może osiągnąć skompromitowany agent. W tym projekcie odpowiedź jest celowo ograniczona.

Debugowanie warstwowe

Zarówno Hermes, jak i Ollama szybko się zmieniają. Obecna dokumentacja Hermes obsługuje serwery typu self-hosted kompatybilne z OpenAI, w tym Ollamę, ale pojawiały się również doniesienia o zachowaniu narzędzi poprzez dostosowane punkty końcowe Ollamy. Jeśli zwykła rozmowa działa, a Hermes wydaje się opisywać wywołania narzędzi bez ich wykonania, sprawdź aktualny tracker problemów Hermes, zanim założysz, że sandbox Podman jest uszkodzony.

To jest powód, by testować całą strukturę jako łańcuch pytań: od tego, czy Podman widzi kartę graficzną, przez to, czy Ollama widzi model, aż po to, czy Hermes może komunikować się z Ollamą:

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?

Nie próbuj debugować wszystkich sześciu warstw jednocześnie.

Co daje gotowa konfiguracja

Otrzymana konfiguracja umożliwia lokalną inferyencję, przyspieszenie za pomocą GPU AMD, trwałą pamięć Hermes oraz jeden zapisywalny obszar pracy hosta:

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

I nie wymaga modelu chmurowego, nie ujawnia plików systemowych hosta, nie ujawnia sokietów Podman ani Docker, a także nie ma normalnego wyjścia do Internetu:

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

To o wiele wygodniejszy sposób na eksperymentowanie z wydajnym lokalnym agentem. Hermes nadal ma wystarczający dostęp, by być przydatny. Po prostu nie może sam decydować, co oznacza „wystarczający dostęp”.