auto-sync: 2026-04-14 13:40:01
This commit is contained in:
739
tasks/installer-skill/TZ_3.3.md
Normal file
739
tasks/installer-skill/TZ_3.3.md
Normal file
@@ -0,0 +1,739 @@
|
||||
# ТЗ: 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 мин | Стоп. Сообщить кто держит и с какого времени. Ждать. |
|
||||
| 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.
|
||||
|
||||
---
|
||||
|
||||
## Поведение при ошибке
|
||||
|
||||
**Никогда не откатывать автоматически.** Агент спрашивает пользователя.
|
||||
|
||||
```
|
||||
❌ ОШИБКА на шаге: <название шага>
|
||||
📋 Что изменялось: <файл/сервис/команда>
|
||||
🔴 Текст ошибки: <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 | Для соответствия реальному формату |
|
||||
Reference in New Issue
Block a user