D47 / R-1269 (P2): a new install never writes into an earlier install's backup — every install has an identity (install_id), the unit records it, every unit writer asks first; another install's unit is set aside whole (backups/set-aside/<app>/<stamp>, a D25 kept copy, „Visszaállítás” + „Eltávolítás” on the restore page); a move that fails refuses the write. Red-proved. Commit only.
gates / gates (push) Successful in 1m33s
gates / gates (push) Successful in 1m33s
This commit is contained in:
1 parent
e1613aea8d
commit
02daebe9c5
30 files changed
+1990
-17
No files matched your search
@@ -3,6 +3,7 @@ package web
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
|
||||
@@ -18,6 +19,8 @@ type keptCopyRow struct {
|
||||
Line string // „a(z) <drive> meghajtóról megőrzött másolat · <why>"
|
||||
SizeHuman string
|
||||
Since string
|
||||
// Restorable (D47): a set-aside recovery unit of an earlier install — the row offers „Visszaállítás" too.
|
||||
Restorable bool
|
||||
}
|
||||
|
||||
func (s *Server) keptCopyRows(lang string) []keptCopyRow {
|
||||
@@ -28,11 +31,12 @@ func (s *Server) keptCopyRows(lang string) []keptCopyRow {
|
||||
for _, k := range s.backupMgr.KeptCopies() {
|
||||
why := s.msgLang(lang, "kept.reason."+k.Reason)
|
||||
out = append(out, keptCopyRow{
|
||||
ID: k.ID,
|
||||
Source: k.Source,
|
||||
Line: s.msgLang(lang, "kept.from_drive", k.Drive) + " · " + why,
|
||||
SizeHuman: backup.HumanizeBytes(k.SizeBytes),
|
||||
Since: k.Since,
|
||||
ID: k.ID,
|
||||
Source: k.Source,
|
||||
Line: s.msgLang(lang, "kept.from_drive", k.Drive) + " · " + why,
|
||||
SizeHuman: backup.HumanizeBytes(k.SizeBytes),
|
||||
Since: k.Since,
|
||||
Restorable: k.Kind == backup.KeptKindUnit,
|
||||
})
|
||||
}
|
||||
return out
|
||||
@@ -68,3 +72,49 @@ func (s *Server) keptCopyRemoveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
offboxRedirectTo(w, r, "/backups/restore", "flash.kept.remove_failed", true, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// keptUnitRestoreHandler is „Visszaállítás" on a set-aside unit (D47): POST /backup/kept/restore (id, confirm=1). The
|
||||
// unit is named only by its record (backup.SetAsideUnitFor: the recorded folder, inside its set-aside tree, same device
|
||||
// + inode), and restored through the one unit-restore body, in the background like every restore.
|
||||
func (s *Server) keptUnitRestoreHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if s.backupMgr == nil {
|
||||
offboxRedirectTo(w, r, "/backups/restore", "flash.offbox.mgr_unreachable", true)
|
||||
return
|
||||
}
|
||||
_ = r.ParseForm()
|
||||
id := strings.TrimSpace(r.FormValue("id"))
|
||||
if r.FormValue("confirm") != "1" {
|
||||
offboxRedirectTo(w, r, "/backups/restore", "flash.kept.restore_needs_confirmation", true)
|
||||
return
|
||||
}
|
||||
if msg, blocked := s.restoreOpBlocked(); blocked {
|
||||
http.Redirect(w, r, "/backups/restore?flash_error="+url.QueryEscape(msg), http.StatusFound)
|
||||
return
|
||||
}
|
||||
k, err := s.backupMgr.SetAsideUnitFor(id)
|
||||
switch {
|
||||
case errors.Is(err, backup.ErrKeptCopyNotFound):
|
||||
offboxRedirectTo(w, r, "/backups/restore", "flash.kept.not_found", true)
|
||||
return
|
||||
case err != nil:
|
||||
offboxRedirectTo(w, r, "/backups/restore", "flash.kept.changed", true)
|
||||
return
|
||||
}
|
||||
if legs := s.backupMgr.DeclaredDriveFileLegs(k.Source); len(legs) > 0 {
|
||||
msg := s.missingFileLegsRefusal(r.Context(), s.langFor(r), k.Source)
|
||||
http.Redirect(w, r, "/backups/restore?flash_error="+url.QueryEscape(msg), http.StatusFound)
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[WARN] [web] Restore of %s from a set-aside unit requested (async): %s from %s", k.Source, k.Path, r.RemoteAddr)
|
||||
s.backupMgr.BeginRestoreOp("restore", k.Source)
|
||||
go func() {
|
||||
app, res, err := s.backupMgr.RestoreSetAsideUnit(id)
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERROR] [web] Restore from the set-aside unit failed (async): stack=%s: %v", app, err)
|
||||
s.backupMgr.EndRestoreOp(false, s.restoreFailedNote("note.restore.unit_failed", err))
|
||||
return
|
||||
}
|
||||
s.backupMgr.EndRestoreOp(true, s.restoredVersionPrefix(app, res.VersionChanged, res.DataPins, res.DataAt)+s.unitRestoreOutcomeMsg(app, res))
|
||||
}()
|
||||
http.Redirect(w, r, "/backups/restore?"+flashQuery("flash", "flash.restore.started"), http.StatusFound)
|
||||
}
|
||||
Reference in new issue
Block a user