Files
wiki/tasks/installer-skill/TZ_3.2.md
2026-04-14 13:40:01 +03:00

762 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ТЗ: 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`
```bash
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`
```bash
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`
```bash
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`
```bash
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`
```bash
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`
```bash
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`
**При успехе:**
```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_<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
```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_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-вывод (для агентов):**
```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 — делает Слава/Стрим после установки