Головна / Статті

Запуск агента Hermes локально в Rootless Podman з Ollama на GPU AMD

Робота агента Hermes локально та безпечно: Podman без прав root, один монтований каталог, внутрішня мережа, Ollama на GPU AMD з ROCm та один юніт systemd.

Видавець: REACTAPP.TOP20 хв читання4524 слів

Джерела: Run Hermes Agent Locally (and securely) with Ollama and Rootless Podman on Arch Linux

Використання AI-агента для роботи з локальними нотатками у форматі Markdown та Obsidian здається простим, поки не виникає питання доступу. Hermes Agent від Nous Research може виконувати команди терміналу, редагувати файли, запускати код та викликати інші інструменти. Надання йому моделі з вказівкою працювати лише з одним папками не створює бар’єру безпеки, адже інструкція є лише текстом. У власній документації Hermes також зазначається, що параметр HERMES_WRITE_SAFE_ROOT захищає звичайні інструменти редагування файлів, проте інструменти терміналу все одно можуть отримати доступ до всього, до чого може отримати доступ обліковий запис операційної системи, на якій працює Hermes.

Наведена нижче конфігурація розміщує сам Hermes усередині безкореневого Podman поруч із сервером Ollama, який запускає локальну модель на GPU AMD. Hermes може редагувати лише одну папку, яка навмисно монтується. Решта файлової системи хоста просто відсутня у його просторі імен контейнера, тому він не може туди потрапити. Hermes офіційно підтримує роботу в контейнері, зберігає постійний стан у директорії /opt/data та працює з самостійно розміщеними серверами інференсу, такими як Ollama, через кінцеву точку, сумісну з OpenAI.

Інструкція була створена на EndeavourOS, похідній від Arch. Команди з встановлення пакетів потрібно адаптувати під інші дистрибутиви.

Встановлення безкореневого Podman

У Arch або EndeavourOS встановіть Podman за допомогою Pacman:

sudo pacman -Syu podman

Pacman може запитати, який OCI-рунтайм використовувати, пропонуючи ці три варіанти:

1) crun
2) krun
3) runc

Вибір, зроблений тут, є першим:

1

Цей движок виконання — crun. Це виправдовує себе згодом, адже функція Podman «keep-groups», яка передає додаткові групи хоста в контейнер без кореня, працює з crun.

Підтвердіть встановлення, вивівши версію та запитавши у Podman, який движок виконання він використовує:

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

Друга команда має відповісти назвою часу виконання:

crun

Контейнери без кореня також потребують підпорядкованих мапувань UID та GID для користувача, тому переконайтеся, що записи існують у обох файлах:

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

Відтепер виконуйте всі команди Podman як звичайний користувач. Не додавайте до них префікс sudo.

Відновлення rootless OverlayFS у разі потреби

Перше завантаження образу на цій машині зазнало невдачі через помилку про те, що ядро не підтримує overlay на файловій системі ext:

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

Коли це станеться, встановіть реалізацію у користувацькому просторі:

sudo pacman -S fuse-overlayfs

Podman рекомендує використовувати fuse-overlayfs у сценаріях без кореня, де вбудований overlay недоступний, і його можна явно вказати як програму монтування без кореня. Переконайтеся, що він встановлений:

which fuse-overlayfs

Очікуваний шлях є:

/usr/bin/fuse-overlayfs

Конфігурація зберігається у файлі для кожного користувача:

~/.config/containers/storage.conf

Якщо він вже існує, краще його відредагувати, а не сліпо замінювати. У розділі зберігання потрібно вибрати драйвер оверлею:

[storage]
driver = "overlay"

А другий розділ має містити назву програми монтування:

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

Потім перевірте ефективні налаштування, відфільтрувавши вивід інформації про налагодження Podman:

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

Переміщення зберігання на більший диск

Для цього проекту цей крок став необхідним майже відразу. Лише образ контейнера Ollama займає кілька гігабайт, моделі часто додають ще від 5 до 20 ГБ або навіть більше, а Podman також використовує тимчасовий дисковий простір під час завантаження та розпакування шарів. Перший спроба отримати Ollama зрештою завершилася помилкою про відсутність простору у певному шляху всередині тимчасової директорії:

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

