# ТЗ: AgentSkill "installer" **Статус:** готово к разработке **Дата:** 2026-04-11 **Автор:** Стрим (по требованиям Славы) **Путь установки:** `~/.openclaw/skills/installer/` **Версия ТЗ:** 3.2 (после аудита безопасности и надёжности) --- ## Назначение Универсальный скилл для **любых изменений на файловой системе и конфигурировании** хостов. **Использование обязательно** при: - Записи / удалении / перемещении файлов на любом хосте - Изменении конфигурационных файлов - Старте / стопе / рестарте сервисов - Установке / удалении пакетов - Изменении прав доступа (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) Рабочие данные (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" - Скопировать ВСЕ изменяемые файлы в backups_dir - Сгенерировать 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. При успешном завершении — удаляется. При ошибке — остаётся для диагностики и возможности продолжить. ``` ### `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 НЕ создаётся ``` ### `verify.sh` ```bash verify.sh --host --session --stage \ [--expect-entity --expect-state ] # Exit 0 = JSON: {"status":"ok","check":"..."} # Exit 1 = JSON: {"status":"failed","check":"...","error":"..."} ``` ### `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} # 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 ] # Читает parameters.yaml, строит 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) # Exit 3 = timeout (команда не завершилась за отведённое время) ``` **Общие правила для всех скриптов:** - Все пишут в лог-файл сессии автоматически (напрямую в файл, НЕ через 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> Если НЕ откатывать: ⚠️ <риски оставить как есть> 💡 <возможные ручные действия> Выполнить откат? (да / нет) ``` **При `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/*` | Формат запроса: ``` ⚠️ КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ: [тип] 📋 Файл: [путь] 🔍 Изменения: [diff] ✅ Бэкап готов: [путь] Подтвердить изменение? (да / нет) ``` --- ## Логирование ### Лог-файл сессии **Путь:** `~/.openclaw/workspace/installer/logs/YYYYMMDD-HHMMSS___.log` > Примечание: в примерах ниже суффикс `_XXXX` опущен для читаемости. ``` [2026-04-11T14:30:00Z] SESSION START: 20260411-143000_ha_automations-fix [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","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): /var/backups/openclaw/YYYYMMDD-HHMMSS_.bak /var/backups/openclaw/rollback/YYYYMMDD-HHMMSS__rollback.sh Локальная копия метаданных (всегда, для всех хостов): ~/.openclaw/workspace/installer/sessions//rollback_meta.json # Содержит: список файлов, пути бэкапов, post_rollback_action # Позволяет восстановить rollback.sh если хост временно недоступен На 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 # 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 # installer/parameters.yaml # Только структура, пути, параметры подключений. # Секреты (ключи, пароли, токены) — ТОЛЬКО в ~/.openclaw/.env 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" # имя переменной в .env с паролем sudo: true sudo_pass_env: "MVA154_SUDO_PASS" health_check: "systemctl is-system-running" post_check: "systemctl is-system-running" 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" # имя переменной в .env с путём к ключу sudo: true sudo_pass_env: "RUVPN_SUDO_PASS" health_check: null # только проверка доступности SSH post_check: null 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" # имя переменной в .env с путём к ключу via: [ruvpn-srv] # маршрут: OpenClaw → ruvpn-srv → ha # Ключ НЕ хранится на jump-хосте — ProxyCommand с ключом из .env sudo: false health_check: "ha core check" post_check: "ha core check" reload_commands: automations: method: api endpoint: "/api/services/automation/reload" http_method: POST auth_env: "HA_TOKEN" base_url_env: "HA_URL" scripts: method: api endpoint: "/api/services/script/reload" http_method: POST auth_env: "HA_TOKEN" base_url_env: "HA_URL" scenes: method: api endpoint: "/api/services/scene/reload" http_method: POST auth_env: "HA_TOKEN" base_url_env: "HA_URL" core: method: cli command: "ha core restart" tags: [homeassistant, haos, critical] localhost: label: "OpenClaw container" type: local # НЕ использует SSH — exec напрямую host: null port: null user: null auth_type: null ssh_key_env: null sudo: true sudo_pass_env: "LOCALHOST_SUDO_PASS" health_check: null post_check: null tags: [local, openclaw] storage: # Логи и реестр — на OpenClaw (workspace, персистентно) 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-хостов — на удалённом хосте backups_remote_dir: "/var/backups/openclaw" rollback_remote_dir: "/var/backups/openclaw/rollback" # Бэкапы для 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 # макс. суммарный размер бэкапов на хосте; при превышении — WARNING в логе lock_timeout_warn_minutes: 30 # предупреждение + спросить Славу lock_timeout_force_minutes: 60 # считать потенциально мёртвым, проверить PID + подтверждение Славы timeouts: ssh_connect_timeout: 10 # ConnectTimeout для SSH (секунды) ssh_alive_interval: 15 # ServerAliveInterval (секунды) ssh_alive_count_max: 3 # ServerAliveCountMax (пропущенных keepalive до разрыва) command_timeout_default: 120 # --timeout по умолчанию для ssh_exec.sh (секунды) session: id_format: "{YYYYMMDD-HHMMSS}_{host}_{description}_{XXXX}" # XXXX = 4 случайных hex-символа для уникальности description_rules: allowed_chars: "a-z0-9-" max_length: 40 auto_normalize: true # авто-транслитерация и нормализация fallback: "action-{XXXX}" # если description не передан; XXXX = 4 hex-символа (единый формат с session ID) notifications: on_success: false on_failure: true on_rollback: true # Канал и механизм — на стороне агента, не скриптов on_failure: auto_rollback: false ask_user: true manager: cleanup_cron: "0 3 * * *" # ежедневно в 03:00 UTC на OpenClaw cleanup_heartbeat: true # также проверять статус через heartbeat 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/*" ``` --- ## .env.example ```bash # installer skill — подключения к хостам # Все ключи и пароли только здесь, никогда в parameters.yaml или скриптах # ruvpn-srv (ключ) RUVPN_SSH_KEY=/home/node/.openclaw/ha_ssh_key RUVPN_SUDO_PASS=your_ruvpn_sudo_password # mva154 (пароль) MVA154_PASSWORD=your_mva154_password MVA154_SUDO_PASS=your_mva154_sudo_password # ha (ключ) HA_SSH_KEY=/home/node/.openclaw/ha_ssh_key HA_TOKEN=your_ha_long_lived_token HA_URL=https://your-ha-url # localhost LOCALHOST_SUDO_PASS=your_localhost_sudo_password ``` --- ## checker.sh — проверка доступности хостов ### Интерфейс ```bash checker.sh [--host [--host ...]] [--all] # Exit 0 = все проверенные хосты доступны и секреты настроены # Exit 1 = одна или более проверок не прошла ``` ### Уровни ошибок — локализация Checker различает **5 уровней проблем** и сообщает точно где сломалось: | Код | Уровень | Смысл | |-----|---------|-------| | `SECRET_MISSING` | Секрет не настроен | Переменная в `.env` отсутствует или пустая | | `SECRET_INVALID` | Секрет некорректен | Переменная задана, но значение невалидно: файл ключа не найден / не читаем, токен слишком короткий (<10 символов) | | `AUTH_FAILED` | Аутентификация провалилась | Секрет есть и валиден локально, но ключ/пароль отвергнут хостом | | `HOST_UNREACHABLE` | Хост недоступен | Timeout, refused, DNS не резолвится | | `CHECK_FAILED` | Health check упал | Хост доступен, но `health_check` вернул ошибку | ### Вывод checker.sh **При успехе:** ``` ✅ ruvpn-srv — OK (12ms) ✅ mva154 — OK (45ms) | systemctl: running ✅ ha — OK (89ms) | ha core check: OK ✅ localhost — OK | sudo: OK All hosts: 4/4 OK ``` **При ошибках — с точной локализацией:** ``` ✅ ruvpn-srv — OK (12ms) ❌ mva154 — SECRET_MISSING: переменная MVA154_PASSWORD не найдена в .env Добавьте в ~/.openclaw/.env: MVA154_PASSWORD=your_password ❌ ha — SECRET_INVALID: файл ключа /home/node/.openclaw/ha_ssh_key не найден Проверьте путь в переменной HA_SSH_KEY ⚠️ localhost — AUTH_FAILED: sudo вернул ошибку (неверный пароль?) Проверьте переменную LOCALHOST_SUDO_PASS в .env Hosts: 1/4 OK | 2 errors | 1 warning ``` **JSON-вывод (для агентов):** ```json { "summary": {"total": 4, "ok": 1, "failed": 2, "warning": 1}, "hosts": { "ruvpn-srv": {"status": "ok", "latency_ms": 12}, "mva154": { "status": "failed", "error_code": "SECRET_MISSING", "error": "MVA154_PASSWORD not found in .env", "hint": "Add MVA154_PASSWORD=your_password to ~/.openclaw/.env" }, "ha": { "status": "failed", "error_code": "SECRET_INVALID", "error": "Key file /home/node/.openclaw/ha_ssh_key not found", "hint": "Check HA_SSH_KEY path in .env" }, "localhost": { "status": "warning", "error_code": "AUTH_FAILED", "error": "sudo authentication failed", "hint": "Check LOCALHOST_SUDO_PASS in .env" } } } ``` ### Порядок проверок для каждого хоста ``` 1. SECRET CHECK — все нужные переменные есть в .env и непустые? → если нет: SECRET_MISSING, стоп для этого хоста 1b. SECRET VALIDATE — значения переменных корректны? → для auth_type=key: файл ключа существует и читаем (-f && -r)? → для токенов (HA_TOKEN и т.д.): длина >10 символов? → если нет: SECRET_INVALID, стоп для этого хоста 2. CONNECTIVITY — хост достижим? (TCP connect с таймаутом 5 сек) → если нет: HOST_UNREACHABLE, стоп для этого хоста → для ssh-chain: сначала проверить каждый хоп в via[] 3. AUTH CHECK — аутентификация проходит? (ключ / пароль) → если нет: AUTH_FAILED, стоп для этого хоста 4. SUDO CHECK — если sudo: true, проверить sudo доступность → если нет: AUTH_FAILED (sudo), стоп 5. HEALTH CHECK — выполнить health_check команду если задана → если не OK: CHECK_FAILED 6. BACKUP DIR — проверить наличие /var/backups/openclaw на хосте → если нет: WARNING (не критично, создаётся при первом бэкапе) ``` ### Использование - **Перед первым запуском скилла** — обязательно - **При добавлении нового хоста** — проверить сразу после добавления в parameters.yaml - **При ошибках подключения** — для диагностики - **Через manager.sh** — `manager.sh --action status` включает connectivity check --- ## Добавление нового хоста 1. Добавить блок в `parameters.yaml`: ```yaml new-host: label: "Описание хоста" type: ssh-direct # или ssh-chain, local host: "IP_ADDRESS" port: 22 user: "username" ssh_key_env: "NEW_HOST_SSH_KEY" sudo: false health_check: null # или "systemctl is-system-running" post_check: null tags: [tag1, tag2] ``` 2. Добавить ключ в `~/.openclaw/.env`: ```bash NEW_HOST_SSH_KEY=/path/to/key ``` 3. Проверить подключение: ```bash scripts/ssh_exec.sh --host new-host --cmd "echo OK" ``` 4. Создать папку для бэкапов на хосте: ```bash scripts/ssh_exec.sh --host new-host --cmd "mkdir -p /var/backups/openclaw/rollback" ``` --- ## Управление (manager.sh) ### Автоматическое — cron на OpenClaw ```bash # Один cron, только на OpenClaw — чистит все хосты централизованно # Перед удалением записывает в registry.jsonl: {"action":"cleanup","deleted_sessions":[...],"ts":"..."} 0 3 * * * ~/.openclaw/skills/installer/scripts/manager.sh --action cleanup ``` ### Через heartbeat ``` HEARTBEAT.md → manager.sh --action status → показать состояние бэкапов ``` ### Вручную ```bash # Статус всех хостов manager.sh --action status # Список сессий за последние 7 дней manager.sh --action list-sessions --days 7 # Детали конкретной сессии manager.sh --action show-session --session 20260411-143000_ha_automations-fix # Откат конкретной сессии manager.sh --action rollback --session 20260411-143000_ha_automations-fix ``` --- ## Что реализует Dev 1. `SKILL.md` — инструкция со всеми разделами (см. список ниже) 2. `parameters.yaml` — по шаблону выше (уже заполнен) 3. `.env.example` — по шаблону выше 4. `scripts/session.sh` — создание сессии, lock (mkdir), нормализация description, cleanup, state machine (state.json) 5. `scripts/backup.sh` — бэкап нескольких файлов, генерация rollback.sh 6. `scripts/verify.sh` — health_check + post_check + --expect-entity/state 7. `scripts/rollback.sh` — откат файлов без set -e, явные статусы 8. `scripts/manager.sh` — cleanup, status, list-sessions, show-session, rollback по session_id 9. `scripts/checker.sh` — проверка доступности хостов с локализацией ошибок (5 уровней) 10. `scripts/ssh_exec.sh` — SSH-хелпер: ssh-direct, ssh-chain (ProxyCommand), local ### Обязательные разделы SKILL.md - Когда использовать (обязательно) - Когда НЕ использовать - Быстрый старт — 6 шагов - Скрипты — справочник с сигнатурами - Добавление нового хоста - Добавление новых секретов - Критические конфиги — подтверждение Славы - Поведение при ошибке - Управление (manager.sh) - Troubleshooting **Не входит в скоуп Dev:** - Наполнение `parameters.yaml` реальными IP/путями — уже готово - Настройка cron на OpenClaw — делает Слава/Стрим после установки