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

740 lines
33 KiB
Markdown
Raw Permalink 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-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_<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"
- Скопировать ВСЕ изменяемые файлы в 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 <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 (через lib.sh → update_state).
При успешном завершении — удаляется (через lib.sh → remove_state).
При ошибке — остаётся для диагностики и возможности продолжить.
```
### `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 НЕ создаётся
# Бэкапы сохраняются в backup_dir хоста (из parameters.yaml → hosts.<id>.backup_dir)
# Лимиты: файл > 50MB → СТОП; суммарно > 500MB → WARNING
```
### `verify.sh`
```bash
verify.sh --host <host_id> --session <session_id> --stage <pre|post>
# 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 <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,"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 <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>] [--session <id>]
# Читает 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 <seconds>` (по умолчанию: 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" "<step>" # health | backup | validate | change | verify
remove_state "$SESSION" # удаление state.json при успехе
write_registry "$SESSION" "<host>" "<status>" [--files "<path>"] [--agent "<name>"]
# запись в registry.jsonl
# write_registry также поддерживает:
# --failed-step "<step>" --error "<msg>" # для failed/timeout
# --state-unknown # для timeout на шаге change
```
### `yaml_get.js`
```bash
node scripts/yaml_get.js <params_file> <action> [args...]
# Actions:
# get <dot.path> → print value
# get-host-field <host> <field> → print host field (поддерживает вложенные пути)
# get-host-json <host> → print host as JSON
# get-via <host> → 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/<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>
Если НЕ откатывать:
⚠️ <риски оставить как есть>
💡 <возможные ручные действия>
Выполнить откат? (да / нет)
```
**При `state_unknown: true`** (timeout на шаге CHANGE) — особо предупредить:
> ⚠️ ВНИМАНИЕ: timeout во время записи файла — состояние может быть неконсистентным!
**При `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/*` |
| OpenClaw | `openclaw.json` |
Формат запроса:
```
⚠️ КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ: [тип]
📋 Файл: [путь]
🔍 Изменения: [diff]
✅ Бэкап готов: [путь]
Подтвердить изменение? (да / нет)
```
---
## Логирование
### Лог-файл сессии
**Путь:** `~/.openclaw/workspace/installer/logs/<session_id>.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):
<host.backup_dir>/YYYYMMDD-HHMMSS_<filename>.bak
<host.rollback_dir>/YYYYMMDD-HHMMSS_<desc>_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/<session_id>/rollback_meta.json
# Содержит: список файлов, пути бэкапов, post_rollback_action
На 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_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_id> [--host <host_id2> ...]] [--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.<id>.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 <session_id>
manager.sh --action rollback --session <session_id>
```
---
## Что реализует 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.<id>.backup_dir | Ранее проверял глобальный /var/backups/openclaw |
| 14 | Session ID в примерах: добавлен суффикс _XXXX | Для соответствия реальному формату |