У справі задіяні два окремі місця зберігання, що легко пропустити. Постійне зберігання зображень, контейнерів та томів без кореневої директорії зазвичай знаходиться під домашньою директорією:

$HOME/.local/share/containers/storage

Завантажені шари тимчасово зберігаються у:

/var/tmp

Зміна параметра --tmpdir у Podman не змінює місцезнаходження каталогу для завантажень. У документації Podman рекомендується замість цього використовувати змінну середовища TMPDIR.

Якщо є більший локальний файловий системний простір, створіть структуру з одним каталогом для зберігання та одним для тимчасових файлів:

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

Створіть обидва та передайте права власності поточному користувачеві:

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

Потім відкрийте конфігурацію зберігання для редагування:

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

Направте корінь графа на новий каталог зберігання, залишивши драйвер оверлею:

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

І збережіть налаштування програми монтування fuse-overlayfs:

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

Виберіть місцеву файлову систему для цього. У документації Podman попереджається, що NFS та інші мережеві файлові системи можуть погано функціонувати як сховище для контейнерів без прав root.

Тимчасові завантаження також потребують переміщення, тому встановіть TMPDIR для поточного шелу:

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

Щоб зробити це постійним у Bash, додайте його до профілю та перезавантажте його:

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

Перевірте нові місця зберігання перед тим, як видалити щось зі старого. Роздрукуйте корень графа та відфільтруйте результати відладки для тимчасової директорії та програми монтування:

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

Лише після свіжої установки Podman та лише тоді, коли новий корінь графу безумовно правильний, слід розглянути можливість видалення залишеного сховища у старому місці:

~/.local/share/containers/storage

Не робіть це бездумно на існуючій інсталяції. Саме тому місце зберігання слід визначити ще до створення тома моделі Ollama.

Одна папка для Hermes, два томи для стану

Виберіть єдиний каталог хоста, до якого може мати доступ Hermes, та зробіть цей вибір усвідомлено. Експортуйте його шлях у змінну та створіть папку:

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

У Obsidian хорошим варіантом є окрема папка всередині сховища, наприклад, каталог для досліджень, розташований поруч із особистими та робочими папками:

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

Усе інше в сховищі залишається невидимим для агента.

Далі створіть два томи, якими сам керуватиме Podman:

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

Перший зберігає конфігурацію, пам’ять та постійний стан Hermes. Другий зберігає локальну модель. Жоден з них не вимагає відкриття додаткових каталогів хоста для агента.

Надання доступу GPU AMD контейнеру

Ця налаштовка призначена для AMD ROCm, а не для NVIDIA CUDA. На картці AMD не встановлюйте nvidia-container-toolkit – це було зроблено раніше, перш ніж помітили помилку. Інструкції Ollama щодо контейнерів використовують образ ollama/ollama:rocm для GPU AMD та передають у контейнер два вузли пристроїв:

/dev/kfd
/dev/dri

Перевірте, чи вони існують на хості:

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

Потім перегляньте групи облікового запису:

groups

Якщо обліковий запис відсутній у групах відео та рендерингу, додайте його:

sudo usermod -aG video,render "$USER"

Потім повністю вийдіть з облікового запису та увійдіть знову. Перезапуск переліку груп у тому самому старому терміналі не покаже нових учасників, що спричиняє зайві проблеми та збентеження:

groups

Після нового входу запустіть його ще раз:

groups

Тепер результат має включати обидві групи:

video render

Опція --group-add keep-groups у Podman згодом передає ці додаткові групи у контейнер без кореня, щоб він міг використовувати пристрої GPU.

Перевірка пристрою TUN

Ще одна проблема виникла через мережу без кореня. Pasta, стандартний механізм мережі без кореня у Podman, повідомив про те, що не може відкрити пристрій TUN:

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

Шукайте вузол пристрою:

ls -l /dev/net/tun

Якщо його немає, завантажте модуль ядра:

sudo modprobe tun

Потім перевірте ще раз. Якщо modprobe повідомляє, що модуль відсутній для поточного ядра, а система нещодавно оновлювалась, порівняйте версію поточного ядра з каталогами модулів:

uname -r
ls /usr/lib/modules/

У такій динамічній дистрибуції, як Arch, пристрій може все ще використовувати старіший ядро, модулі якого були видалені під час оновлення. Запустіть перезавантаження та спробуйте ще раз.

