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
In this repository25 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- audit-securitySkill
- bootstrap-new-serverSkill
- cleanup-existing-serverSkill
- configure-vpn-routingSkill
- deploy-serviceSkill
- devSkill
- extract-subscription-serversSkill
- finalize-vpn-routingSkill
- generate-client-configSkill
- health-checkSkill
- install-monitoring-stackSkill
- inventory-scanSkill
- migrate-server-to-serverSkill
- refresh-vpn-knowledgeSkill
- restore-from-backupSkill
- retroSkill
- rotate-secretsSkill
- second-opinionSkill
- setup-happ-subscriptionSkill
- setup-secrets-vaultSkill
- setup-server-proxySkill
- setup-vpn-panelSkill
- sysadmin-initSkill
- sysadmin-meetSkill
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
- Very specific: line 115
Важно: STOP-сообщение при backups.enabled=false дословно содержит /sysadmin-init --reconfigure — это единственный путь оператора к …
≈ “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.
Files
<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_PATH | backups/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 /optVerify: все инструменты в 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 initVerify: 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
Оркестратор последовательно:
- Запускает все БД-дампы по списку
$DATABASES - Делает
restic backupвсей$BACKUP_DIR - Удаляет локальные файлы старше 1 дня (только локальные, offsite остаётся)
- Запускает
restic forget --pruneс retention 7+4+6 - Логирует всё в
/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>&1Verify: systemctl status cron running, cat /etc/cron.d/backup показывает обе строки.
Шаг 6: Алерт о возрасте бэкапа
scripts/check-backup-age.sh:
- Читает
restic snapshots --latest 1 --json→ timestamp последнего snapshot - Если старше 36 часов И
ALERT_CHANNELнастроен → отправляет алерт через соответствующий транспорт (Telegram bot API / Slack incoming webhook /mail-команда — выбирается case-блоком). - Без
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) |
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
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.

