Развёртывание deepseek-harness в отдельном LXC 116 на proxmox-pve3, подключённого к omniroute как LLM-провайдеру, доступного снаружи по harness.skud24.ru через traefik-ha.

Содержание

Хост: LXC 116 на pve3 (198.51.100.1), Debian 12, unprivileged, статический IP 198.51.100.2/24. Изначально 2 CPU/2GB RAM/16GB rootfs — память пришлось поднять до 4096MB + 2048MB swap (pct set 116 --memory 4096 --swap 2048 + pct reboot), сборка падала в OOM при 2GB.

Установка: git clone репозитория в /opt/harness, pnpm install && pnpm run build. Два нетривиальных препятствия по пути: 1. Node.js 20 не годится. pnpm install падал ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite — pnpm-версия репозитория (11.7.0, задана через packageManager в package.json) требует Node ≥22.13. Попытка вручную пинить corepack prepare pnpm@9.15.0 --activate не сработала — corepack всё равно уважает packageManager внутри самого проекта и переставляет обратно на 11.7.0. Единственный рабочий фикс — поднять Node.js до 22.x (NodeSource setup_22.x). 2. OOM на сборке. pnpm run build (tsc -b + tsdown/vite по ~246 пакетам монорепо) падал FATAL ERROR: Reached heap limit даже с NODE_OPTIONS=--max-old-space-size=3584 — реальным потолком был cgroup-лимит LXC (2GB), не V8 heap-size. Исправлено увеличением RAM контейнера (см. выше).

Backend: провайдер omniroute в /root/.dsh/settings.yaml (api: openai-completions, baseURL: https://omniroute.skud24.ru/v1). Изначально модели-алиасы auto/best-coding, auto/coding:fast, auto/best-reasoning, auto/best-chat — заменены 2026-08-22 на конкретные пиненные модели (kr/claude-sonnet-4.5, kr/claude-haiku-4.5, kr/deepseek-3.2, kr/glm-5, kr/qwen3-coder-next, дефолт kr/claude-sonnet-4.5) — см. находку про "Invalid model" ниже. Ключ через OMNIROUTE_API_KEY в systemd-юните.

Сетевая экспозиция — двухслойная, из-за собственных ограничений dsh:

Найденная и исправленная 2026-08-21 проблема — 403 на /api/agentPreset.list после того, как basicAuth уже пройден. Симптом: страница настроек открывается, но список моделей не грузится, DevTools показывает HTTP 403 на /api/* вызовах. Диагностика через прямые curl-запросы показала: 198.51.100.4:3080 напрямую (Host-заголовок 198.51.100.4) → 404 (обычный, метод/маршрут не тот); тот же путь через LAN-прокси (198.51.100.2:3081, Host-заголовок иной) → 403; через сам домен без авторизации → 401 (basicAuth Traefik, ожидаемо); с валидной basicAuth-парой → 403 (то есть Traefik пропустил, само приложение отбило). Причина — второй защитный механизм dsh, не связанный с --host: allowlist доверенных Host-заголовков (защита от DNS rebinding), см. deepseek-harness. Исправлено добавлением флага --trusted-host harness.skud24.ru в ExecStart юнита dsh.service + systemctl daemon-reload && systemctl restart dsh. После фикса /api/agentPreset.list через домен с авторизацией отдаёт 404 (тот же безобидный ответ, что и локально на GET) вместо 403 — модели должны загружаться штатно из браузера (реальный клиент шлёт правильный метод/RPC-конверт).

systemd-юниты (LXC 116):

Доступ: https://harness.skud24.ru, basicAuth mix:<пароль> — единственный барьер после снятия ipWhiteList (см. ниже). TLS Let's Encrypt, действителен 2026-08-21 → 2026-11-19.

Найденная и исправленная 2026-08-21/22 проблема — "Invalid model" при попытке чата. Корень — не в dsh, а в omniroute: auto/ комбо-роутеры (auto/best-coding и другие) пробуют первым кандидатом kiro/auto-kiro, который падает HTTP 400 от бэкенда kiro с текстом Invalid model. Please select a different model to continue. — и OmniRoute абортит весь комбо целиком вместо перехода к оставшимся ~17 кандидатам ("skipping fallback to other targets to prevent infinite loop"). Отдельно кандидат github-models/openai/gpt-4.1 падает независимо HTTP 410 (внешний retirement brownout GitHub Models, не связан с нашей конфигурацией). Это баг/недонастройка самого OmniRoute — не чинится со стороны dsh/LXC 116, требует внимания в OmniRoute-конфиге на 198.51.100.6. Воркараунд: settings.yaml перепинен на конкретные модели (kr/, см. выше) — не решает первопричину, но чат снова работает.

Открытие в интернет (2026-08-22). По явному запросу пользователя (подтверждён выбор "полностью открыть 198.51.100.3/0" из двух вариантов) middleware ipWhiteList удалён из harness.yml на traefik-ha — Traefik подхватил без рестарта (~2 сек), проверено live curl (401 без пароля → 200 с паролем). basicAuth остаётся единственным барьером. ⚠️ dsh даёт модели произвольное выполнение кода на хосте — стоит следить за стойкостью пароля.

harness → n8n: MCP-клиент (2026-08-23). Направление, обратное SSH-мосту через n8n-skud24 (там Claude/n8n управляют лабой по SSH) — здесь сам dsh подключается к n8n как MCP-клиент и получает его тулы в свой собственный список. Механизм — встроенный плагин @deepseek-ai/dsh-mcp-client (пакет уже в node_modules профиля), конфигурируется per-профиль в /root/.dsh/profiles/<profile>/cordis.patch.yml (пустой YAML-массив по умолчанию — редактировать его, не cordis.yml). n8n отдаёт instance-level MCP endpoint http://198.51.100.7:5678/mcp-server/http (LAN-адрес, не через Traefik/домен) — требует отдельного MCP-токена (не n8n API key), генерируется в n8n UI: Settings → Instance-level MCP → Connect a client. Патч:

- insert:
    - id: mcp-n8n
      name: "@deepseek-ai/dsh-mcp-client"
      config:
        serverName: n8n
        transport: streamable-http
        url: http://198.51.100.7:5678/mcp-server/http
        headers:
          Authorization: "Bearer <MCP-токен>"

Грабля: официальный пример дока dsh-плагина использует !!js YAML-тег для интерполяции process.env. в значении — не работает в per-profile cordis.patch.yml (парсер оверлеев кидает YAMLException: cannot resolve a node with !<tag:yaml.org,2002:js> explicit tag; похоже !!js поддерживается только в конфигах верхнего уровня сборки, не в profile-патчах). Решение — токен прописан прямой строкой, не через env-переменную. Применено к обоим профилям (web — боевой за harness.skud24.ru, и headless). После systemctl restart dsh проверено: 29 тулов mcp__n8n__ (search_workflows, execute_workflow, create_workflow_from_code, get_node_types, validate_workflow, list_credentials и т.д.) доступны модели — подтверждено через dsh --profile headless "list all tools whose name contains n8n".

Связано с

Источник