Вибір локальної моделі

Для карт із об’ємом VRAM близько 16 ГБ Ollama наразі рекомендує gemma4 як локальну модель для Hermes, а системи з об’ємом близько 24 ГБ можуть використовувати моделі на кшталт qwen3.6. У цьому прикладі використовується версія gemma4 із 12 мільярдів параметрів:

gemma4:12b

Збережіть його у змінній, налаштувавши модель під фактичне обладнання:

export HERMES_MODEL="gemma4:12b"

Іншим важливим показником є довжина контексту. Ollama за замовчуванням налаштовує GPU з VRAM менше 24 ГБ лише на контекст 4K, тоді як обидва проекти рекомендують мінімум 64 000 токенів у разі використання агентів. Це буде налаштовано більш детально далі.

Завантаження зображень та моделі

Потрібні три зображення: звичайне зображення Ollama, версія для ROCm та зображення Hermes:

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

Звичайна версія Ollama виконує одноразове завантаження моделі. Версія для ROCm стає постійним сервером інференсу AMD.

Завантажте модель у її постійний об’єм за допомогою тимчасового контейнера. Спочатку очистіть будь-які застарілі контейнери:

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

Потім запустіть програму завантаження з монтованим об’ємом моделі та нічого більше:

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

Якщо мережування без root-прав все ще не працює, для цього тимчасового програми для завантаження підійде мережування хоста. Знову видаліть контейнер:

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

І почніть з мережі хоста:

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

У цьому контейнері не підключено жодного робочого простору Hermes. Завантажте модель через нього:

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

Підтвердіть за допомогою оголошення:

podman exec ollama-bootstrap ollama list

Обраний модель має з’явитися:

gemma4:12b

Видаліть тимчасовий контейнер. Модель залишається у об’ємі:

podman rm -f ollama-bootstrap

Якщо Podman скаржиться, що ім’я контейнера вже використовується:

container name "ollama-bootstrap" is already in use

Не створюйте більше нових імен. Краще очистіть застарілий контейнер:

podman rm -f ollama-bootstrap

або додайте флаг --replace.

Внутрішня мережа та под

У першій версії проекту зовсім не використовувалась мережа:

--network none

Це було змінено на спеціалізований внутрішній міст Podman. Hermes та Ollama отримують спільний простір імен мережі, у якому вони можуть спілкуватися між собою, тоді як Podman обмежує доступ ззовні. Опція --internal існує саме для обмеження зовнішнього доступу в мережах типу bridge. Створіть її:

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

Потім створіть под у цій мережі:

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

Опція keep-id розміщує обліковий запис хоста без привілеїв у просторі імен користувача контейнера, а числа, які він відображає, можна вибрати. Контейнер Hermes працює під ідентифікатором 10000:10000, саме тому і з’являються ці значення.

Запуск Ollama з ROCm

Тут виникла одна з менш очевидних проблем. Команда, яка нарешті спрацювала, виглядає так:

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

Його лінійки, спеціальні для AMD, є наступними:

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

У цій структурі Podman дві змінні середовища не повинні бути пропущені:

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

Причиною є загадкова несправність. Модель була завантажена правильно, файли blobs та manifest існували, а том був належним чином підключений. Проте перелік моделей не повернув жодних даних:

podman exec ollama ollama list

Журнал контейнера розповів цю історію:

podman logs ollama

У ньому було показано, що Ollama використовує кореневий каталог файлової системи як домашній каталог та шлях до моделей, отриманий від нього:

HOME=/
OLLAMA_MODELS=/.ollama/models

де насправді проживав модель:

/root/.ollama/models

Ollama формує шлях до своєї стандартної моделі з головного каталогу та директорії .ollama/models, якщо тільки змінна OLLAMA_MODELS не встановлює інший шлях. Тому модель ніколи не зникала — Ollama просто шукав її у неправильному місці. Очікувальне встановлення обох змінних відразу вирішило проблему:

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

Тепер у описі має відображатися модель:

podman exec ollama ollama list

Підтвердження використання GPU

Шукайте у журналі рядки з ROCm та GPU:

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

Робоча конфігурація AMD виявляє обчислювальний пристрій ROCm. Потім запускається швидкий тестовий запит, який просить модель надати точну відповідь:

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

Відразу після цього перегляньте діючі моделі:

