Один бинарник backupctl: - server: API для агентов и restore, веб-морда (один админ), SQLite, ретеншн GFS, heavy-части, уведомления ntfy, раздача agent.sh/restore.sh; - backup/plan: сбор по каталогу категорий (files/systemd/xui/compose), горячие дампы Postgres/Mongo/MySQL, снимок x-ui.db, docker-тома, digest образов, проверка crontab, потоковая отправка tar.zst; - restore: провижининг на основе vps_installer с теми же версиями пакетов, recipes 3x-ui/nebula/sing-box нужных версий, восстановление compose с заливкой дампов и закреплением образов по digest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
42 lines
1.3 KiB
Go
42 lines
1.3 KiB
Go
package server
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSelectGFS(t *testing.T) {
|
|
// 60 дней ежедневных бэкапов + второй бэкап в последний день
|
|
now := time.Date(2026, 10, 6, 3, 0, 0, 0, time.Local)
|
|
times := []time.Time{now.Add(time.Hour)}
|
|
for d := 0; d < 60; d++ {
|
|
times = append(times, now.AddDate(0, 0, -d))
|
|
}
|
|
keep := SelectGFS(times, 7, 4, 3)
|
|
|
|
if !keep[0] {
|
|
t.Fatal("последний бэкап должен сохраняться")
|
|
}
|
|
if keep[1] {
|
|
t.Error("второй бэкап того же дня не должен сохраняться")
|
|
}
|
|
for i := 2; i <= 7; i++ {
|
|
if !keep[i] {
|
|
t.Errorf("дневной бэкап %d должен сохраняться", i)
|
|
}
|
|
}
|
|
// 7 дневных (вкл. idx 0) + недели/месяцы частично пересекаются; верхняя граница 7+4+3
|
|
if n := len(keep); n < 7 || n > 14 {
|
|
t.Errorf("сохранено %d бэкапов", n)
|
|
}
|
|
if keep[len(times)-1] && len(keep) == len(times) {
|
|
t.Error("ничего не удалено")
|
|
}
|
|
if len(SelectGFS(nil, 7, 4, 6)) != 0 {
|
|
t.Error("пустой список")
|
|
}
|
|
if k := SelectGFS(times, 0, 0, 0); len(k) != 1 || !k[0] {
|
|
t.Errorf("при нулевых keep_* остаётся только последний: %v", k)
|
|
}
|
|
}
|