agentleFS
vefmvaivefmvai/sysadmin/.claude/skills/setup-backups/SKILL.md

setup-backups

Skill1 starsChanged 3 months agoMITView on GitHub

Бэкапы с нуля: restic для encrypted snapshots, offsite-хранилище (S3 / B2 / WebDAV — Яндекс.Диск, NextCloud), retention 7+4+6 (daily+weekly+monthly), алерт «бэкап >36 ч» в Telegram/Slack/email, обязательный прогон restore на временный контейнер. Покрывает БД (PostgreSQL, MySQL, Redis dump). Триггеры: «нужны бэкапы», «настрой backup», «restic с retention», «offsite на S3/WebDAV/Backblaze», «без бэкапов нельзя ничего менять». НЕ для бэкапа uploads/файлов (это отдельный шаблон); НЕ для setup без настроенного хранилища.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Важно: STOP-сообщение при backups.enabled=false дословно содержит /sysadmin-init --reconfigure — это единственный путь оператора к …

    line 115

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md
<role> Я настраиваю инфраструктуру бэкапов так, чтобы у оператора больше никогда не было «надежды вместо бэкапа». Реальный прогон restore на временный контейнер — обязательная часть скилла; без подтверждённого восстановления хотя бы одной БД скилл не считается завершённым. </role><context> Что предполагается на сервере: - Docker и хотя бы одна БД (PostgreSQL / MySQL / Redis), которую нужно бэкапить - Хранилище для бэкапов настроено (S3 bucket / Backblaze B2 / WebDAV-endpoint типа Яндекс.Диск / NextCloud) - Креденшелы хранилища доступны через менеджер паролей (Keychain / pass / KeePassXC / ...) - Канал алертов настроен (Telegram-бот / Slack webhook / email — опционально, но рекомендуется)

Что НЕ предполагается:

  • Настройка хранилища с нуля (это отдельная задача, делает оператор)
  • Бэкап файловых uploads приложений (отдельный шаблон, не входит в этот скилл)
  • БД на отдельной машине / managed Cloud DB — другой подход, не покрывается</context>
<goals> После выполнения должно стать TRUE: - restic-репозиторий создан с шифрованием AES-256, passphrase в менеджере паролей - Скрипты дампа БД установлены (`backup-postgres.sh` / `backup-mysql.sh` / `backup-redis.sh`) - Оркестратор `backup-all.sh` запускает все БД-дампы и заливает их через restic - Cron-расписание установлено в `/etc/cron.d/backup` (бэкап в 03:00, проверка возраста в 09:00) - `check-backup-age.sh` отправляет алерт «бэкап старше 36 часов» в канал оператора (Telegram / Slack / email — настраивается параметрами) - Runbook `runbooks/backup-restore.md` сгенерирован по шаблону под конкретный сервер - **Restore test пройден** — одна из БД восстановлена на временный контейнер, row counts сверены с продом </goals>

Шаг 0: Чтение конфига (STRICT)

Скилл — STRICT-режим: без конфига инфры (infra-config.json) он не запускается. Конфиг определяет, куда складываются бэкапы (S3 / B2 / WebDAV), какая retention, нужны ли Telegram-алерты и в каком менеджере паролей искать restic-passphrase. Без этих решений скилл угадывал бы намерения — это запрещено правилами агента.

Используй общий helper _lib/find-config.sh (единая точка изменения для всех STRICT/OPTIONAL скиллов — алгоритм идентичен Cold Start Protocol персоны). $SYSADMIN_ROOT запоминается на Шаге 1 Cold Start.

source "$SYSADMIN_ROOT/.claude/skills/_lib/find-config.sh"

# STRICT: exit 1 с понятным сообщением если конфига нет
find_sysadmin_config strict       # $CONFIG = infra-config.json (backups/notifications)
find_brain_config || true         # $BRAIN_CONFIG = agent-config.json (secrets.manager)

# secrets.manager — агент-поле (ADR-0013): живёт в мозге ($BRAIN_CONFIG).
# Legacy-совместимость: если мозга нет — читаем из $CONFIG (старый единый формат).
get_agent_field() {  # $1=jq-путь, $2=default (путь одинаков в мозге и legacy)
  local v=""
  [ -n "${BRAIN_CONFIG:-}" ] && v=$(jq -r "$1 // empty" "$BRAIN_CONFIG" 2>/dev/null)
  [ -z "$v" ] && [ -n "${CONFIG:-}" ] && [ -f "${CONFIG:-}" ] && v=$(jq -r "$1 // empty" "$CONFIG" 2>/dev/null)
  [ -z "$v" ] && v="$2"
  echo "$v"
}

# Подсистема должна быть включена (инфра-поле → $CONFIG)
BAK_ENABLED=$(get_config_field backups.enabled false)
if [ "$BAK_ENABLED" != "true" ]; then
    cat <<'EOF' >&2
