Подняли родную панель hermes dashboard на LXC 141 за NPM с basic_auth. Грабля: флаг --insecure отключает cookie-gate (включает легаси _SESSION_TOKEN) → login ok, но всё внутри 401. Фикс: бинд 0.0.0.0 БЕЗ --insecure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
16 KiB
16 KiB
date, tags
| date | tags | ||||||
|---|---|---|---|---|---|---|---|
| 2026-06-18 |
|
German — Hermes Agent как личный супер-ассистент Олега (LXC 141)
Контекст
Олег попросил поставить Hermes Agent (NousResearch, open-source, MIT, Python, model-agnostic — из ресёрча ../notes/claude/2026-06-08-145004-найди-аналог-openclaw-для-меня-нужен-аркестратор-и) на homelab. Сначала — «для тестирования самого Hermes через Telegram», затем расширил: настроить как супер-помощника с полным доступом к базе знаний. Имя — Герман (German).
Что развёрнуто
- LXC 141
german(10.0.0.141, Debian 12, 2 vCPU / 3 GB / 12 GB на local-lvm, nesting, unprivileged, onboot=1). Создан изdebian-12-standard 12.12. - Hermes Agent v0.16.0 — установлен официальным
install.sh --non-interactive --skip-setup. Код/usr/local/lib/hermes-agent, данные/root/.hermes(uv + managed Node). - Telegram-бот «Герман Непомнящий» @german_dttb_bot (id 8885932329). Gateway = systemd
hermes-german.service(hermes gateway run --replace, Restart=always, drain 210s, crash-guard, NoNewPrivileges/PrivateTmp).
Модель
- Провайдер OmniRoute (OpenAI-совместимый шлюз на LXC 132):
base_url http://10.0.0.179:20128/v1. - Активная модель:
cc/claude-opus-4-8(Opus 4.8 via Max). Fallback-цепочка (2026-06-21):cx/gpt-5.5→cc/claude-sonnet-4-6;api_max_retries: 1. (Былоapi_max_retries: 6+ fallbackcc/sonnet-4-6первым → German отвечал по 533с/9мин: 6 ретраев с бэкоффом на каждой капнутой Max-модели. Фикс: 1 попытка + независимыйcx/gpt-5.5первым → failover за секунды. Ответ 13с после фикса.) Fallback в Hermes срабатывает на 400 «out of extra usage» (проверено по логам cc→fallback) — поэтому рабочий fallback критичен. ⚠️ Прежний fallbackkr/claude-sonnet-4.5сдох: OmniRoute потерял креды провайдера Kiro («No credentials for provider: kiro», 2026-06-19) → German падал ПОЛНОСТЬЮ (и primary капнут, и fallback мёртв).cx/gpt-5.5выбран финальным fallback потому, что Codex — отдельный провайдер, не Max → переживает полный кап Max-квоты. Проверено: cx/gpt-5.5 работает через агентский цикл с тулами+KB. - КОРЕНЬ 400 «out of extra usage» (исправленное понимание): это НЕ персистентное исчерпание квоты. Олег верно заметил: «если бы лимиты — ты (Claude на Opus 4.8) тоже бы не работал». Проверка по факту 2026-06-18 ~22:30:
curl cc/claude-opus-4-8к OmniRoute с system-prompt 14B / 2КБ / 8КБ → все 200. То есть 400 в 19:11/19:18 был транзиентным — краткий кап 5-часового окна Max в момент пиковой нагрузки (Max делят this-session/German/openclaw/swarmclaw/code-server). Окно отпускает само. Если 400 участятся — включить overflow (pay-as-you-go) на claude.ai/settings/usage. - ⚠️
cc/claude-opus-4-8(Max) ФЛАПАЕТ400: You're out of extra usage: прямой curl к OmniRoute то проходит (19:16), то нет — реальные запросы Олега падали (19:11 «Привет», 19:18 «Бенелюкс», разные request_id). Причина: включённая Max-квота Opus в текущем окне исчерпана (overflow/pay-as-you-go выключен), а окно делят openclaw (cc/opus-4-7) + swarmclaw (cc/opus-4-8) + code-server через тот же OmniRoutecc/*. - 400 — non-retryable BadRequestError → fallback НЕ срабатывает (Hermes уводит в fallback только на rate-limit/5xx/connection). Поэтому fallback на Sonnet от Opus-400 не спасает.
- Решение: primary =
kr/claude-sonnet-4.5(free, Kiro/AWS, не флапает, не ест Max-квоту, не конкурирует с другими ботами Олега). Проверено: запрос «Бенелюкс» через тулы вернул корректную сводку по KB. - Вернуть Opus, когда окно Max освободится / включён overflow:
sed -i 's|kr/claude-sonnet-4.5|cc/claude-opus-4-8|' /root/.hermes/config.yaml && systemctl restart hermes-german. - Доп. правки сессии: отключён
display.platforms.telegram.streaming(было задвоение сообщений в Telegram); fallback-цепочка протестирована (форматfallback_providers: [{provider,model,base_url}], ключ берётся из env — работает), но снята, т.к. от 400 не помогает. - Ключ —
OPENAI_API_KEY+OPENAI_BASE_URLв/root/.hermes/.env(chmod 600). auxiliary(vision/web_extract/compression/session_search) →provider: main— иначе лезли бы в OpenRouter (ключа нет) и падали.- ⚠️ Грабля для будущего: оба воркфлоу-ревьюера по коду утверждали, что на приватный IP-эндпоинт
OPENAI_API_KEYиз env «гейтится по хосту» и нуженmodel.api_key: ${OPENAI_API_KEY}в config.yaml, иначе 401. Эмпирически опровергнуто — CLI- и gateway-вызовы к 10.0.0.179 проходят с env-ключом безmodel.api_key. Если когда-нибудь начнёт давать 401 на первом вызове модели — добавитьapi_key: ${OPENAI_API_KEY}в секциюmodel:иsystemctl restart hermes-german.
База знаний (KB)
- Зеркало vault клонировано в
/root/german/knowledge-base(workspace =/root/german, systemdWorkingDirectory). - Обновление:
/root/kb-pull.sh(cron*/15) делаетgit fetch + git reset --hard origin/main— это read-only зеркало (правки агента там затираются; для своих заметок у него/root/german/notes+ native memory). - Безопасность кред: из
.git/configworkspace убран токен Gitea (былhttps://oleg:TOKEN@...); remote сделан tokenless, креды вынесены в/root/.git-credentials(chmod 600, вне workspace). - Доступ Германа к KB: тулсеты file (read_file/grep) + terminal. Семантического индекса нет — навигация через
knowledge-base/CLAUDE.md→ grep. Это прописано вSOUL.md.
Конфиг (ключевое, /root/.hermes/config.yaml, 600)
platform_toolsets.telegram— явный список[web, terminal, file, memory, todo, skills, session_search, tts](+ kanban по дефолту).- Почему не пресет
hermes-telegram: security-ревью (читалоtoolsets.py:440) показало, что пресет тянет полный core-набор (browser, execute_code, computer_use), аdisabled_toolsetsих НЕ убирает (категории — web/browser/terminal/code_execution/image_gen). Для бота с доступом к секретам это лишние каналы исхода. Провереноhermes tools --summary: на Telegram 9/26 тулов, browser/code-exec/computer-use отсутствуют. - web-поиск без ключей:
web.backend: ddgs(DuckDuckGo, бесплатно). Browser недоступен (требует BROWSERBASE_API_KEY) — веб через ddgs +curlв terminal. SOUL.md(личность «Герман», навигация по KB, правила безопасности) — 600.- Прочее:
display.language: ru,session_reset: idle 2880m,memory_enabled,security.redact_secrets: true+tirith_enabled: true,stt(faster-whisper base — голосовые транскрибируются).
Безопасность — модель угроз
- Доступ только у Олега:
TELEGRAM_ALLOWED_USERS=1292155421,guest_mode: false,unauthorized_dm_behavior: ignore. Проверено по коду (telegram.py/authz_mixin.py): пустой allowlist = deny-all, fail-closed. Чужие сообщения молча игнорируются. - terminal=local + root = Герман может выполнять любой bash на 141 и читать секреты из KB. Это намеренно (DevOps-ассистент), защита держится на allowlist. Жёсткий systemd-сэндбокс (ProtectSystem/ProtectHome) не ставил — сломал бы функции. Будущее ужесточение: отдельный непривилегированный юзер.
redact_secrets: true— секреты вычищаются из tool output/логов/ответов перед доставкой.
Эксплуатация
- Статус/логи:
systemctl status hermes-german; логи в файлах/root/.hermes/logs/{gateway,agent}.log(не в journal). - Рестарт после правок config/SOUL:
systemctl restart hermes-german(дренаж до ~180с). - Бэкапы конфигов:
/root/.hermes/config.yaml.bak.*,.env.bak.orig. - Эндпоинт-санити:
curl -s http://10.0.0.179:20128/v1/models(должен отдавать cc/claude-opus-4-8).
Проверено
- ✅ Установка, конфиг грузится,
hermes status→ Model cc/claude-opus-4-8 / Custom endpoint. - ✅ End-to-end CLI: вопрос по KB → grep → верный ответ (IP openclaw 10.0.0.239).
- ✅ Прямой вызов OmniRoute cc/claude-opus-4-8 (стрим).
- ✅ Gateway:
✓ telegram connected(polling), getMe ok, allowlist на Олега. - ✅ Тулсет Telegram безопасен (без browser/code-exec/computer-use).
- ✅ Live gateway-раунд-трип: сообщение Олега «Привет» прошло Telegram→allowlist→OmniRoute→Anthropic (ключ резолвится в gateway без
model.api_key— опасения ревью не подтвердились). На cc/opus упёрлись в квоту Max (400); наkr/claude-sonnet-4.5— KB-вопрос через тулы вернул верный IP.
NetBird (добавлено 2026-06-18)
Чтобы German дотягивался до клиентских площадок в mesh (как openclaw/swarmclaw).
- IP
100.70.99.82,german.netbird.cloud, группа Claude-Diag. Setup-key в ../projects/dttb/credentials.md (64DF527E-…, создан через PAT/API). - Установка только через apt-репозиторий
pkgs.netbird.io— официальныйcurl install.sh | shредиректит наgithub.com/netbirdio/.../releases/download/v0.73.0/install.sh, а GitHub releases таймаутят из RU-сети (connection timed out). Шаги: добавить ключpkgs.netbird.io/debian/public.keyв keyring +deb [...] https://pkgs.netbird.io/debian stable main→apt install netbird(v0.73.0). Работает в unprivileged-LXC черезnesting=1+ kernel WireGuard (wt0, tun не нужен). - Enroll с
--disable-dns:netbird up --setup-key <KEY> --disable-dns --hostname german. Это сознательно — на LXC 132/137 NetBird-DNS (resolv.conf через wt0) уже ломал связность; German держит свой1.1.1.1/8.8.8.8и ходит по IP. Magic-DNS*.netbird.cloudв контейнере НЕ резолвится (search dttb.ruхайджачит) — только по IP 100.70.x. - Проверено: openclaw
100.70.167.54:18789→ 200; github/telegram/OmniRoute/DNS целы;netbirdautostart enabled; hermes-german не пострадал.
Веб-дашборд german.dttb.ru (добавлено 2026-06-26)
Родная веб-панель Hermes (hermes dashboard: чат + config + sessions + встроенный PTY-терминал), не путать с Open WebUI.
- systemd
hermes-dashboard.service(рядом с gateway, не вместо):hermes dashboard --host 0.0.0.0 --port 9119 --skip-build --no-open, Restart=always, бинд0.0.0.0:9119. - Фронт пришлось собрать:
web/distотсутствовал.cd /usr/local/lib/hermes-agent/web && npm install && npm run build→ vite кладёт в../hermes_cli/web_dist(outDir в vite.config, НЕweb/dist), оттуда--skip-buildи отдаёт. node v22 (warning EBADENGINE про node≥24 — не критично). - NPM #40 (10.0.0.195)
german.dttb.ru→10.0.0.141:9119, force-SSL+WSS+HTTP2, block-exploits. LE-серт id130 (HTTP-01, до 24.09.2026). NPM v2.14: cert-create толькоmeta:{}(email/agree/dns_challenge = «additional properties»). - DNS: публичный A
german.dttb.ru→176.62.183.186добавлен через Spaceweb API (editMainaction:add, один вызов — циклом ломает зону). Локально wildcard*.dttb.ru→10.0.0.195уже резолвил. Проверять пропагацию только DoH (8.8.8.8/resolve),digхайджачится локальным DNS. - Аутентификация —
dashboard.basic_authв/root/.hermes/config.yaml:username: oleg,password_hash(scrypt, черезplugins.dashboard_auth.basic.hash_password),secret(32 байта hex),session_ttl_seconds: 43200,public_url: https://german.dttb.ru. Плейнтекст-пароль не хранится. Креды в ../projects/dttb/credentials.md. Бэкапconfig.yaml.bak-dashboard-20260626. - ⚠️ ГЛАВНАЯ ГРАБЛЯ —
--insecureотключает gate.should_require_auth(host, allow_public): non-loopback +--insecure→auth_required=False→ активен легаси_SESSION_TOKEN-middleware (ephemeral токен, инжектится в loopback-HTML), basic-cookie-gategated_auth_middlewareстановится no-op. Симптом:/auth/password-login→{"ok":true}(кука минтится), но/api/auth/me+ все данные→401. Провайдер/секрет/токен/куки исправны (доказано: офлайн_unsign(live_token, _resolve_secret(cfg))→payload ок; round-trip провайдера→ok). Фикс = убрать--insecure(бинд0.0.0.0без него:auth_required=True, провайдерbasic, и0.0.0.0принимает любой Host — иначе явный бинд на IP даёт 400 «Invalid Host header» на NPM-домен). Hermes сам это документирует:dashboard_register.py:218«To require login (LAN/public): hermes dashboard --host 0.0.0.0». Лог-маркер:Dashboard binding to 0.0.0.0 with OAuth auth gate enabled. Providers: basic. - Проверено end-to-end через NPM:
https://german.dttb.ruloginoleg→/api/auth/me→200,/api/sessions→200; http→301; серт CN german.dttb.ru. Telegram-gateway не задет (оба сервиса active+enabled).
TODO / на будущее
- Fallback-цепочка (cc/sonnet-4-6, kr/sonnet-4.5, cx/gpt-5.4) — формат
fallback_providersсapi_key_env(НЕ${VAR}— путь резолва фолбэков не делает env-подстановку). Пока не ставил (primary надёжен). - Настоящий web-search через self-hosted SearXNG (
SEARXNG_URL) или ключ Tavily/Firecrawl — если ddgs будет мало. - Рассмотреть запуск под non-root юзером для уменьшения blast radius.