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>
This commit is contained in:
commit
1d44d067e9
53 files changed
+7278
No files matched your search
@@ -0,0 +1,71 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Matcher проверяет абсолютные пути (со слешами) по списку исключений.
|
||||
//
|
||||
// - шаблон без glob-символов — префикс: "/opt/x/pgdata" исключает и сам каталог, и всё внутри;
|
||||
// - шаблон с "/" и glob-символами — сопоставляется с полным путём ("/etc/x-ui/x-ui.db*");
|
||||
// - шаблон без "/" — с именем файла ("*.swp").
|
||||
type Matcher struct {
|
||||
patterns []string
|
||||
}
|
||||
|
||||
func NewMatcher(patterns ...[]string) *Matcher {
|
||||
m := &Matcher{}
|
||||
for _, ps := range patterns {
|
||||
for _, p := range ps {
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
if p != "/" {
|
||||
p = strings.TrimSuffix(p, "/")
|
||||
}
|
||||
m.patterns = append(m.patterns, p)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Matcher) Add(p ...string) {
|
||||
m.patterns = append(m.patterns, NewMatcher(p).patterns...)
|
||||
}
|
||||
|
||||
func (m *Matcher) Match(abs string) bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
for _, p := range m.patterns {
|
||||
if !strings.ContainsAny(p, "*?[") {
|
||||
if abs == p || strings.HasPrefix(abs, p+"/") {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if strings.Contains(p, "/") {
|
||||
if ok, _ := path.Match(p, abs); ok {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ok, _ := path.Match(p, path.Base(abs)); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Covers сообщает, попадает ли abs под один из путей (точное совпадение или вложенность).
|
||||
func Covers(paths []string, abs string) bool {
|
||||
for _, p := range paths {
|
||||
p = strings.TrimSuffix(p, "/")
|
||||
if abs == p || strings.HasPrefix(abs, p+"/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in new issue
Block a user