Оповещения: группа «Администратор системы», блок «Кому писать» открыт; копии: адрес хранилища как в проводнике
Администратору (адрес из «Почты») — сбои (ИШ, связь с НРД, службы bj, DEPO) и обновления (новый выпуск bj, ИШ, лицензия, сертификаты); отделам — переводы и поручения. Существующие установки: администратор получает всё, что получал. Новые оповещения: «Доступно обновление bj» (раз на выпуск), «Служба … не работает / снова работает» (модули и служба ключей Валидаты). Резервные копии: \\сервер\папка или сервер + общая папка по полям → //сервер/папка; понятная причина отказа доступа. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
@@ -46,6 +46,49 @@ func (b BackupSettings) keep() int {
|
||||
|
||||
func (b BackupSettings) dir() string { return pickStr(strings.TrimSpace(b.Dir), "bj-backup") }
|
||||
|
||||
// smbLocation — адрес хранилища, как его вводят люди (\\nas\backup\bj как в проводнике, //nas/backup, smb://nas/backup;
|
||||
// или сервер в поле «Хранилище», а общая папка — в поле «Папка»), → общая папка для smbclient (//сервер/папка) и
|
||||
// путь внутри неё. Раньше «\\10.10.10.11\» + «backup_test» превращалось в //10.10.10.11//backup_test — без общей
|
||||
// папки, и доступ не проверялся. Только сервер без общей папки — share «//сервер» (форма скажет, чего не хватает).
|
||||
func smbLocation(share, dir string) (string, string) {
|
||||
parts := func(s string) []string {
|
||||
s = strings.ReplaceAll(strings.TrimSpace(s), `\`, "/")
|
||||
if i := strings.Index(s, "://"); i > 0 && strings.EqualFold(s[:i], "smb") {
|
||||
s = s[i+3:]
|
||||
}
|
||||
var out []string
|
||||
for _, p := range strings.Split(s, "/") {
|
||||
if p = strings.TrimSpace(p); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
sp, dp := parts(share), parts(dir)
|
||||
if len(sp) == 0 {
|
||||
return "", strings.Join(dp, "/")
|
||||
}
|
||||
if len(sp) == 1 { // только сервер: общая папка — начало «Папки»
|
||||
if len(dp) == 0 {
|
||||
return "//" + sp[0], ""
|
||||
}
|
||||
sp, dp = []string{sp[0], dp[0]}, dp[1:]
|
||||
}
|
||||
sub := append(append([]string{}, sp[2:]...), dp...)
|
||||
return "//" + sp[0] + "/" + sp[1], strings.Join(sub, "/")
|
||||
}
|
||||
|
||||
// smbWin — адрес хранилища так, как его видят в проводнике Windows (//nas/backup → \\nas\backup).
|
||||
func smbWin(share string) string {
|
||||
if !strings.HasPrefix(share, "//") {
|
||||
return share
|
||||
}
|
||||
return strings.ReplaceAll(share, "/", `\`)
|
||||
}
|
||||
|
||||
// smbShareOK — в адресе есть и сервер, и общая папка.
|
||||
func smbShareOK(share string) bool { return strings.Count(strings.TrimPrefix(share, "//"), "/") == 1 }
|
||||
|
||||
// BackupStatus — итог последней копии (пишет root-скрипт).
|
||||
type BackupStatus struct {
|
||||
At string `json:"at"`
|
||||
@@ -85,6 +128,12 @@ func backupParams(r *http.Request) map[string]string {
|
||||
p[k] = v
|
||||
}
|
||||
}
|
||||
if p["share"] != "" { // адрес как в проводнике → общая папка и путь внутри неё
|
||||
p["share"], p["dir"] = smbLocation(p["share"], p["dir"])
|
||||
if p["dir"] == "" {
|
||||
delete(p, "dir")
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -110,14 +159,17 @@ func registerBackup(mux *http.ServeMux, rc *RuntimeConfig) {
|
||||
}
|
||||
b := rc.Snapshot().Backup
|
||||
b.Enabled = r.FormValue("enabled") != ""
|
||||
b.Share = strings.TrimSpace(r.FormValue("share"))
|
||||
b.Dir = strings.TrimSpace(r.FormValue("dir"))
|
||||
b.Share, b.Dir = smbLocation(r.FormValue("share"), r.FormValue("dir"))
|
||||
b.User = strings.TrimSpace(r.FormValue("user"))
|
||||
b.EveryHours, _ = strconv.Atoi(r.FormValue("every_hours"))
|
||||
b.KeepDays, _ = strconv.Atoi(r.FormValue("keep_days"))
|
||||
pass := r.FormValue("password")
|
||||
if b.Share == "" {
|
||||
setupFlash(w, r, "Резервное копирование: укажите хранилище, например //nas/backup")
|
||||
setupFlash(w, r, `Резервное копирование: укажите сетевую папку, как в проводнике, например \\nas\backup`)
|
||||
return
|
||||
}
|
||||
if !smbShareOK(b.Share) {
|
||||
setupFlash(w, r, `Резервное копирование: укажите не только сервер, но и общую папку на нём — например \\`+strings.TrimPrefix(b.Share, "//")+`\backup`)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
|
||||
|
||||
Reference in New Issue
Block a user