podman exec ollama ollama ps

Дві колонки заслуговують уваги:

PROCESSOR
CONTEXT

Ідеально, якби стовпець з процесором відображав GPU, а не перенесення обчислень на CPU, причому контекст стосується наступного:

64000

Сам Ollama рекомендує перевіряти список своїх процесів, щоб побачити як виділений контекст, так і процес перенесення даних. Модель, яка пропонує великий нативний контекст, не обов’язково отримує насправді стільки ж ресурсів.

Запуск Hermes з одним монтажем

Тепер створіть контейнер Hermes усередині того ж пода:

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

Важливо те, чого в ньому бракує. Жоден з цих елементів не з’являється:

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

Єдиний довільний параметр прив’язки хоста — це папка робочого простору, яка мапується у каталог даних контейнера:

$HERMES_WORKSPACE
        ↓
/opt/data/workspace

Власний стан Hermes знаходиться у вказаному об’ємі, який підключений за його шляхом до даних:

hermes-data
        ↓
/opt/data

Офіційний контейнер використовує /opt/data для зберігання постійного стану та автоматично встановлює в нього значення HERMES_WRITE_SAFE_ROOT. Цей механізм захисту від запису є частиною багатошарової системи безпеки, а не основним сандбоксом, адже у документації Hermes попереджається, що команди терміналу можуть обійти обмеження щодо роботи з файлами. Справжньою межею є простір імен файлової системи Podman.

Перевірка меж монтування

Перевірте, що саме хост фактично відкриває для доступу Hermes:

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

Очікуйте дві рядки: одну для обсягу даних та одну для дозволеної папки:

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

Домашній каталог та сокет container-runtime не повинні з’являтися. Особливо перевірте сокет:

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

Можна також перевіряти шляхи, чутливі до хоста, наприклад, шукаючи інформацію про те, чи видимий папка SSH:

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

Пам’ятайте, що у контейнера є власні системні каталоги. Їх наявність є нормальною, адже вони належать контейнеру, а не хосту.

Hermes та Ollama

Обидва контейнери користуються мережевим простором імен поду, тому вони взаємодіють через loopback. Протестуйте API Ollama, сумісне з OpenAI, зсередини контейнера Hermes:

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

Відповідь має бути у форматі JSON, що містить локальну модель. Перша спроба використовувала Python heredoc, але нічого не вивела:

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

Причина була простою. Python читає свою програму з stdin у такій формі, але подман exec не отримав команди під’єднатися до stdin. Додавання флага для інтерактивного режиму дозволяє працювати версії з 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

Відсутній прапор коштував кількох додаткових хвилин.

Доведення того, що зовнішній світ недосяжний

Спробуйте отримати дані з зовнішнього веб-сайту зсередини контейнера Hermes:

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

Це має зазнати невдачі, і це навмисно. Hermes може отримати доступ до Ollama всередині контейнера, але мережа не дозволяє звичайного зовнішнього доступу.

Не панікуйте, якщо список контейнерів виглядає так:

podman ps

показує порт поруч із Ollama:

11434/tcp

Це не означає, що порт було опубліковано на хості. Опублікований порт виглядатиме приблизно так:

0.0.0.0:11434->11434/tcp

У цій конфігурації ніде не використовується -p 11434:11434.

Налаштування Hermes для місцевого Ollama

Запустіть налаштування Hermes від імені користувача контейнера у робочому просторі:

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

Вибірник постачальників перелічує багато хмарних сервісів. Не обирайте хмарний варіант:

Ollama Cloud

Оберіть варіант введення ручного кінцевого пункту:

Custom endpoint (enter URL manually)

Для базової URL використовуйте адресу локального мережевого інтерфейсу та шлях v1:

http://127.0.0.1:11434/v1

Якщо потрібен ключ API, залиште його порожнім. У поточних інструкціях щодо інтеграції Hermes від Ollama саме так описаний процес використання спеціального кінцевого пункту з порожнім ключем для локального сервера. Якщо вас запитають про сумісність API, оберіть варіант чат-доповнень, сумісних з OpenAI. Для назви моделі введіть:

gemma4:12b

а щодо довжини контексту:

64000

У документації постачальника Hermes зазначено, що локальний Ollama має використовувати кінцеву точку v1 та потребує принаймні 64 Кб контексту для робочих процесів агентів, які використовують інструменти.

