Files
wiki/tasks/installer-skill/TZ.md
2026-04-12 21:55:33 +03:00

34 KiB
Raw Blame History

ТЗ: 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_<host>_<description>_<XXXX>
   - Инициализировать лог-файл сессии и 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

session.sh --host <host_id> --desc <описание> --agent <agent_name>
# Возвращает 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/<session_id>/state.json
Содержит: {"session_id":"...","host":"...","current_step":"health|backup|validate|change|verify",
           "started":"...","updated":"..."}
Обновляется при переходе между шагами pipeline.
При успешном завершении — удаляется.
При ошибке — остаётся для диагностики и возможности продолжить.

backup.sh

backup.sh --session <session_id> --host <host_id> \
          --files <file1> [<file2> ...] \
          --post-rollback-action <reload_type|none>
# Exit 0 = JSON: {"backups":[...],"rollback":"<path>"}
# Exit 1 = JSON: {"error":"...","failed_file":"...","cleaned_up":true}
# При ошибке: СТОП, все уже скопированные бэкапы удаляются (staging), rollback.sh НЕ создаётся

verify.sh

verify.sh --host <host_id> --session <session_id> --stage <pre|post> \
          [--expect-entity <entity_id> --expect-state <state>]
# Exit 0 = JSON: {"status":"ok","check":"..."}
# Exit 1 = JSON: {"status":"failed","check":"...","error":"..."}

rollback.sh

rollback.sh --session <session_id> --host <host_id>
# Восстанавливает файлы в обратном порядке изменений
# НЕ выполняет 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

manager.sh --action cleanup [--host <host_id>]   # чистка старых бэкапов + orphaned state.json/lock
manager.sh --action status [--host <host_id>]    # кол-во бэкапов, размер, даты
manager.sh --action list-sessions [--host <host_id>] [--days N]  # список сессий
manager.sh --action show-session --session <id>  # детали сессии
manager.sh --action rollback --session <id>      # откат конкретной сессии

ssh_exec.sh

ssh_exec.sh --host <host_id> --cmd "<команда>" [--timeout <seconds>]
# Читает parameters.yaml, строит SSH/ProxyCommand/local exec автоматически
# Для type=local: выполняет команду напрямую (не SSH)
# Для type=ssh-chain: использует ProxyCommand (ключ НЕ хранится на jump-хосте)
# SSH-опции берутся из parameters.yaml → timeouts (ConnectTimeout, ServerAliveInterval, ServerAliveCountMax)
# --timeout оборачивает команду в `timeout <seconds>` (по умолчанию: 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/<host>/
Создание: mkdir (атомарно) — если mkdir вернул ошибку, lock занят
Содержимое: info.json с полями agent, session, started, pid (если доступен)

Три уровня реакции по возрасту lock:

Возраст Действие
< 30 мин Стоп. Сообщить кто держит и с какого времени. Ждать.
3060 мин Уведомить Славу. Предложить снять. Ждать явного ОК.
> 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.


Поведение при ошибке

Никогда не откатывать автоматически. Агент спрашивает пользователя.

❌ ОШИБКА на шаге: <название шага>
📋 Что изменялось: <файл/сервис/команда>
🔴 Текст ошибки: <stderr / exit code>
⚠️ Текущее состояние: <что уже применено, что нет>

Если ОТКАТИТЬ:
  ✅ <что вернётся в исходное состояние>
  ⚠️ <возможные побочные эффекты отката>
  🔧 Команда: <путь к rollback.sh>

Если НЕ откатывать:
  ⚠️ <риски оставить как есть>
  💡 <возможные ручные действия>

Выполнить откат? (да / нет)

При rollback_failed — немедленно уведомить Славу:

🚨 ТРЕБУЕТСЯ РУЧНОЕ ВМЕШАТЕЛЬСТВО
Откат не удался. Файл может быть в неконсистентном состоянии.
Сессия: <id>
Хост: <host>
Бэкап: <path>

Критические конфиги — обязательное подтверждение Славы

Перед шагом ИЗМЕНЕНИЕ — запросить явное "да":

Тип Примеры файлов
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_<host>_<desc>_<XXXX>.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

При успехе:

{"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"}

При ошибке:

{"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_<filename>.bak
  /var/backups/openclaw/rollback/YYYYMMDD-HHMMSS_<desc>_rollback.sh

Локальная копия метаданных (всегда, для всех хостов):
  ~/.openclaw/workspace/installer/sessions/<session_id>/rollback_meta.json
  # Содержит: список файлов, пути бэкапов, post_rollback_action
  # Позволяет восстановить rollback.sh если хост временно недоступен

На localhost (OpenClaw workspace):
  ~/.openclaw/workspace/installer/backups/localhost/YYYYMMDD-HHMMSS_<filename>.bak
  ~/.openclaw/workspace/installer/backups/localhost/rollback/YYYYMMDD-HHMMSS_<desc>_rollback.sh

Содержимое rollback.sh

#!/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

# 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

# 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 — проверка доступности хостов

Интерфейс

checker.sh [--host <host_id> [--host <host_id2> ...]] [--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-вывод (для агентов):

{
  "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.shmanager.sh --action status включает connectivity check

Добавление нового хоста

  1. Добавить блок в parameters.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]
  1. Добавить ключ в ~/.openclaw/.env:
NEW_HOST_SSH_KEY=/path/to/key
  1. Проверить подключение:
scripts/ssh_exec.sh --host new-host --cmd "echo OK"
  1. Создать папку для бэкапов на хосте:
scripts/ssh_exec.sh --host new-host --cmd "mkdir -p /var/backups/openclaw/rollback"

Управление (manager.sh)

Автоматическое — cron на OpenClaw

# Один 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 → показать состояние бэкапов

Вручную

# Статус всех хостов
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 — делает Слава/Стрим после установки