В infra-config.json указано backups.enabled=false — бэкапы не настраиваются.

Если хочешь включить — запусти /sysadmin-init --reconfigure
и переключи backups.enabled на true. После этого скилл заработает.
EOF
    exit 0
fi

# Чтение значений из конфига
BACKUP_DESTINATION_FROM_CONFIG=$(get_config_field backups.destination)
RCLONE_REMOTE_FROM_CONFIG=$(get_config_field backups.rclone_remote)
RETENTION_DAYS_FROM_CONFIG=$(get_config_field backups.retention.daily 7)
RETENTION_WEEKS_FROM_CONFIG=$(get_config_field backups.retention.weekly 4)
RETENTION_MONTHS_FROM_CONFIG=$(get_config_field backups.retention.monthly 6)

# Telegram — есть/нет
TG_ENABLED=$(get_config_field notifications.telegram.enabled false)
[ "$TG_ENABLED" = "true" ] && ALERT_CHANNEL_FROM_CONFIG="telegram" || ALERT_CHANNEL_FROM_CONFIG=""

# Менеджер паролей → конвенция индекса для restic-passphrase (агент-поле → мозг)
SECRETS_MANAGER=$(get_agent_field '.secrets.manager' keychain)
case "$SECRETS_MANAGER" in
    keychain)   BACKUP_PASS_REF_FROM_CONFIG="keychain://infra/restic-passphrase" ;;
    pass)       BACKUP_PASS_REF_FROM_CONFIG="pass:infra/restic-passphrase" ;;
    1password)  BACKUP_PASS_REF_FROM_CONFIG="op://infra/restic/passphrase" ;;
    bitwarden)  BACKUP_PASS_REF_FROM_CONFIG="bw://infra/restic-passphrase" ;;
    *) BACKUP_PASS_REF_FROM_CONFIG="" ;;
esac

# CLI-override > конфиг (для отладочных прогонов и edge cases)
BACKUP_DESTINATION="${BACKUP_DESTINATION:-$BACKUP_DESTINATION_FROM_CONFIG}"
RCLONE_REMOTE="${RCLONE_REMOTE:-$RCLONE_REMOTE_FROM_CONFIG}"
RETENTION_DAYS="${RETENTION_DAYS:-$RETENTION_DAYS_FROM_CONFIG}"
RETENTION_WEEKS="${RETENTION_WEEKS:-$RETENTION_WEEKS_FROM_CONFIG}"
RETENTION_MONTHS="${RETENTION_MONTHS:-$RETENTION_MONTHS_FROM_CONFIG}"
ALERT_CHANNEL="${ALERT_CHANNEL:-$ALERT_CHANNEL_FROM_CONFIG}"
BACKUP_PASS_REF="${BACKUP_PASS_REF:-$BACKUP_PASS_REF_FROM_CONFIG}"

Важно: STOP-сообщение при backups.enabled=false дословно содержит /sysadmin-init --reconfigure — это единственный путь оператора к включению подсистемы. Без явного указания пути STOP превращается в тупик.

Бэкап секретов всё равно из менеджера паролей. TG-токен, S3-keys, restic passphrase — все живут в менеджере паролей оператора (определяется по secrets.manager), не в конфиге. Конфиг хранит только индекс/имя, по которому скилл найдёт значение в момент работы.

Параметры

ПараметрDefaultОписание
BACKUP_DESTINATION(из infra-config.json: backups.destination)s3 / b2 / yandex-disk-webdav / nextcloud-webdav / owncloud-webdav / local
RCLONE_REMOTE(из infra-config.json: backups.rclone_remote — для webdav)Имя rclone-remote из ~/.config/rclone/rclone.conf
BACKUP_USER(required для webdav)WebDAV username (берётся из менеджера паролей при выполнении)
BACKUP_PASS_REF(из agent-config.json: secrets.manager + конвенция индекса)Ссылка на passphrase в менеджере паролей
S3_ACCESS_KEY / S3_SECRET_KEY(required для s3, из менеджера паролей)S3 credentials
DATABASES(autodetect)Список БД-контейнеров через запятую
RETENTION_DAYS(из infra-config.json: backups.retention.daily)Daily snapshots
RETENTION_WEEKS(из infra-config.json: backups.retention.weekly)Weekly snapshots
RETENTION_MONTHS(из infra-config.json: backups.retention.monthly)Monthly snapshots
ALERT_CHANNEL(из infra-config.json: notifications.telegram.enabled → telegram)telegram / slack / email — какой канал использовать
ALERT_TOKEN(optional, из менеджера паролей)Токен/webhook (Telegram bot token, Slack incoming webhook URL, SMTP credentials ref)
ALERT_TARGET(optional)Получатель (Telegram chat_id, Slack channel, email address)
BACKUP_DIR/opt/backups/dbsЛокальная директория промежуточных дампов
RESTIC_REPO_PATHbackups/infraПуть репозитория внутри хранилища