Використання локального термінального бекенду всередині контейнера

Спочатку це здається дивним. Hermes зазвичай попереджає про налаштування локального термінального бекенду:

terminal:
  backend: local

виконує команди терміналу безпосередньо на тій самій машині, де знаходиться Hermes, без жодної ізоляції. На пристрої з безпосередньо встановленим Hermes це було б небезпечно. Оскільки сам Hermes вже знаходиться всередині Podman, локальний бекенд відповідає контейнеру Hermes:

Hermes "local"
      ↓
Hermes container

а не до робочої станції:

Hermes "local"
      ↓
your Arch workstation

Встановіть серверний модуль:

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

А робоча директорія:

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

Також варто встановити вподобання щодо режиму домашньої сторінки, заснованого на профілі:

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

Не налаштовуйте Docker-бекенд Hermes для цього дизайну. Це вимагатиме надання Hermes доступу до середовища виконання контейнерів, що скасовує одне з основних рішень щодо ізоляції.

Запуск Hermes

Інтерактивна сесія починається за тим самим шаблоном виконання, що й під час налаштування:

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

Для оптимізації нотаток запит може просити Hermes переглянути Markdown-нотатки у робочому просторі, впорядкувати структуру та заголовки, виправити форматування та граматику, зберегти кожну технічну деталь, команду, посилання та Obsidian-вікіпосилання недоторканими, а також ніколи не скорочувати справжній контент лише для того, щоб зробити нотатку коротшою. Зміни потрапляють безпосередньо у єдину папку монтованого хоста.

Запуск середовища під час завантаження

Як тільки все почне працювати інтерактивно, після перезавантаження под має автоматично повернутися, без необхідності вручну запускати Ollama та Hermes. Створіть користувацьку одиницю systemd:

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

Розділ починається з опису та рядка з інструкціями щодо порядку дій:

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

Розділ з обслуговуванням — це одноразова операція, яка запускає под, зупиняє його з 30-секундним періодом відстрочки та дає дві хвилини на запуск:

