- новая категория fail2ban: jail.local, jail.d (в т.ч. 3x-ipl.local), IP-лимит 3x-ui (filter.d/action.d/3x-ipl.conf), свои *.local; при восстановлении создаются логи 3xipl*.log, без которых jail не поднимается - xui: свои *.dat из /usr/local/x-ui/bin (ipcheck, discord-*) — на них ссылается роутинг; штатные geo-файлы релиза не бэкапятся - recipe 3x-ui больше не очищает /usr/local/x-ui и не стирает восстановленные файлы - маски (*, ?) в paths и keep_binaries каталога - provision ставит nftables вместе с fail2ban - .gitattributes: LF во всех файлах Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
83832fc734
commit
cbd3cde01d
7 files changed
+110
-7
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
*.sh text eol=lf
|
||||||
@@ -172,7 +172,8 @@ curl -fsSL https://backup.example.com/restore.sh | sudo bash -s -- --token bm_..
|
|||||||
| Категория | Что входит |
|
| Категория | Что входит |
|
||||||
|---|---|
|
|---|---|
|
||||||
| system | правила UFW, все crontab (`/var/spool/cron/crontabs`), `ban-blacklist`, `/etc/sysctl.d` |
|
| system | правила UFW, все crontab (`/var/spool/cron/crontabs`), `ban-blacklist`, `/etc/sysctl.d` |
|
||||||
| xui | `/etc/x-ui` (снимок базы через `VACUUM INTO`, без логина в панель), `/root/cert`, версия 3x-ui |
|
| xui | `/etc/x-ui` (снимок базы через `VACUUM INTO`, без логина в панель), `/root/cert`, свои geo-файлы `/usr/local/x-ui/bin/*.dat` (ipcheck, discord и т.п., кроме штатных), версия 3x-ui |
|
||||||
|
| fail2ban | `jail.local`, `jail.d/` (в т.ч. `3x-ipl.local`), IP-лимит 3x-ui (`filter.d`/`action.d/3x-ipl.conf`), свои `*.local`; при восстановлении создаются логи `3xipl.log` |
|
||||||
| nebula | `/etc/nebula`, юнит, версия |
|
| nebula | `/etc/nebula`, юнит, версия |
|
||||||
| caddy | `/etc/caddy`, `/var/www`, сертификаты |
|
| caddy | `/etc/caddy`, `/var/www`, сертификаты |
|
||||||
| telegraf | `/etc/telegraf`, sudoers |
|
| telegraf | `/etc/telegraf`, sudoers |
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ func (c *collector) collectSpec(s catalog.Spec) (*manifest.Category, error) {
|
|||||||
RestoreAfter: s.RestoreAfter,
|
RestoreAfter: s.RestoreAfter,
|
||||||
}
|
}
|
||||||
skip := archive.NewMatcher(c.cat.Exclude, s.Exclude)
|
skip := archive.NewMatcher(c.cat.Exclude, s.Exclude)
|
||||||
for _, p := range s.Paths {
|
for _, p := range expandPaths(s.Paths) {
|
||||||
if !sysx.Exists(p) {
|
if !sysx.Exists(p) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ func (c *collector) collectSpec(s catalog.Spec) (*manifest.Category, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range s.KeepBinaries {
|
for _, b := range expandPaths(s.KeepBinaries) {
|
||||||
if sysx.Exists(b) {
|
if sysx.Exists(b) {
|
||||||
mc.Binaries = append(mc.Binaries, b)
|
mc.Binaries = append(mc.Binaries, b)
|
||||||
c.add(manifest.MainPart, binItem(s.Name, b))
|
c.add(manifest.MainPart, binItem(s.Name, b))
|
||||||
@@ -236,6 +236,20 @@ func (c *collector) cmdDump(cat string, d catalog.CmdDump, mc *manifest.Category
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expandPaths раскрывает маски (*, ?, [...]) в путях категории; пути без масок — как есть.
|
||||||
|
func expandPaths(paths []string) []string {
|
||||||
|
var out []string
|
||||||
|
for _, p := range paths {
|
||||||
|
if !strings.ContainsAny(p, "*?[") {
|
||||||
|
out = append(out, p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m, _ := filepath.Glob(p)
|
||||||
|
out = append(out, m...)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func anyExists(paths []string) bool {
|
func anyExists(paths []string) bool {
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
if sysx.Exists(p) {
|
if sysx.Exists(p) {
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.hsrv.site/mrleo1nid/backup-master/internal/archive"
|
||||||
|
"gitea.hsrv.site/mrleo1nid/backup-master/internal/catalog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExpandPaths(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
for _, n := range []string{"ipcheck.dat", "geoip.dat", "x.txt"} {
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, n), nil, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
got := expandPaths([]string{filepath.Join(dir, "*.dat"), "/etc/x-ui", filepath.Join(dir, "*.none")})
|
||||||
|
want := []string{filepath.Join(dir, "geoip.dat"), filepath.Join(dir, "ipcheck.dat"), "/etc/x-ui"}
|
||||||
|
if !slices.Equal(got, want) {
|
||||||
|
t.Fatalf("expandPaths = %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Свои geo-файлы попадают в бэкап, штатные из релиза 3x-ui — нет.
|
||||||
|
func TestXUIGeoExcludes(t *testing.T) {
|
||||||
|
cat, err := catalog.Default()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := cat.Find("xui")
|
||||||
|
skip := archive.NewMatcher(cat.Exclude, s.Exclude)
|
||||||
|
cases := map[string]bool{
|
||||||
|
"/usr/local/x-ui/bin/ipcheck.dat": false,
|
||||||
|
"/usr/local/x-ui/bin/discord-geosite.dat": false,
|
||||||
|
"/usr/local/x-ui/bin/geoip.dat": true,
|
||||||
|
"/usr/local/x-ui/bin/geosite_RU.dat": true,
|
||||||
|
"/usr/local/x-ui/bin/geoip_IR.dat": true,
|
||||||
|
"/usr/local/x-ui/bin/ipcheck.dat.bak": true,
|
||||||
|
"/etc/x-ui/x-ui.db": true,
|
||||||
|
}
|
||||||
|
for p, want := range cases {
|
||||||
|
if got := skip.Match(p); got != want {
|
||||||
|
t.Errorf("exclude(%s) = %v, want %v", p, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f2b := cat.Find("fail2ban")
|
||||||
|
if f2b == nil || !slices.Contains(f2b.Paths, "/etc/fail2ban/jail.d") || !slices.Contains(f2b.Paths, "/etc/fail2ban/filter.d/3x-ipl.conf") {
|
||||||
|
t.Fatalf("категория fail2ban: %+v", f2b)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,13 +23,44 @@ categories:
|
|||||||
- name: xui
|
- name: xui
|
||||||
type: xui
|
type: xui
|
||||||
detect: [/usr/local/x-ui/x-ui]
|
detect: [/usr/local/x-ui/x-ui]
|
||||||
paths: [/etc/x-ui, /root/cert]
|
paths:
|
||||||
exclude: ["/etc/x-ui/x-ui.db*"]
|
- /etc/x-ui
|
||||||
|
- /root/cert
|
||||||
|
# свои geo-файлы (ipcheck.dat, discord-*.dat и т.п.): на них ссылается роутинг,
|
||||||
|
# без них Xray не стартует; штатные geo*.dat приходят с релизом 3x-ui
|
||||||
|
- /usr/local/x-ui/bin/*.dat
|
||||||
|
exclude:
|
||||||
|
- "/etc/x-ui/x-ui.db*"
|
||||||
|
- "/usr/local/x-ui/bin/geoip.dat"
|
||||||
|
- "/usr/local/x-ui/bin/geosite.dat"
|
||||||
|
- "/usr/local/x-ui/bin/geo*_IR.dat"
|
||||||
|
- "/usr/local/x-ui/bin/geo*_RU.dat"
|
||||||
|
- "*.bak"
|
||||||
service: x-ui
|
service: x-ui
|
||||||
version_cmd: [/usr/local/x-ui/x-ui, -v]
|
version_cmd: [/usr/local/x-ui/x-ui, -v]
|
||||||
install: xui
|
install: xui
|
||||||
keep_binaries: [/usr/local/x-ui, /usr/bin/x-ui, /etc/systemd/system/x-ui.service]
|
keep_binaries: [/usr/local/x-ui, /usr/bin/x-ui, /etc/systemd/system/x-ui.service]
|
||||||
|
|
||||||
|
# fail2ban: свои jail'ы (jail.local, jail.d/*.local) и IP-лимит 3x-ui (3x-ipl).
|
||||||
|
# Штатные filter.d/action.d ставит пакет — берём только файлы 3x-ui и свои *.local.
|
||||||
|
- name: fail2ban
|
||||||
|
type: systemd
|
||||||
|
detect: [/etc/fail2ban]
|
||||||
|
paths:
|
||||||
|
- /etc/fail2ban/jail.local
|
||||||
|
- /etc/fail2ban/jail.d
|
||||||
|
- /etc/fail2ban/fail2ban.local
|
||||||
|
- /etc/fail2ban/fail2ban.conf # 3x-ui включает в нём allowipv6
|
||||||
|
- /etc/fail2ban/filter.d/3x-ipl.conf
|
||||||
|
- /etc/fail2ban/action.d/3x-ipl.conf
|
||||||
|
- /etc/fail2ban/filter.d/*.local
|
||||||
|
- /etc/fail2ban/action.d/*.local
|
||||||
|
service: fail2ban
|
||||||
|
restore_after: [system, xui]
|
||||||
|
post_restore:
|
||||||
|
# jail 3x-ipl читает лог x-ui: без файла fail2ban не поднимет jail
|
||||||
|
- "if ls /etc/fail2ban/jail.d/3x-ipl* >/dev/null 2>&1; then mkdir -p /var/log/x-ui && touch /var/log/x-ui/3xipl.log /var/log/x-ui/3xipl-banned.log; fi"
|
||||||
|
|
||||||
- name: nebula
|
- name: nebula
|
||||||
type: systemd
|
type: systemd
|
||||||
detect: [/etc/nebula]
|
detect: [/etc/nebula]
|
||||||
|
|||||||
@@ -189,7 +189,8 @@ ok "$(iptables --version)"
|
|||||||
|
|
||||||
# ---------- 5. Fail2ban ----------
|
# ---------- 5. Fail2ban ----------
|
||||||
step "5. Fail2ban"
|
step "5. Fail2ban"
|
||||||
apt_install fail2ban python3-systemd
|
# nftables: штатный banaction fail2ban в Ubuntu 24.04 — nftables, без nft jail sshd не банит
|
||||||
|
apt_install fail2ban python3-systemd nftables
|
||||||
IGNORE_IP="127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8"
|
IGNORE_IP="127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8"
|
||||||
if [[ -n $CLIENT_IP ]]; then
|
if [[ -n $CLIENT_IP ]]; then
|
||||||
IGNORE_IP+=" $CLIENT_IP"
|
IGNORE_IP+=" $CLIENT_IP"
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ func installXUI(ctx context.Context, version, arch string) error {
|
|||||||
v = "v" + v
|
v = "v" + v
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("https://github.com/MHSanaei/3x-ui/releases/download/%s/x-ui-linux-%s.tar.gz", v, arch)
|
url := fmt.Sprintf("https://github.com/MHSanaei/3x-ui/releases/download/%s/x-ui-linux-%s.tar.gz", v, arch)
|
||||||
_ = os.RemoveAll("/usr/local/x-ui")
|
// каталог не очищаем: в нём уже лежат восстановленные свои geo-файлы (ipcheck.dat и т.п.),
|
||||||
|
// архив релиза перезапишет только свои файлы
|
||||||
if err := fetchTarGz(ctx, url, func(name string) (string, bool) {
|
if err := fetchTarGz(ctx, url, func(name string) (string, bool) {
|
||||||
name = strings.TrimPrefix(name, "./")
|
name = strings.TrimPrefix(name, "./")
|
||||||
if !strings.HasPrefix(name, "x-ui/") && name != "x-ui" {
|
if !strings.HasPrefix(name, "x-ui/") && name != "x-ui" {
|
||||||
|
|||||||
Reference in new issue
Block a user