Процедура

Шаг 1: Pre-check

Проверить инструменты и доступы перед началом:

# Инструменты
which rclone restic jq || echo "Поставить недостающие"

# Доступ к хранилищу (для WebDAV-варианта типа Яндекс.Диск, NextCloud)
# Имя remote'а в rclone задаётся оператором при `rclone config` — например, `webdav-backup`.
rclone lsd "$RCLONE_REMOTE": || echo "Сначала настроить rclone config"

# Список БД-контейнеров (autodetect)
docker ps --format '{{.Names}}' | grep -E '(postgres|mysql|mariadb|redis)'

# Свободное место под локальные дампы (>= 2x размер крупнейшей БД)
df -h /opt

Verify: все инструменты в PATH, хранилище доступно, есть >=2x места.

Шаг 2: Создание restic-репозитория

restic создаёт зашифрованный репозиторий один раз. Passphrase ОБЯЗАТЕЛЬНО хранить в менеджере паролей — если потерять, бэкапы становятся бесполезными (ключ AES-256 без passphrase не расшифровать).

# Прочитать passphrase из менеджера паролей
export RESTIC_PASSWORD="$(read_from_vault $BACKUP_PASS_REF)"

# Установить URL репозитория (под выбранное хранилище)
case "$BACKUP_DESTINATION" in
  s3)     export RESTIC_REPOSITORY="s3:s3.amazonaws.com/$BACKUP_BUCKET/$RESTIC_REPO_PATH" ;;
  b2)     export RESTIC_REPOSITORY="b2:$BACKUP_BUCKET:$RESTIC_REPO_PATH" ;;
  webdav) export RESTIC_REPOSITORY="rclone:$RCLONE_REMOTE:$RESTIC_REPO_PATH" ;;  # Яндекс.Диск / NextCloud / ownCloud
  *) echo "ERROR: BACKUP_DESTINATION не задан или неизвестен (s3/b2/webdav)" >&2; exit 2 ;;
esac

# Инициализация (только один раз!)
restic init

Verify: restic snapshots отвечает пустым списком без ошибки.

Шаг 3: Установка скриптов дампа БД

Скопировать scripts/backup-*.sh в /opt/infra/scripts/backup/:

install -m 0755 scripts/backup-postgres.sh /opt/infra/scripts/backup/backup-postgres.sh
install -m 0755 scripts/backup-mysql.sh    /opt/infra/scripts/backup/backup-mysql.sh
install -m 0755 scripts/backup-redis.sh    /opt/infra/scripts/backup/backup-redis.sh
install -m 0755 scripts/backup-all.sh      /opt/infra/scripts/backup/backup-all.sh
install -m 0755 scripts/check-backup-age.sh /opt/infra/scripts/backup/check-backup-age.sh

Каждый скрипт принимает имя контейнера как аргумент, делает docker exec для дампа изнутри контейнера (НЕ с хоста — иначе несовместимость версий клиента и сервера ломает дамп) и складывает результат в $BACKUP_DIR с timestamp в имени.

Шаг 4: Конфигурация оркестратора backup-all.sh

Оркестратор последовательно:

  1. Запускает все БД-дампы по списку $DATABASES
  2. Делает restic backup всей $BACKUP_DIR
  3. Удаляет локальные файлы старше 1 дня (только локальные, offsite остаётся)
  4. Запускает restic forget --prune с retention 7+4+6
  5. Логирует всё в /var/log/backup-cron.log

set -e намеренно НЕ включён — если упала одна БД, остальные должны успеть забэкапиться.

Шаг 5: Cron-расписание

Скопировать templates/backup-cron-d в /etc/cron.d/backup:

# Полный бэкап раз в сутки в 03:00 UTC (наименьшая нагрузка)
0 3 * * * root /opt/infra/scripts/backup/backup-all.sh >> /var/log/backup-cron.log 2>&1

# Проверка возраста раз в сутки в 09:00 UTC (после того как ночной бэкап точно завершился)
0 9 * * * root /opt/infra/scripts/backup/check-backup-age.sh >> /var/log/backup-cron.log 2>&1

Verify: systemctl status cron running, cat /etc/cron.d/backup показывает обе строки.

Шаг 6: Алерт о возрасте бэкапа

scripts/check-backup-age.sh:

  1. Читает restic snapshots --latest 1 --json → timestamp последнего snapshot
  2. Если старше 36 часов И ALERT_CHANNEL настроен → отправляет алерт через соответствующий транспорт (Telegram bot API / Slack incoming webhook / mail-команда — выбирается case-блоком).
  3. Без ALERT_CHANNEL — пишет WARNING в /var/log/backup-cron.log (не молчит, но и не падает).

