diff --git a/tasks/installer-skill/TZ.md b/tasks/installer-skill/TZ_3.2.md similarity index 100% rename from tasks/installer-skill/TZ.md rename to tasks/installer-skill/TZ_3.2.md diff --git a/tasks/installer-skill/TZ_3.3.md b/tasks/installer-skill/TZ_3.3.md new file mode 100644 index 0000000..34a22a4 --- /dev/null +++ b/tasks/installer-skill/TZ_3.3.md @@ -0,0 +1,739 @@ +# ТЗ: AgentSkill "installer" + +**Статус:** актуализировано после аудита +**Дата:** 2026-04-14 +**Автор:** Стрим (по требованиям Славы) +**Путь установки:** `~/.openclaw/skills/installer/` +**Версия ТЗ:** 3.3 (синхронизировано с реальным кодом и parameters.yaml) + +--- + +## Назначение + +Универсальный скилл для **любых изменений на файловой системе и конфигурировании** хостов. + +**Использование обязательно** при: +- Записи / удалении / перемещении файлов на любом хосте +- Изменении конфигурационных файлов +- Старте / стопе / рестарте сервисов +- Установке / удалении пакетов +- Изменении прав доступа (chmod / chown) + +Область: любые хосты — SSH прямой, SSH через jump, localhost. + +--- + +## Структура скилла + +``` +~/.openclaw/skills/installer/ +├── SKILL.md # инструкция для агентов +├── parameters.yaml # хосты, пути, настройки (без секретов) +├── .env.example # список переменных окружения +└── scripts/ + ├── session.sh # создание сессии, lock, cleanup retention, state machine + ├── backup.sh # бэкап файлов + генерация rollback.sh + ├── verify.sh # health check + валидация изменений + ├── rollback.sh # восстановление файлов из бэкапа + ├── manager.sh # центральный менеджер (cleanup, status, list) + ├── checker.sh # проверка доступности хостов и секретов + ├── ssh_exec.sh # SSH-хелпер с ProxyCommand (shared) + ├── lib.sh # общие функции (update_state, write_registry, remove_state) + └── yaml_get.js # YAML query helper (Node.js, читает parameters.yaml) + +Рабочие данные (workspace): +~/.openclaw/workspace/installer/ +├── logs/ # лог-файлы сессий +├── sessions/ # state.json незавершённых сессий + rollback_meta.json +├── .lock/ # lock-директории по хостам +├── registry.jsonl # реестр всех изменений +└── backups/localhost/ # бэкапы для localhost +``` + +--- + +## Pipeline — 6 обязательных шагов + +``` +1. ПОДГОТОВКА + - Загрузить parameters.yaml (проверить config_version) + секреты из .env + - Проверить незавершённые сессии для данного хоста → предложить продолжить / откатить + - Создать session ID: YYYYMMDD-HHMMSS___ + - Инициализировать лог-файл сессии и state.json (current_step: "init") + - Захватить LOCK (mkdir — атомарно) + +2. HEALTH CHECK + - Обновить state.json → current_step: "health" + - Проверить доступность хоста (SSH connectivity) + - Выполнить host-specific health_check команду (если задана) + - Залогировать результат + +3. БЭКАП + - Обновить state.json → current_step: "backup" + - Скопировать ВСЕ изменяемые файлы в backup_dir хоста (из parameters.yaml) + - Сгенерировать rollback.sh на хосте + сохранить rollback_meta.json локально + - Если бэкап хотя бы одного файла не удался — СТОП, cleanup частичных бэкапов + +4. ВАЛИДАЦИЯ + - Обновить state.json → current_step: "validate" + - Проверить синтаксис новых файлов (YAML, JSON, nginx -t и т.д.) + - Выполнить dry-run / config check если доступен + - Показать агенту diff изменений + - Для критических конфигов — запросить явное подтверждение Славы + - Только при чистой валидации переходить к шагу 5 + +5. ИЗМЕНЕНИЕ + - Обновить state.json → current_step: "change" + - Применить изменения с логированием каждого действия + - При ошибке — немедленно СТОП, спросить об откате + +6. VERIFY (post-check) + - Обновить state.json → current_step: "verify" + - Повторить health_check + - Проверить ожидаемый результат (--expect-entity / --expect-state) + - При ошибке — спросить об откате с развёрнутым объяснением + - Записать результат в registry.jsonl + - Удалить state.json сессии (при успехе) + - Освободить LOCK + - Запустить cleanup_old_sessions() +``` + +--- + +## Интерфейс скриптов + +### `session.sh` +```bash +session.sh --host --desc <описание> --agent +# Возвращает SESSION_ID в stdout (JSON: {"session_id":"..."}) +# Exit 0 = успех +# Exit 1 = lock занят (JSON: {"error":"lock_busy","owner":"...","since":"...","age_minutes":N}) +# Exit 2 = lock завис 30-60 мин (требует подтверждения Славы) +# Правила description: только a-z0-9-, макс 40 символов, авто-нормализация +# При старте проверяет незавершённые сессии для данного хоста +# Если есть → JSON: {"warning":"incomplete_session","session":"...","step":"..."} +# Агент обязан предложить: продолжить / откатить / отменить +``` + +**Файл состояния сессии (state machine):** +``` +~/.openclaw/workspace/installer/sessions//state.json +Содержит: {"session_id":"...","host":"...","current_step":"health|backup|validate|change|verify", + "started":"...","updated":"..."} +Обновляется при переходе между шагами pipeline (через lib.sh → update_state). +При успешном завершении — удаляется (через lib.sh → remove_state). +При ошибке — остаётся для диагностики и возможности продолжить. +``` + +### `backup.sh` +```bash +backup.sh --session --host \ + --files [ ...] \ + --post-rollback-action +# Exit 0 = JSON: {"backups":[...],"rollback":""} +# Exit 1 = JSON: {"error":"...","failed_file":"...","cleaned_up":true} +# При ошибке: СТОП, все уже скопированные бэкапы удаляются (staging), rollback.sh НЕ создаётся +# Бэкапы сохраняются в backup_dir хоста (из parameters.yaml → hosts..backup_dir) +# Лимиты: файл > 50MB → СТОП; суммарно > 500MB → WARNING +``` + +### `verify.sh` +```bash +verify.sh --host --session --stage +# Exit 0 = JSON: {"status":"ok","check":"..."} +# Exit 1 = JSON: {"status":"failed","check":"...","error":"..."} +# --stage pre → health_check; --stage post → post_check +# Проверка entity state — ответственность прикладного скилла (HA и т.д.), не installer'а +``` + +### `rollback.sh` +```bash +rollback.sh --session --host +# Восстанавливает файлы в обратном порядке изменений +# НЕ выполняет reload/restart — это делает агент после +# ⚠️ ОБЯЗАТЕЛЬНО: агент ДОЛЖЕН выполнить post_rollback_action после rollback +# ⚠️ ОБЯЗАТЕЛЬНО: агент ДОЛЖЕН запустить verify.sh --stage post после отката +# Exit 0 = JSON: {"status":"files_restored","post_rollback_action_required":true,"post_rollback_action":"..."} +# Exit 1 = JSON: {"status":"partial","errors":N,"details":[...]} +# Exit 2 = JSON: {"status":"nothing_done","error":"backup not found"} +# Без set -e — явная проверка каждого шага +``` + +### `manager.sh` +```bash +manager.sh --action cleanup [--host ] # чистка старых бэкапов + orphaned state.json/lock +manager.sh --action status [--host ] # кол-во бэкапов, размер, даты +manager.sh --action list-sessions [--host ] [--days N] # список сессий +manager.sh --action show-session --session # детали сессии +manager.sh --action rollback --session # откат конкретной сессии +``` + +### `ssh_exec.sh` +```bash +ssh_exec.sh --host --cmd "<команда>" [--timeout ] [--session ] +# Читает parameters.yaml (через yaml_get.js), строит SSH/ProxyCommand/local exec автоматически +# Для type=local: выполняет команду напрямую (не SSH) +# Для type=ssh-chain: использует ProxyCommand (ключ НЕ хранится на jump-хосте) +# SSH-опции берутся из parameters.yaml → timeouts (ConnectTimeout, ServerAliveInterval, ServerAliveCountMax) +# --timeout оборачивает команду в `timeout ` (по умолчанию: timeouts.command_timeout_default) +# --session — опциональный, для логирования в контексте сессии +# Exit 0 = success; Exit 1 = error; Exit 3 = timeout +``` + +### `lib.sh` +```bash +source ~/.openclaw/skills/installer/scripts/lib.sh +update_state "$SESSION" "" # health | backup | validate | change | verify +remove_state "$SESSION" # удаление state.json при успехе +write_registry "$SESSION" "" "" [--files ""] [--agent ""] + # запись в registry.jsonl +# write_registry также поддерживает: +# --failed-step "" --error "" # для failed/timeout +# --state-unknown # для timeout на шаге change +``` + +### `yaml_get.js` +```bash +node scripts/yaml_get.js [args...] +# Actions: +# get → print value +# get-host-field → print host field (поддерживает вложенные пути) +# get-host-json → print host as JSON +# get-via → print via host IDs (newline-separated) +# list-hosts → print host IDs (one per line) +# check-version → exit 0 if config_version=1 +# Зависимость: npm-модуль `yaml` +``` + +**Общие правила для всех скриптов:** +- Все пишут в лог-файл сессии автоматически (напрямую в файл, НЕ через stdout) +- **stdout** — только JSON-результат для парсинга агентом, ничего больше +- **stderr** — человекочитаемые сообщения и отладка +- Успех → JSON в stdout + exit 0 +- Ошибка → JSON `{"error":"...","step":"..."}` в stdout + exit 1+ +- Exit codes: `0` = успех, `1` = ошибка, `2` = требуется подтверждение, `3` = timeout + +--- + +## Блокировка (Lock) + +``` +Lock-директория: ~/.openclaw/workspace/installer/.lock// +Создание: mkdir (атомарно) — если mkdir вернул ошибку, lock занят +Содержимое: info.json с полями agent, session, started, pid (если доступен) +``` + +**Три уровня реакции по возрасту lock:** + +| Возраст | Действие | +|---------|---------| +| < 30 мин | Стоп. Сообщить кто держит и с какого времени. Ждать. | +| 30–60 мин | Уведомить Славу. Предложить снять. Ждать явного ОК. | +| > 60 мин | Считать потенциально мёртвым. Агент проверяет PID владельца (если доступен) и запрашивает подтверждение Славы. Снятие через атомарный rename (mv lock → lock.removing), затем создание нового — не через rm+mkdir. Логирует принудительное снятие. | + +**Освобождение:** `rm -rf "$LOCK_DIR"` + `trap 'rm -rf "$LOCK_DIR"' EXIT` в скриптах. + +**Orphaned state.json:** при принудительном снятии lock — `state.json` в `sessions/` НЕ удаляется (нужен для диагностики). Очистка orphaned state.json выполняется `manager.sh --action cleanup`: удаляются state.json сессий старше `retention_days`, у которых нет активного lock. + +--- + +## Поведение при ошибке + +**Никогда не откатывать автоматически.** Агент спрашивает пользователя. + +``` +❌ ОШИБКА на шаге: <название шага> +📋 Что изменялось: <файл/сервис/команда> +🔴 Текст ошибки: +⚠️ Текущее состояние: <что уже применено, что нет> + +Если ОТКАТИТЬ: + ✅ <что вернётся в исходное состояние> + ⚠️ <возможные побочные эффекты> + 🔧 Команда: <путь к rollback.sh> + +Если НЕ откатывать: + ⚠️ <риски оставить как есть> + 💡 <возможные ручные действия> + +Выполнить откат? (да / нет) +``` + +**При `state_unknown: true`** (timeout на шаге CHANGE) — особо предупредить: +> ⚠️ ВНИМАНИЕ: timeout во время записи файла — состояние может быть неконсистентным! + +**При `rollback_failed`** — немедленно уведомить Славу: +``` +🚨 ТРЕБУЕТСЯ РУЧНОЕ ВМЕШАТЕЛЬСТВО +Откат не удался. Файл может быть в неконсистентном состоянии. +Сессия: +Хост: +Бэкап: +``` + +--- + +## Критические конфиги — обязательное подтверждение Славы + +Перед шагом ИЗМЕНЕНИЕ — запросить явное "да": + +| Тип | Примеры файлов | +|-----|---------------| +| Docker | `docker-compose.yml`, `Dockerfile`, `.env` сервисов | +| Nginx | `nginx.conf`, `sites-available/*`, `sites-enabled/*` | +| Системные | `/etc/fstab`, `/etc/hosts`, `/etc/ssh/sshd_config` | +| HA Core | `configuration.yaml` | +| Сеть | `/etc/network/interfaces`, `/etc/netplan/*` | +| OpenClaw | `openclaw.json` | + +Формат запроса: +``` +⚠️ КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ: [тип] +📋 Файл: [путь] +🔍 Изменения: [diff] +✅ Бэкап готов: [путь] +Подтвердить изменение? (да / нет) +``` + +--- + +## Логирование + +### Лог-файл сессии +**Путь:** `~/.openclaw/workspace/installer/logs/.log` + +``` +[2026-04-11T14:30:00Z] SESSION START: 20260411-143000_ha_automations-fix_a3f1 +[2026-04-11T14:30:00Z] HOST: ha (192.168.2.139 via ruvpn-srv) +[2026-04-11T14:30:01Z] LOCK: acquired +[2026-04-11T14:30:02Z] HEALTH CHECK: ha core check → OK +[2026-04-11T14:30:03Z] BACKUP: /homeassistant/automations.yaml → /var/backups/openclaw/20260411-143003_automations.yaml.bak +[2026-04-11T14:30:03Z] ROLLBACK SCRIPT: /var/backups/openclaw/rollback/20260411-143003_automations_rollback.sh +[2026-04-11T14:30:04Z] VALIDATE: yaml syntax OK +[2026-04-11T14:30:04Z] VALIDATE: ha core check with new file → OK +[2026-04-11T14:30:05Z] WRITE: /homeassistant/automations.yaml +[2026-04-11T14:30:06Z] RELOAD: automation/reload → HTTP 200 +[2026-04-11T14:30:07Z] VERIFY: automation.alert_device_became_available state=on → OK +[2026-04-11T14:30:07Z] LOCK: released +[2026-04-11T14:30:07Z] CLEANUP: removed 0 old sessions +[2026-04-11T14:30:07Z] SESSION END: SUCCESS +``` + +### Реестр изменений +**Путь:** `~/.openclaw/workspace/installer/registry.jsonl` + +**При успехе:** +```json +{"ts":"2026-04-11T14:30:07Z","session":"20260411-143000_ha_automations-fix_a3f1","host":"ha","files":["/homeassistant/automations.yaml"],"backups":["/var/backups/openclaw/20260411-143003_automations.yaml.bak"],"rollback":"/var/backups/openclaw/rollback/20260411-143003_automations_rollback.sh","post_rollback_action":"automations","status":"success","agent":"stream"} +``` + +**При ошибке:** +```json +{"ts":"...","session":"...","host":"ha","files":[...],"backups":[...],"rollback":"...","post_rollback_action":"automations","status":"failed","failed_step":"verify","error":"ha core check: Invalid config for automation","rolled_back":false,"agent":"stream"} +``` + +**Полный список статусов:** + +| `status` | Смысл | Доп. поля | +|---------|-------|----------| +| `success` | Всё прошло | — | +| `failed` | Ошибка, состояние файлов известно | `failed_step`, `error`, `rolled_back: false` | +| `timeout` | Команда не завершилась, состояние файлов может быть неизвестно | `failed_step`, `error`, `state_unknown: true/false`, `rolled_back: false` | +| `rolled_back` | Ошибка + откат выполнен | `failed_step`, `error`, `rolled_back: true`, `rollback_ts` | +| `rollback_failed` | Ошибка + откат тоже упал | `failed_step`, `error`, `rollback_error`, `rolled_back: false` | +| `cancelled` | Отменено пользователем | `cancelled_at_step` | + +**Правило `state_unknown`:** если timeout произошёл на шаге CHANGE — `state_unknown: true` (файл мог быть записан частично). Агент обязан показать это пользователю перед вопросом об откате. + +--- + +## Бэкапы и rollback.sh + +### Структура хранения +``` +На удалённом хосте (SSH): + /YYYYMMDD-HHMMSS_.bak + /YYYYMMDD-HHMMSS__rollback.sh + + Пути backup_dir и rollback_dir — индивидуальные для каждого хоста (из parameters.yaml): + mva154: /home/slin/backups/openclaw (+ /rollback) + ruvpn-srv: /home/vpn/backups/openclaw (+ /rollback) + ha: /var/backups/openclaw (+ /rollback) + vpn-srv: /home/vpn/backups/openclaw (+ /rollback) + +Локальная копия метаданных (всегда, для всех хостов): + ~/.openclaw/workspace/installer/sessions//rollback_meta.json + # Содержит: список файлов, пути бэкапов, post_rollback_action + +На localhost (OpenClaw workspace): + ~/.openclaw/workspace/installer/backups/localhost/YYYYMMDD-HHMMSS_.bak + ~/.openclaw/workspace/installer/backups/localhost/rollback/YYYYMMDD-HHMMSS__rollback.sh +``` + +### Содержимое rollback.sh +```bash +#!/bin/bash +# ROLLBACK SESSION: 20260411-143000_ha_automations-fix_a3f1 +# Generated: 2026-04-11T14:30:03Z +# Files: /homeassistant/automations.yaml +# POST ROLLBACK ACTION: automations (выполняет агент через API, не этот скрипт) + +# Без set -e — явная проверка каждого шага +ERRORS=0 + +echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ROLLBACK START" >&2 + +# Файл 1/1: восстановить +cp /var/backups/openclaw/20260411-143003_automations.yaml.bak \ + /homeassistant/automations.yaml +if [ $? -eq 0 ]; then + echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] FILE RESTORED: /homeassistant/automations.yaml" >&2 +else + echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ERROR: failed to restore /homeassistant/automations.yaml" >&2 + ERRORS=$((ERRORS + 1)) +fi + +# Итог +if [ $ERRORS -eq 0 ]; then + echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ROLLBACK COMPLETE: files restored" >&2 + echo '{"status":"files_restored","post_rollback_action_required":true,"post_rollback_action":"automations"}' + exit 0 +else + echo '{"status":"partial","errors":'$ERRORS'}' + exit 1 +fi +``` + +--- + +## parameters.yaml — актуальная схема + +```yaml +config_version: 1 # версия формата; скрипты проверяют совместимость при загрузке + +hosts: + + mva154: + label: "mva154 (основной сервер)" + type: ssh-direct + host: "82.22.50.71" + port: 22 + user: "slin" + auth_type: password + password_env: "MVA154_PASSWORD" + sudo: true + sudo_pass_env: "MVA154_SUDO_PASS" + health_check: "systemctl is-system-running" + post_check: "systemctl is-system-running" + backup_dir: "/home/slin/backups/openclaw" + rollback_dir: "/home/slin/backups/openclaw/rollback" + tags: [docker, nginx, main] + + ruvpn-srv: + label: "RUVPN-сервер" + type: ssh-direct + host: "185.130.212.192" + port: 3322 + user: "vpn" + auth_type: key + ssh_key_env: "RUVPN_SSH_KEY" + sudo: true + # sudo_pass_env не нужен: настроен NOPASSWD в /etc/sudoers.d/vpn + health_check: null + post_check: null + backup_dir: "/home/vpn/backups/openclaw" + rollback_dir: "/home/vpn/backups/openclaw/rollback" + tags: [ruvpn, jump] + + ha: + label: "Home Assistant (HAOS)" + type: ssh-chain + host: "192.168.2.139" + port: 22 + user: "root" + auth_type: key + ssh_key_env: "HA_SSH_KEY" + via: [ruvpn-srv] + sudo: false + health_check: "ha core check" + post_check: "ha core check" + backup_dir: "/var/backups/openclaw" + rollback_dir: "/var/backups/openclaw/rollback" + # reload/restart — ответственность HA-скилла (через API/CLI), не installer'а + tags: [homeassistant, haos, critical] + + vpn-srv: + label: "VPNSRV (homenet)" + type: ssh-chain + host: "192.168.2.200" + port: 22 + user: "vpn" + auth_type: key + ssh_key_env: "VPNSRV_SSH_KEY" + via: [ruvpn-srv] + sudo: true + # sudo_pass_env не нужен: настроен NOPASSWD для пользователя vpn + health_check: "systemctl is-active frpc && systemctl is-active xray" + post_check: "systemctl is-active frpc && systemctl is-active xray" + backup_dir: "/home/vpn/backups/openclaw" + rollback_dir: "/home/vpn/backups/openclaw/rollback" + tags: [proxy, frp, xray, homenet] + + localhost: + label: "OpenClaw container" + type: local + host: null + port: null + user: null + auth_type: null + ssh_key_env: null + sudo: false # sudo не установлен в контейнере + health_check: "node -e \"JSON.parse(require('fs').readFileSync('/home/node/.openclaw/openclaw.json'))\"" + post_check: "pgrep -f openclaw-gateway > /dev/null && echo 'Gateway OK'" + tags: [local, openclaw] + +storage: + logs_workspace: "~/.openclaw/workspace/installer/logs" + sessions_dir: "~/.openclaw/workspace/installer/sessions" + registry: "~/.openclaw/workspace/installer/registry.jsonl" + lock_dir: "~/.openclaw/workspace/installer/.lock" + + # Бэкапы для SSH-хостов — на удалённом хосте (backup_dir/rollback_dir из hosts) + # Бэкапы для localhost — в workspace: + backups_local_dir: "~/.openclaw/workspace/installer/backups/localhost" + rollback_local_dir: "~/.openclaw/workspace/installer/backups/localhost/rollback" + + retention_days: 30 + max_backup_file_size_mb: 50 + max_backup_total_mb: 500 + lock_timeout_warn_minutes: 30 + lock_timeout_force_minutes: 60 + +timeouts: + ssh_connect_timeout: 10 + ssh_alive_interval: 15 + ssh_alive_count_max: 3 + command_timeout_default: 120 + +session: + id_format: "{YYYYMMDD-HHMMSS}_{host}_{description}_{XXXX}" + description_rules: + allowed_chars: "a-z0-9-" + max_length: 40 + auto_normalize: true + fallback: "action-{XXXX}" + +notifications: + on_success: false + on_failure: true + on_rollback: true + +on_failure: + auto_rollback: false + ask_user: true + +manager: + cleanup_cron: "0 3 * * *" + cleanup_heartbeat: true + +usage: + mandatory: true + scope: + - filesystem + - config + - services + - packages + - permissions + critical_configs: + - pattern: "docker-compose.yml" + - pattern: "Dockerfile" + - pattern: "nginx.conf" + - pattern: "sites-available/*" + - pattern: "sites-enabled/*" + - pattern: "/etc/fstab" + - pattern: "/etc/hosts" + - pattern: "/etc/ssh/sshd_config" + - pattern: "configuration.yaml" + - pattern: "/etc/network/*" + - pattern: "/etc/netplan/*" + - pattern: "openclaw.json" +``` + +--- + +## .env.example + +```bash +# installer skill — подключения к хостам +# Все ключи и пароли только здесь, никогда в parameters.yaml или скриптах + +# ruvpn-srv (ключ, NOPASSWD sudo) +RUVPN_SSH_KEY=/home/node/.openclaw/ha_ssh_key + +# mva154 (пароль) +MVA154_PASSWORD=your_mva154_password +MVA154_SUDO_PASS=your_mva154_sudo_password + +# ha (ключ) +HA_SSH_KEY=/home/node/.openclaw/ha_ssh_key + +# vpn-srv (ключ, NOPASSWD sudo) +VPNSRV_SSH_KEY=/path/to/vpnsrv_key + +# HA API (используется HA-скиллом, не installer'ом) +# HA_TOKEN=your_ha_long_lived_token +# HA_BOT_TOKEN=your_telegram_bot_token +``` + +--- + +## checker.sh — проверка доступности хостов + +### Интерфейс +```bash +checker.sh [--host [--host ...]] [--all] +# Exit 0 = все проверенные хосты доступны и секреты настроены +# Exit 1 = одна или более проверок не прошла +``` + +### Уровни ошибок + +| Код | Уровень | Смысл | +|-----|---------|-------| +| `SECRET_MISSING` | Секрет не настроен | Переменная в `.env` отсутствует или пустая | +| `SECRET_INVALID` | Секрет некорректен | Файл ключа не найден / не читаем, токен <10 символов | +| `AUTH_FAILED` | Аутентификация провалилась | Ключ/пароль отвергнут хостом | +| `HOST_UNREACHABLE` | Хост недоступен | Timeout, refused, DNS не резолвится | +| `CHECK_FAILED` | Health check упал | Хост доступен, но `health_check` вернул ошибку | + +### Порядок проверок для каждого хоста + +``` +1. SECRET CHECK — все нужные переменные есть в .env и непустые? +1b. SECRET VALIDATE — значения корректны? (файл ключа существует, токен длинный) +2. CONNECTIVITY — хост достижим? (для ssh-chain: сначала каждый хоп в via[]) +3. AUTH CHECK — аутентификация проходит? +4. SUDO CHECK — если sudo: true, проверить sudo доступность +5. HEALTH CHECK — выполнить health_check если задана +6. BACKUP DIR — проверить наличие backup_dir на хосте (из hosts..backup_dir) +``` + +--- + +## Добавление нового хоста + +1. Добавить блок в `parameters.yaml`: +```yaml +new-host: + label: "Описание хоста" + type: ssh-direct # или ssh-chain, local + host: "IP_ADDRESS" + port: 22 + user: "username" + auth_type: key # или password + ssh_key_env: "NEW_HOST_SSH_KEY" # или password_env + sudo: false + health_check: null + post_check: null + backup_dir: "~/backups/openclaw" + rollback_dir: "~/backups/openclaw/rollback" + tags: [tag1, tag2] +``` + +2. Добавить ключ в `~/.openclaw/.env`: +```bash +NEW_HOST_SSH_KEY=/path/to/key +``` + +3. Проверить подключение: +```bash +scripts/checker.sh --host new-host +scripts/ssh_exec.sh --host new-host --cmd "echo OK" +``` + +4. Настройка персистентности: +Installer создаст backup_dir/rollback_dir автоматически при первом запуске `backup.sh`. + +--- + +## Добавление новых секретов + +Только в `~/.openclaw/.env` (никогда в parameters.yaml или скрипты): +```bash +NEW_SECRET=value +``` + +Формат ссылки в parameters.yaml: `secret_env: "NEW_SECRET"`. + +--- + +## Управление (manager.sh) + +### Автоматическое — cron на OpenClaw +```bash +0 3 * * * ~/.openclaw/skills/installer/scripts/manager.sh --action cleanup +``` + +### Вручную +```bash +manager.sh --action status +manager.sh --action list-sessions --days 7 +manager.sh --action show-session --session +manager.sh --action rollback --session +``` + +--- + +## Что реализует Dev + +1. `SKILL.md` — инструкция для агентов +2. `parameters.yaml` — хосты и настройки (уже заполнен) +3. `.env.example` — по шаблону выше +4. `scripts/session.sh` — создание сессии, lock, state machine +5. `scripts/backup.sh` — бэкап файлов + генерация rollback.sh +6. `scripts/verify.sh` — health_check + post_check +7. `scripts/rollback.sh` — откат файлов без set -e +8. `scripts/manager.sh` — cleanup, status, list-sessions, show-session, rollback +9. `scripts/checker.sh` — проверка хостов с 5 уровнями ошибок +10. `scripts/ssh_exec.sh` — SSH-хелпер: ssh-direct, ssh-chain, local +11. `scripts/lib.sh` — общие функции (update_state, write_registry, remove_state) +12. `scripts/yaml_get.js` — YAML query helper (Node.js) + +### Обязательные разделы SKILL.md +- Когда использовать (обязательно) +- Когда НЕ использовать +- Быстрый старт — 6 шагов +- Обновление state.json между шагами (lib.sh) +- Скрипты — справочник с сигнатурами +- Добавление нового хоста (включая backup_dir/rollback_dir) +- Добавление новых секретов +- Критические конфиги — подтверждение Славы +- Поведение при ошибке +- Управление (manager.sh) +- Логирование +- Troubleshooting + +**Не входит в скоуп Dev:** +- Наполнение `parameters.yaml` реальными IP/путями — уже готово +- Настройка cron на OpenClaw — делает Слава/Стрим после установки + +--- + +## Changelog (от TZ 3.2 → 3.3) + +| # | Что изменилось | Причина | +|---|---------------|---------| +| 1 | Добавлен `lib.sh` в структуру скилла | Фактически используется в SKILL.md, но отсутствовал в ТЗ | +| 2 | Добавлен `yaml_get.js` в структуру скилла | Реализован, используется скриптами, отсутствовал в ТЗ | +| 3 | `ruvpn-srv`: убран `sudo_pass_env` | На хосте настроен NOPASSWD | +| 4 | `localhost`: `sudo: false`, убран `sudo_pass_env` | sudo не установлен в контейнере; добавлены health/post_check | +| 5 | Добавлен хост `vpn-srv` (ssh-chain через ruvpn-srv) | Новый хост в реальном parameters.yaml | +| 6 | `backup_dir`/`rollback_dir` — индивидуальные для каждого хоста | В ТЗ 3.2 были только глобальные; в реальности у каждого хоста свои | +| 7 | Убраны `storage.backups_remote_dir` / `rollback_remote_dir` | Заменены на per-host backup_dir/rollback_dir | +| 8 | `ha`: убраны `reload_commands` | Reload — ответственность HA-скилла, не installer'а | +| 9 | `.env.example`: убраны `HA_TOKEN`/`HA_URL`/`RUVPN_SUDO_PASS`/`LOCALHOST_SUDO_PASS` | HA_TOKEN/HA_URL — HA-скилл; NOPASSWD хосты не требуют sudo_pass | +| 10 | `openclaw.json` добавлен в critical_configs | Критичный конфиг OpenClaw | +| 11 | `ssh_exec.sh`: добавлен `--session` параметр | Для логирования в контексте сессии | +| 12 | Добавлена `backup_dir` в шаблон «добавление нового хоста» | Ранее отсутствовала | +| 13 | checker.sh: backup_dir берётся из hosts..backup_dir | Ранее проверял глобальный /var/backups/openclaw | +| 14 | Session ID в примерах: добавлен суффикс _XXXX | Для соответствия реальному формату |