[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

Якщо кореневий граф Podman знаходиться на окремому диску, цей диск має бути надійно підключеним під час запуску системи, а не таким, який робочий стіл підключає інтерактивно після входу. Можна додати необов’язкову перевірку підключення ExecStartPre, адаптовану до конкретної файлової системи.

Перезавантажте systemd:

systemctl --user daemon-reload

Увімкніть одиницю вимірювання:

systemctl --user enable hermes-local.service

Почніть його:

systemctl --user start hermes-local.service

І перевірте це:

systemctl --user status hermes-local.service

Оскільки це послуга типу oneshot із увімкненим параметром RemainAfterExit, такий статус є нормальним:

Active: active (exited)

У Podman існує новіший механізм Quadlet для декларативного визначення контейнерів під керуванням systemd. Однак, коли середовище вже підготовлене та протестоване вручну, найменше перешкод створює невелика користувацька служба, яка запускає pod та вже довела свою ефективність.

Щоб дозволити безкореневій користувацькій службі запускатися під час завантаження системи ще до входу в графічний інтерфейс, увімкніть параметр lingering:

sudo loginctl enable-linger "$USER"

Перевірте це:

loginctl show-user "$USER" -p Linger

Очікуваний результат:

Linger=yes

Функція linger у Systemd змушує менеджер служб користувача запускатися під час завантаження та залишатися доступним поза звичайними сеансами входу. Після перезавантаження список контейнерів:

podman ps

Має вже відображатися контейнер із запущеними Ollama та Hermes. Інтерактивна сесія Hermes все ще запускається за допомогою команди exec:

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

Служба запуску запускає середовище. Вона не відкриває інтерактивний чат.

Проблеми, які дійсно виникли

Ось стислий перелік збоїв, зустрічених по дорозі, кожен з яких супроводжується описом проблеми та способом її вирішення.

Помилка оверлею від ядра:

Problem:
kernel does not support overlay fs

Проблему вирішили шляхом встановлення fuse-overlayfs та налаштування його як програми монтування типу overlay:

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

Закінчується місце під тимчасовим каталогом:

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

Було необхідно як переміщення кореня графа за потреби, так і налаштування TMPDIR, оскільки прапорець tmpdir у Podman — це щось інше:

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

Конфлікт імен для контейнера bootstrap:

Problem:
ollama-bootstrap name already in use

Це виправляється шляхом видалення застарілого контейнера:

Fix:
podman rm -f ollama-bootstrap

Паста не може відкрити пристрій TUN:

Problem:
pasta cannot open /dev/net/tun

Це вирішується шляхом завантаження модуля tun, або перезавантаженням, якщо встановлені модулі не відповідають поточному ядру:

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

Інструментальний набір NVIDIA, встановлений на комп’ютері AMD:

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

Цього просто не слід робити, замість цього використовуйте зображення ROCm із пристроями kfd та dri:

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

Нові члени групи з відео та рендерингом, які не відображалися у списку груп:

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

Потрібно вийти та увійти, оскільки існуюча сесія зберігає свої початкові групи:

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

Файли моделей та маніфест є, тоді як список Ollama порожній:

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

діагностується шляхом читання журналу, і якщо шляхи до домашньої директорії та моделей вказують на корінь файлової системи, тоді як том монтується у каталоз користувача root, необхідно явно встановити обидві змінні:

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

Тест на Python від Hermes до Ollama, який мовчки нічого не вивів:

Problem:
Hermes → Ollama Python test silently prints nothing

Проблему вирішили шляхом додавання інтерактивного флага під час використання heredoc або просто за допомогою форми внутрішнього запиту:

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

Меню постачальника без очевидного місцевого запису Ollama:

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

Проблему вирішили, обравши ручний користувацький кінцевий пункт та ввівши локальну URL версії v1:

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

І Гермес скаржиться на недостатній розмір вікна контексту, хоча все інше працювало належним чином:

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

Цю проблему можна вирішити, встановивши довжину контексту з боку сервера на 64000, налаштувавши Hermes на ту саму значення та перевіривши фактичне виділення пам’яті за допомогою списку процесів Ollama:

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

Де має знаходитися межа довіри

Основний урок тут не стосується безпосередньо Ollama чи ROCm. Йдеться про вирішення питання, де саме знаходиться межа довіри. Інструкція про те, щоб ніколи не читати нічого поза певним каталогом, є поведінковим наказом. Контейнер Podman, якому ніколи не надавали решту файлової системи хоста, є межею операційної системи. Функція безпечного запису даних у Hermes є корисною, але сам Hermes описує її як додатковий шар, а не справжню пісочницю, оскільки команди оболонки можуть обійти захист інструментів для роботи з файлами.

Отримана модель довіри складається з шарів від найслабкіших до найсильніших: починаючи з обмежень, встановлених через інструкції, та захисту від запису даних у Hermes, і закінчуючи межею контейнера:

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

Навіть якщо модель отримує зловмисну інструкцію, приховану у файлі Markdown, цікавим питанням є не те, чи буде вона слухатися початкового запиту, а те, до чого насправді зможе дістатися скомпрометований агент. У цій конструкції відповідь навмисно є обмеженою.

Відлагодження по шарах

Як Hermes, так і Ollama швидко змінюються. Текуща документація Hermes підтримує самостійно розгорнуті кінцеві точки, сумісні з OpenAI, включаючи Ollama, але також існують звіти про поведінку викликів інструментів через користувацькі кінцеві точки Ollama. Якщо звичайний чат працює, а Hermes, здається, описує виклики інструментів, не виконуючи їх, перевірте поточний трекер проблем Hermes, перш ніж припускати, що сандбокс Podman несправний.

Ось чому потрібно тестувати цю стеку як ланцюг запитань: чи бачить Podman GPU, чи бачить Ollama модель, та чи може Hermes спілкуватися з 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?

Не намагайтеся налаштовувати всі шість шарів одночасно.

Що дає готова конфігурація

Отримана конфігурація забезпечує локальні обчислення, прискорення за допомогою GPU AMD, постійну пам’ять Hermes та один робочий простір хоста для запису:

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

І він не потребує хмарної моделі, не відкриває файлову систему хоста, не відкриває сокети Podman чи Docker та не має звичайного виходу в Інтернет:

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

Це набагато зручніший спосіб експериментувати з потужним локальним агентом. Hermes все ще має достатній доступ, щоб бути корисним. Просто він не може сам вирішувати, що означає достатній доступ.