Порог 36 часов (а не 24) — даёт 12-часовое окно на повторный прогон, если первая попытка упала из-за временной недоступности хранилища.

Шаг 7: Restore test (ОБЯЗАТЕЛЬНО)

Без этого шага скилл НЕ считается завершённым. Бэкап, который ни разу не восстанавливали, — это надежда, а не бэкап.

Ветка «БД на сервере ещё нет» (свежий сервер: бэкапы настраиваются ДО переноса данных). Конвейер всё равно проверяется целиком, на синтетике: тестовый контейнер с известным числом строк → прогон оркестратора backup-all.sh (не отдельного дамп-скрипта!) → restore во второй контейнер → сверка counts → уборка обоих контейнеров и тестового snapshot (restic forget --tag e2e-test --prune). Полная процедура ветки и грабля ожидания pg_isready — references/pipeline-pitfalls.md.

Ветка «БД уже есть» — боевая сверка. Порядок восстановления PostgreSQL канонический и описан по шагам в references/pg-restore-order.md (образ контейнера → globals → createdb → pg_restore → сверка row counts); там же готовые команды, игнорируемые ошибки и pgvector. Здесь не дублирую — открываю справочник и иду по нему. Для MySQL и Redis пошагового справочника нет; что известно и чего не проверяли — references/pipeline-pitfalls.md.

Каркас прогона:

restic restore latest --target /tmp/restore-test     # 1. извлечь свежий snapshot
# 2-4. поднять временный контейнер тем же образом, что на проде,
#      восстановить globals → createdb → pg_restore   (см. pg-restore-order.md)
# 5. сверить row counts главной таблицы: прод против временного контейнера
docker rm -f pg-restore-test                          # 6. только после успешной сверки

Verify: row counts совпадают (допуск ±несколько записей от дневной активности между дампом и сверкой); если расхождение значимое → блокер, скилл не закрывается, разбираться.

Шаг 8: Документация в runbook

Скопировать templates/backup-restore-runbook.md → runbooks/backup-restore.md и заполнить плейсхолдеры:

  • <CONTAINERS> → конкретные имена контейнеров на этом сервере
  • <MAIN_DB> → имя главной БД для restore-test
  • <DESTINATION> → конкретное хранилище (s3 / b2 / webdav-через-Я.Диск или NextCloud + путь)
  • <LAST_VERIFIED> → дата фактического прогона Шага 7

Финальная строка runbook: Last verified: YYYY-MM-DD, by <agent>, on <production-host>.

Что помню до чтения справки

Три правила обязаны срабатывать сразу, иначе бэкап будет выглядеть настроенным и не быть им:

  • Бэкап без проверенного restore — не бэкап. Шаг 7 не пропускается никогда, а прогон идёт через сам backup-all.sh: ручной вызов restic с руками экспортированными переменными маскирует главный класс багов (разбор — references/pipeline-pitfalls.md).
  • Потеря passphrase = потеря всех бэкапов навсегда. AES-256 без ключа не расшифровать. Passphrase живёт в менеджере паролей оператора, а не в конфиге и не в git.
  • Managed-БД этот скилл не покрывает. Увидел RDS / Managed Postgres / Supabase — говорю честно, что нужен другой подход, и не изображаю настройку.

Bundled resources

ФайлЧто это и когда открывать
references/pipeline-pitfalls.mdОбвязка ведёт себя не так, как обещано: source без set -a (самая дорогая грабля, симптом «дампы SUCCESS, restic Fatal»), pg_dump с хоста, set -e в оркестраторе, Redis SAVE вместо BGSAVE, curl без таймаута, тарифицируемые multipart-огрызки, права на env-файл, граничные случаи (большие БД, managed, приватная сеть)
references/restic-quirks.mdСам restic и хранилища: форма RESTIC_REPOSITORY по типам, WebDAV-тротлинг и transfers = 1, права bucket-полиси, forget без --prune, параметры backup, безопасность passphrase, производительность, таблица частых ошибок
references/pg-restore-order.mdПорядок восстановления PostgreSQL: globals → createdb → pg_restore, pgvector (нужен образ pgvector/pgvector, иначе type "public.vector" does not exist), игнорируемые ошибки, сверка row counts, большие БД и параллельный restore. Открывать на Шаге 7
scripts/backup-postgres.sh, backup-mysql.sh, backup-redis.shдампы по типам БД (Шаг 3)
scripts/backup-all.shоркестратор: все дампы + restic backup + retention (Шаг 4)
scripts/check-backup-age.shалерт «бэкап старше 36 часов» в канал оператора (Шаг 6)
templates/backup-cron-dшаблон /etc/cron.d/backup (Шаг 5)
templates/backup-restore-runbook.mdшаблон runbooks/backup-restore.md (Шаг 8)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.