Files
backup-master/internal/agent/xui.go
T
mrleo1nidandClaude Opus 5.5 1d44d067e9 backup-master: сервер бэкапов, агент и восстановление VPS
Один бинарник 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>
2026-10-06 21:52:40 +03:00

48 lines
1.2 KiB
Go

package agent
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"strings"
"gitea.hsrv.site/mrleo1nid/backup-master/internal/manifest"
_ "modernc.org/sqlite"
)
const xuiDB = "/etc/x-ui/x-ui.db"
// snapshotXUI делает консистентную копию базы 3x-ui на горячую (VACUUM INTO),
// без логина в панель и без остановки сервиса.
func (c *collector) snapshotXUI(mc *manifest.Category) error {
if _, err := os.Stat(xuiDB); err != nil {
c.warn("xui: нет %s", xuiDB)
return nil
}
dir, err := c.spoolDir(mc.Name)
if err != nil {
return err
}
out := filepath.Join(dir, "x-ui.db")
if err := vacuumInto(xuiDB, out); err != nil {
c.warn("xui: снимок базы не удался: %v", err)
return nil
}
mc.Dumps = append(mc.Dumps, manifest.Dump{Name: "x-ui.db", Kind: "sqlite", Target: xuiDB})
c.add(manifest.MainPart, dumpItem(mc.Name, out, "x-ui.db"))
return nil
}
func vacuumInto(src, dst string) error {
_ = os.Remove(dst)
db, err := sql.Open("sqlite", "file:"+src+"?_pragma=busy_timeout(10000)")
if err != nil {
return err
}
defer db.Close()
_, err = db.Exec(fmt.Sprintf("VACUUM INTO '%s'", strings.ReplaceAll(dst, "'", "''")))
return err
}