Files
backup-master/README.md
T

236 lines
15 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.
# backup-master
Сервис бэкапов для VPS. Делает три вещи:
- принимает и хранит бэкапы, у него есть простая веб-морда для одного админа;
- агент на каждом сервере раз в сутки собирает бэкап по категориям, корректно дампит базы и записывает, какие версии софта стоят;
- восстанавливает бэкап **на новом сервере**: готовит систему (на основе [vps_installer](https://gitea.hsrv.site/mrleo1nid/vps_installer)), ставит **те же версии** утилит, раскладывает конфиги и данные, запускает сервисы.
Всё это делает один бинарник `backupctl`. Как он устроен внутри — в [docs/DESIGN.md](docs/DESIGN.md).
## Установка сервера
Ставим из [последнего релиза](https://gitea.hsrv.site/mrleo1nid/backup-master/releases/latest). Ссылка `releases/download/latest/...` всегда ведёт на свежую версию. Чтобы поставить конкретную версию, замените `latest` на тег, например `v0.1.0`.
```bash
REL=https://gitea.hsrv.site/mrleo1nid/backup-master/releases/download/latest
ARCH=$(dpkg --print-architecture) # amd64 или arm64
# пользователь и каталоги
sudo useradd -r -s /usr/sbin/nologin -d /var/lib/backup-master backup-master
sudo install -d -o backup-master -g backup-master -m 700 /var/lib/backup-master
sudo install -d /opt/backup-master/dist
# бинарники: свой — в /usr/local/bin, оба — в dist для раздачи агентам
for a in amd64 arm64; do sudo curl -fsSL "$REL/backupctl-linux-$a" -o "/opt/backup-master/dist/backupctl-linux-$a"; done
sudo curl -fsSL "$REL/checksums.txt" -o /tmp/backupctl-checksums.txt
(cd /opt/backup-master/dist && grep backupctl-linux /tmp/backupctl-checksums.txt | sha256sum -c -)
# через .new + mv: запущенный бинарник напрямую не перезаписать (Text file busy)
sudo install -m 755 "/opt/backup-master/dist/backupctl-linux-$ARCH" /usr/local/bin/backupctl.new
sudo mv -f /usr/local/bin/backupctl.new /usr/local/bin/backupctl
backupctl version
# администратор веб-морды
sudo -u backup-master BM_DATA=/var/lib/backup-master backupctl admin-passwd admin
# systemd: поправьте BM_PUBLIC_URL на внешний адрес сервера
sudo curl -fsSL "$REL/backup-master.service" -o /etc/systemd/system/backup-master.service
sudo sed -i 's|https://backup.example.com|https://backup.ВАШ-ДОМЕН|' /etc/systemd/system/backup-master.service
sudo systemctl daemon-reload
sudo systemctl enable --now backup-master
```
Сервер слушает `127.0.0.1:8080`, TLS даёт Caddy:
```bash
curl -fsSL "$REL/Caddyfile.example" # блок для /etc/caddy/Caddyfile, замените домен
sudo systemctl reload caddy
```
Агентам сервер раздаёт бинарники из `BM_DIST` (`/opt/backup-master/dist`).
### Caddy на другом хосте (например, сервер в VM Proxmox, Caddy на хосте)
Здесь Caddy проксирует не на `127.0.0.1`, а на IP виртуалки. В примере VM — `10.0.0.10`, хост Proxmox — `10.0.0.1`.
На VM сервис должен слушать сеть, а порт должен быть открыт только хосту с Caddy:
```bash
sudo sed -i 's|BM_LISTEN=127.0.0.1:8080|BM_LISTEN=0.0.0.0:8080|' /etc/systemd/system/backup-master.service
sudo systemctl daemon-reload && sudo systemctl restart backup-master
sudo ufw allow from 10.0.0.1 to any port 8080 proto tcp # если на VM включён UFW
```
На хосте с Caddy сначала проверьте доступ — должно вернуться `ok`:
```bash
curl -s http://10.0.0.10:8080/healthz
```
Затем укажите в Caddyfile адрес VM:
```
backup.example.com {
request_body {
max_size 200GB
}
reverse_proxy 10.0.0.10:8080 {
transport http {
read_timeout 6h
write_timeout 6h
}
}
}
```
```bash
sudo systemctl reload caddy
```
`BM_TRUST_PROXY=1` (включён в юните) говорит серверу брать IP клиента из `X-Forwarded-For` от Caddy — по нему ограничивается перебор пароля. Поэтому порт 8080 должен быть доступен **только** с хоста Caddy, иначе заголовок можно подделать. Если включён файрвол Proxmox для VM, разрешите 8080 и там.
Если Caddy с хоста получает 502, а на VM `curl http://127.0.0.1:8080/healthz` отвечает `ok`, проблема в `BM_LISTEN` или в файрволе.
### Администратор
Логин и пароль задаёт одна команда. Её же используют, чтобы сменить логин, пароль или оба сразу. Перезапуск сервиса не нужен, все открытые сессии сбрасываются:
```bash
sudo -u backup-master BM_DATA=/var/lib/backup-master backupctl admin-passwd новый_логин
```
**Обновление сервера:** снова выполните блок «бинарники», затем `sudo systemctl restart backup-master`. Агенты обновляются командой `curl -fsSL https://backup.ВАШ-ДОМЕН/agent.sh | sudo bash` без `--token`, конфиг при этом сохраняется.
### Уведомления
В «Настройках» включаются каналы: **ntfy** и/или **Telegram**. Оба необязательные, уведомления уходят во все настроенные.
Уведомления приходят:
- об ошибках бэкапа;
- когда от клиента нет бэкапа дольше заданного срока (по умолчанию 26 ч);
- если включено — и об успешных бэкапах. В Telegram они приходят без звука.
Каждое уведомление содержит ссылку на карточку клиента.
Как подключить Telegram:
1. Создайте бота у [@BotFather](https://t.me/BotFather) и скопируйте токен.
2. Напишите боту что-нибудь (или добавьте его в группу). Затем откройте `https://api.telegram.org/bot<токен>/getUpdates` и возьмите `chat.id`. У групп он вида `-100…`.
3. В «Настройках» укажите токен и chat id. Чатов можно несколько, через запятую. Для группы с темами дополнительно укажите ID темы.
4. Отметьте «Отправить тестовое уведомление» и сохраните — на странице будет видно, дошло ли сообщение в каждый канал.
Если `api.telegram.org` недоступен с сервера бэкапов, укажите в поле «Адрес Bot API» свой [Bot API сервер](https://github.com/tdlib/telegram-bot-api) или прокси.
## Добавление клиента
1. В веб-морде нажмите «Добавить клиента» и укажите домен сервера.
2. Выполните на сервере выданную команду:
```bash
curl -fsSL https://backup.example.com/agent.sh | sudo bash -s -- --token bm_... --now
```
Команда ставит `backupctl`, конфиг `/etc/backup-agent/agent.yaml` и таймер `backup-agent.timer` (03:00 ± 30 мин), показывает план (`backupctl plan`) и запускает первый бэкап.
3. В карточке клиента видно, что попадает в бэкап, версии софта и предупреждения. Например, cron запускает скрипт, который не попадает ни в одну категорию.
Полезные команды на клиенте:
```bash
sudo backupctl plan # что будет в бэкапе (дампы реально выполняются во временный каталог)
sudo systemctl start backup-agent # бэкап сейчас
journalctl -u backup-agent -f
sudo backupctl backup --heavy # с heavy-частями вне расписания
```
## Восстановление
В карточке клиента нажмите «Команда восстановления». Токен живёт 24 часа и даёт доступ только на чтение бэкапов этого клиента. Если нужен конкретный бэкап, откройте его и нажмите «Восстановить из этого бэкапа». На чистой Ubuntu 24.04:
```bash
curl -fsSL https://backup.example.com/restore.sh | sudo bash -s -- --token bm_...
```
Что происходит:
1. Показывается сводка бэкапа и запрашивается подтверждение.
2. Базовая подготовка системы (`internal/restore/provision.sh`): пользователь с sudo, hostname, fail2ban, UFW, blocklist, SSH hardening. Docker, Caddy и Telegraf ставятся **тех же версий apt-пакетов**, если такие версии ещё есть в репозитории. Спрашиваются только логин и пароль пользователя.
3. Распаковка: файлы ложатся на свои места, docker-тома — прямо в тома.
4. Категории восстанавливаются по порядку:
- 3x-ui, Nebula и sing-box ставятся той версии, что была на старом сервере. Если скачать не получилось, используется копия бинарника из бэкапа;
- база x-ui кладётся на место;
- compose-проекты: образы закрепляются по digest (`compose.restore.yml`), базы поднимаются, в них заливаются дампы, затем запускается весь проект;
- systemd-сервисы включаются и запускаются так, как было на старом сервере.
5. В конце выводятся итог и список того, что осталось сделать: DNS, lighthouse, перезагрузка.
Флаги:
- `--backup ID` — конкретный бэкап;
- `--skip-heavy` — без MinIO Stoat;
- `--skip-provision` — повторный прогон без базовой подготовки;
- `--user NAME`, `--yes`, `--keep-staging`.
## Что бэкапится
Встроенный каталог — [internal/catalog/default.yaml](internal/catalog/default.yaml):
| Категория | Что входит |
|---|---|
| system | правила UFW, все crontab (`/var/spool/cron/crontabs`), `ban-blacklist`, `/etc/sysctl.d` |
| xui | `/etc/x-ui` (снимок базы через `VACUUM INTO`, без логина в панель), `/root/cert`, версия 3x-ui |
| nebula | `/etc/nebula`, юнит, версия |
| caddy | `/etc/caddy`, `/var/www`, сертификаты |
| telegraf | `/etc/telegraf`, sudoers |
| sing-box-warp | `/opt/sing-box-warp`, `/etc/sing-box-warp`, юнит, sysctl, версия sing-box-extended |
| scripts | `/var/scripts`, `update-geosite.sh` |
| pnvpanel | `/opt/PnvPanel` целиком, кроме `pgdata`; `pg_dump`; том `ticket_uploads` |
| stoat | `/opt/stoat`, кроме `data/db` и `data/rabbit`; `mongodump`; `data/minio` идёт отдельной heavy-частью раз в неделю |
| stoat-mbot | `/opt/stoat-mbot` целиком, восстанавливается после stoat |
| psiphon | отключён |
Compose-проекты, которых нет в каталоге, находятся автоматически (`docker compose ls`). Они бэкапятся с настройками по умолчанию и помечаются в морде как «авто». Базы распознаются по образу (postgres, mongo, mysql, mariadb) и дампятся без остановки.
Каталог для конкретного клиента можно переопределить в его карточке (YAML):
```yaml
categories:
- {name: scripts, disabled: true} # отключить встроенную
- name: hostdb # своя категория с дампом
type: files
paths: [/etc/mysql]
dumps:
- name: full_dump.sql
cmd: mysqldump --all-databases --single-transaction
restore_cmd: mysql < "$DUMP_FILE"
```
## Сборка из исходников
```bash
make build # dist/backupctl-linux-amd64, dist/backupctl-linux-arm64
make test
```
Без make:
```bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "-s -w" -o dist/backupctl-linux-amd64 ./cmd/backupctl
```
CI (Gitea Actions):
- `.gitea/workflows/ci.yml` на каждый push и PR проверяет форматирование, запускает vet и тесты и собирает бинарники;
- `release.yml` на тег `v*` публикует релиз с бинарниками, `checksums.txt` и файлами из `deploy/`.
Новый релиз:
```bash
git tag v0.2.0 && git push origin v0.2.0
```
## Хранение
Бэкапы лежат в `/var/lib/backup-master/backups/<домен>/<дата>-<id>/`: `main.tar.zst`, heavy-части и `manifest.json`. Архив — обычный tar+zstd, его можно разобрать и без backupctl:
```bash
zstd -dc main.tar.zst | tar -tv
```
Ротация:
- обычные бэкапы — по схеме GFS: последний всегда + N дневных, недельных и месячных (по умолчанию 7/4/6);
- heavy-части — только последние `heavy_keep` (по умолчанию 1).