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