v0.253.0 — errors carry the key of the sentence they are (R-557 slice 2 release B)
gates / gates (push) Successful in 24s
gates / gates (push) Successful in 24s
179 Hungarian sentences were built deep inside a package with fmt.Errorf and printed by whoever caught them: too late to translate where they are shown, too early where they are made. Every one now carries its key across that gap. ZERO Hungarian error literals remain. util.MsgError does three things at once, each earned: - Error() is the Hungarian, byte for byte, so every un-converted printer is unchanged; - errors.Is answers for the kind AND for a wrapped cause (KindErrorf dropped the cause); - an error ARGUMENT renders recursively, so "formázás sikertelen: %w" translates whole. A foreign error — restic, docker, ssh, the stdlib — prints verbatim. It is not ours. 76 display sites go through errText, and TestNoErrErrorInPageOutput convicts any that do not. memoryVerdict returns an error rather than a sentence, so the deploy's 409 and the household's language come from one value; UpdateRefusal gained a Cause to carry it. Plurals, one rule, stated once: a key with .one/.other takes its COUNT first. Not a per-call-site flag — the producer somebody forgot would read "3 app is not running". The guard caught a real key collision (alert.deadapp.one) the day the rule landed. TWO DEFECTS FOUND IN MY OWN TOOLING, recorded rather than quietly fixed. The bulk converter silently dropped multi-line concatenations, damaging 7 producers — and the parity gate could not see it, because every surviving fragment WAS a real base literal while the CALL had lost text; two behaviour tests caught it. And the counting script was case-sensitive, so it said "0 left" while five remained. MinAgent: 0.131.0 (unchanged). No hub release needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -354,7 +354,7 @@ func (m *Manager) resetOrphanedRepo(ctx context.Context, base, env []string, rea
|
||||
// unless the repo is currently orphaned. It builds the base/env and runs the move-aside + re-init.
|
||||
func (m *Manager) ResetOrphanedRepo(ctx context.Context) error {
|
||||
if !m.OffboxOrphaned() {
|
||||
return fmt.Errorf("az offsite tároló nincs elárvult állapotban")
|
||||
return util.MsgError("err.backup.az_offsite_tarolo_nincs_elarvult_allapotban")
|
||||
}
|
||||
t := m.settings.GetOffboxTarget()
|
||||
base, env := m.offboxBaseArgs(t)
|
||||
@@ -932,7 +932,7 @@ func (m *Manager) runOffboxBackup(ctx context.Context, withProgress bool) error
|
||||
// run refuses.
|
||||
m.offboxPruneOnly(ctx, base, env)
|
||||
m.offboxRecordStats(ctx, base, env) // the prune may have brought the size back down — refresh
|
||||
runErr = util.KindErrorf(ErrOffsiteQuota, "A távoli mentés túllépte a tárhelykeretet (%d/%d GB) — törölj régi mentéseket vagy kérj nagyobb keretet.", usedGB, quota)
|
||||
runErr = util.MsgErrorf(ErrOffsiteQuota, "err.backup.offsite_over_quota", usedGB, quota)
|
||||
} else {
|
||||
// R-43/R-44 (v0.148.0) — THE COHERENCE PRE-PHASE. Refresh the DB/volume dumps and the recovery
|
||||
// units BEFORE capturing, so the snapshot restic is about to write is an internally coherent
|
||||
|
||||
@@ -3,6 +3,7 @@ package backup
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -213,7 +214,7 @@ func (m *Manager) writeSafetyDump(ctx context.Context, stackName, nsRoot string)
|
||||
}
|
||||
dbs, err := discover(ctx)
|
||||
if err != nil {
|
||||
return safetyDumpSet{}, fmt.Errorf("a biztonsági mentés előtt nem sikerült felderíteni az adatbázisokat: %w", err)
|
||||
return safetyDumpSet{}, util.MsgError("err.backup.a_biztonsagi_mentes_elott_nem_sikerult", err)
|
||||
}
|
||||
var mine []DiscoveredDB
|
||||
for _, db := range dbs {
|
||||
@@ -227,7 +228,7 @@ func (m *Manager) writeSafetyDump(ctx context.Context, stackName, nsRoot string)
|
||||
|
||||
dumpDir := AppDBDumpPath(nsRoot, stackName)
|
||||
if err := os.MkdirAll(dumpDir, 0755); err != nil {
|
||||
return safetyDumpSet{}, fmt.Errorf("a biztonsági mentés könyvtára nem hozható létre: %w", err)
|
||||
return safetyDumpSet{}, util.MsgError("err.backup.a_biztonsagi_mentes_konyvtara_nem_hozhato", err)
|
||||
}
|
||||
set := safetyDumpSet{Stamp: time.Now().UTC().Format("20060102T150405Z")}
|
||||
for _, db := range mine {
|
||||
@@ -248,7 +249,7 @@ func (m *Manager) writeSafetyDump(ctx context.Context, stackName, nsRoot string)
|
||||
safe := filepath.Join(dumpDir, fmt.Sprintf("%s%s-%s-%s.sql", preRestoreDumpPrefix, set.Stamp, stackName, db.DBType))
|
||||
res := m.dumpForSafety(ctx, db, safe)
|
||||
if res.Error != nil {
|
||||
return safetyDumpSet{}, fmt.Errorf("a jelenlegi adatbázis biztonsági mentése sikertelen (%s): %w — a visszaállítás nem indult el", db.ContainerName, res.Error)
|
||||
return safetyDumpSet{}, util.MsgError("err.backup.a_jelenlegi_adatbazis_biztonsagi_mentese_sikertelen", db.ContainerName, res.Error)
|
||||
}
|
||||
// EVERY file, not just the first — R-379, and the reason is on safetyDumpSet.
|
||||
set.Files = append(set.Files, safetyDumpFile{DB: db, Path: safe})
|
||||
@@ -440,7 +441,7 @@ func (m *Manager) rollbackSafetyDump(ctx context.Context, stack string, set safe
|
||||
}
|
||||
live, dErr := discover(ctx)
|
||||
if dErr != nil {
|
||||
return fmt.Errorf("a visszavonás előtt nem sikerült felderíteni az adatbázisokat: %w", dErr)
|
||||
return util.MsgError("err.backup.a_visszavonas_elott_nem_sikerult_felderiteni", dErr)
|
||||
}
|
||||
liveFor := func(want DiscoveredDB) (DiscoveredDB, bool) {
|
||||
for _, db := range live {
|
||||
@@ -453,13 +454,13 @@ func (m *Manager) rollbackSafetyDump(ctx context.Context, stack string, set safe
|
||||
|
||||
for _, f := range set.Files {
|
||||
if _, sErr := os.Stat(f.Path); sErr != nil {
|
||||
return fmt.Errorf("a visszavonáshoz szükséges mentés nem található (%s): %w", filepath.Base(f.Path), sErr)
|
||||
return util.MsgError("err.backup.a_visszavonashoz_szukseges_mentes_nem_talalhato", filepath.Base(f.Path), sErr)
|
||||
}
|
||||
target, ok := liveFor(f.DB)
|
||||
if !ok {
|
||||
// Fail closed: pouring an undo into a container we cannot identify is worse than saying
|
||||
// we could not do it.
|
||||
return fmt.Errorf("a(z) %s adatbázis-tárolója nem található a visszavonáshoz", f.DB.ContainerName)
|
||||
return util.MsgError("err.backup.a_z_adatbazis_taroloja_nem_talalhato", f.DB.ContainerName)
|
||||
}
|
||||
if target.ContainerID != f.DB.ContainerID {
|
||||
m.logger.Printf("[DEBUG] [offbox] %s: %s was re-created during the restore (%s → %s) — rolling back into the live container",
|
||||
@@ -467,7 +468,7 @@ func (m *Manager) rollbackSafetyDump(ctx context.Context, stack string, set safe
|
||||
}
|
||||
m.logger.Printf("[INFO] [offbox] %s: rolling back to the pre-restore state from %s", stack, filepath.Base(f.Path))
|
||||
if err := imp(ctx, target, f.Path); err != nil {
|
||||
return fmt.Errorf("a korábbi állapot visszaállítása sikertelen (%s): %w", target.ContainerName, err)
|
||||
return util.MsgError("err.backup.a_korabbi_allapot_visszaallitasa_sikertelen", target.ContainerName, err)
|
||||
}
|
||||
}
|
||||
m.logger.Printf("[INFO] [offbox] %s: rollback complete — %d database(s) returned to the pre-restore state", stack, len(set.Files))
|
||||
@@ -505,7 +506,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
return res, fmt.Errorf("stack provider not configured")
|
||||
}
|
||||
if err := m.acquireRunning(); err != nil {
|
||||
return res, fmt.Errorf("egy másik mentési/visszaállítási művelet már fut")
|
||||
return res, util.MsgError("err.backup.egy_masik_mentesi_visszaallitasi_muvelet_mar")
|
||||
}
|
||||
defer m.releaseRunning()
|
||||
|
||||
@@ -514,7 +515,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
return res, err
|
||||
}
|
||||
if _, sErr := os.Stat(scratch); sErr != nil {
|
||||
return res, fmt.Errorf("nincs előkészített teljes visszaállítás — futtass előbb egy teljes visszaállítást")
|
||||
return res, util.MsgError("err.backup.nincs_elokeszitett_teljes_visszaallitas_futtass_elobb")
|
||||
}
|
||||
id, paths, err := m.offboxLatestSnapshot(ctx, stack)
|
||||
if err != nil {
|
||||
@@ -541,12 +542,9 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
// above is untouched for the 13 apps that DO have a drive to get wrong.
|
||||
if !m.isStackDeployed(stack) {
|
||||
if rec := m.recordedPlacementFromScratch(scratch); rec.Known() {
|
||||
return res, fmt.Errorf("a(z) %s nincs telepítve, ezért nincs hová visszaállítani az adatait. "+
|
||||
"A mentése szerint az adatai itt voltak: %s. Telepítsd újra az alkalmazást (Alkalmazások) "+
|
||||
"ugyanerre a helyre, utána ez a visszaállítás működni fog", stack, rec.Drive)
|
||||
return res, util.MsgError("err.backup.not_installed_known_drive", stack, rec.Drive)
|
||||
}
|
||||
return res, fmt.Errorf("a(z) %s nincs telepítve, ezért nincs hová visszaállítani az adatait — "+
|
||||
"telepítsd újra az alkalmazást (Alkalmazások), utána ez a visszaállítás működni fog", stack)
|
||||
return res, util.MsgError("err.backup.not_installed", stack)
|
||||
}
|
||||
// The destination is resolved by the SAME rule the capture side used to write this snapshot
|
||||
// (CaptureRecoveryUnit → GetAppDrivePath): the app's drive if it has one, the system data path
|
||||
@@ -558,8 +556,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
// installed, but the box cannot name its own data root (systemDataPath unset). Saying
|
||||
// "nincs telepítve" here would send the customer to reinstall an app that is already
|
||||
// running, and the real fault would stay invisible.
|
||||
return res, fmt.Errorf("a(z) %s telepítve van, de a vezérlő nem tudja megállapítani, hová tartoznak az adatai "+
|
||||
"(nincs beállítva rendszer-adatterület). Ellenőrizd a tárhely beállításait (Tárhely), utána indítsd újra a visszaállítást", stack)
|
||||
return res, util.MsgError("err.backup.no_data_root", stack)
|
||||
}
|
||||
liveNs := m.namespaceRoot(hdd)
|
||||
|
||||
@@ -609,7 +606,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
// unit-only restore was run) refuses with ZERO copies.
|
||||
for _, pl := range placements {
|
||||
if _, sErr := os.Stat(pl.src); sErr != nil {
|
||||
return res, fmt.Errorf("a teljes visszaállítás hiányos (%s nincs meg) — futtass előbb egy teljes visszaállítást", filepath.Base(pl.src))
|
||||
return res, util.MsgError("err.backup.a_teljes_visszaallitas_hianyos_nincs_meg", filepath.Base(pl.src))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +619,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
}
|
||||
}
|
||||
if scratchUnit == "" {
|
||||
return res, fmt.Errorf("a pillanatképben nincs mentési egység — a visszaállítás nem indítható")
|
||||
return res, util.MsgError("err.backup.a_pillanatkepben_nincs_mentesi_egyseg_a")
|
||||
}
|
||||
scratchDumpDir := filepath.Join(scratchUnit, "db-dumps")
|
||||
man := readManifest(filepath.Join(scratchUnit, "manifest.json"))
|
||||
@@ -684,13 +681,13 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
if hasDB {
|
||||
if _, sErr := os.Stat(safety); sErr != nil {
|
||||
// Fail-closed: never replay when the undo is not verifiably on disk.
|
||||
return res, fmt.Errorf("a biztonsági mentés nem található a lemezen — a visszaállítás biztonsági okból nem indult el")
|
||||
return res, util.MsgError("err.backup.a_biztonsagi_mentes_nem_talalhato_a")
|
||||
}
|
||||
// Fail-closed (R-47): the app HAS a database but no compose service can be identified to
|
||||
// start alone for the replay. The only alternative would be to start everything and replay
|
||||
// into the race that produced H4 — refusing with the live app untouched is the better outcome.
|
||||
if len(dbServices) == 0 {
|
||||
return res, fmt.Errorf("Az adatbázis-szolgáltatás nem azonosítható a(z) %s alkalmazásban — a visszaállítás biztonsági okból nem indult el.", stack)
|
||||
return res, util.MsgError("err.backup.az_adatbazis_szolgaltatas_nem_azonosithato_a", stack)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,7 +696,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
// it used to leave the app down with nothing on disk recording that it was owed a restart — and a
|
||||
// full offsite restore is a LONG window, so this is the shape most likely to be interrupted.
|
||||
if err := m.appStop.Begin("offbox-reconstitute:"+stack, ReasonOffboxReconstitute, []string{stack}); err != nil {
|
||||
return res, fmt.Errorf("a(z) %s leállítása előtti jelölő nem menthető: %w", stack, err)
|
||||
return res, util.MsgError("err.backup.a_z_leallitasa_elotti_jelolo_nem", stack, err)
|
||||
}
|
||||
// restartStack starts the app and clears the marker ONLY when the start actually succeeded — a
|
||||
// failed start leaves the marker so the next startup retries. Every bring-up below goes through
|
||||
@@ -744,7 +741,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
if sErr := restartStack(); sErr != nil {
|
||||
m.logger.Printf("[WARN] [offbox] %s: restart after failed placement also failed: %v", stack, sErr)
|
||||
}
|
||||
return res, fmt.Errorf("a(z) %s fájljainak visszaállítása sikertelen: %w", stack, cErr)
|
||||
return res, util.MsgError("err.backup.a_z_fajljainak_visszaallitasa_sikertelen", stack, cErr)
|
||||
}
|
||||
res.FilesPlaced += n
|
||||
}
|
||||
@@ -770,7 +767,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
if sErr := restartStack(); sErr != nil {
|
||||
m.logger.Printf("[WARN] [offbox] %s: restart after failed volume replay also failed: %v", stack, sErr)
|
||||
}
|
||||
return res, fmt.Errorf("a(z) %s adatkötetének visszaállítása sikertelen: %w", stack, vErr)
|
||||
return res, util.MsgError("err.backup.a_z_adatkotetenek_visszaallitasa_sikertelen", stack, vErr)
|
||||
}
|
||||
|
||||
// --- DATABASE -------------------------------------------------------------------------------
|
||||
@@ -785,7 +782,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
if sErr := restartStack(); sErr != nil {
|
||||
m.logger.Printf("[WARN] [offbox] %s: full start after failed DB-only start also failed: %v", stack, sErr)
|
||||
}
|
||||
return res, fmt.Errorf("a(z) %s adatbázis-szolgáltatásának indítása sikertelen: %w", stack, err)
|
||||
return res, util.MsgError("err.backup.a_z_adatbazis_szolgaltatasanak_inditasa_sikertelen", stack, err)
|
||||
}
|
||||
n, iErr := m.reimportDBDumpsFrom(ctx, stack, scratchDumpDir)
|
||||
res.DBsReplayed = n
|
||||
@@ -813,9 +810,7 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
// R-383: the undo copy is DESCRIBED FROM DISK, never from the path alone. See
|
||||
// undoCopyPhrase — this sentence used to assert the file existed in exactly the
|
||||
// branch where a missing file is one of the two causes.
|
||||
return res, fmt.Errorf("a(z) %s adatbázisának visszaállítása sikertelen, és a korábbi állapot visszatöltése sem sikerült. "+
|
||||
"Az alkalmazást biztonsági okból LEÁLLÍTVA hagytuk, hogy az adatai ne sérüljenek tovább. "+
|
||||
"Vedd fel velünk a kapcsolatot — %s", stack, undoCopyPhrase(safetySet))
|
||||
return res, util.MsgError("err.backup.db_restore_and_rollback_failed", stack, undoCopyPhrase(safetySet))
|
||||
}
|
||||
res.RolledBack = true
|
||||
if sErr := restartStack(); sErr != nil {
|
||||
@@ -824,12 +819,11 @@ func (m *Manager) ReconstituteFromOffsite(ctx context.Context, stack string, ack
|
||||
// Says BOTH things. A message that reported only the failure would leave the customer
|
||||
// believing their data was gone when it is back — the omission of a GAIN is as
|
||||
// misleading as the omission of a loss.
|
||||
return res, fmt.Errorf("a(z) %s adatbázisának visszaállítása sikertelen — az adataid visszakerültek a visszaállítás előtti állapotba, "+
|
||||
"az alkalmazás fut tovább. Ha újra megpróbálnád, előbb vedd fel velünk a kapcsolatot", stack)
|
||||
return res, util.MsgError("err.backup.db_restore_failed_rolled_back", stack)
|
||||
}
|
||||
}
|
||||
if err := restartStack(); err != nil {
|
||||
return res, fmt.Errorf("a(z) %s újraindítása sikertelen a fájlok visszaállítása után: %w", stack, err)
|
||||
return res, util.MsgError("err.backup.a_z_ujrainditasa_sikertelen_a_fajlok", stack, err)
|
||||
}
|
||||
// R-475: an update hold may now name the OFF-SITE copy, and this is the route back it names. The
|
||||
// unit restore clears it in RestoreFromRecoveryUnitAt; this path never went through that function,
|
||||
|
||||
@@ -137,6 +137,7 @@ func RunRecoveryCheck(d RecoveryCheckDeps) int {
|
||||
// refused — a password is present and DIFFERS. Installing would clobber the key this box's CURRENT
|
||||
// repository is encrypted under, so it is refused. No force option is offered here: that
|
||||
// decision needs a human who knows which history they intend to keep.
|
||||
//
|
||||
// RecoverInstallOutcome names the terminal states of a recovery+install. Distinct values because
|
||||
// "it did nothing", "it refused" and "it installed" are different facts and a caller — CLI or web —
|
||||
// must be able to say which happened without parsing prose.
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -134,7 +135,7 @@ func (m *Manager) offboxLatestSnapshot(ctx context.Context, stack string) (id st
|
||||
Paths []string `json:"paths"`
|
||||
}
|
||||
if json.Unmarshal(out, &snaps) != nil || len(snaps) == 0 {
|
||||
return "", nil, fmt.Errorf("offbox: nincs pillanatkép a(z) %s alkalmazáshoz", stack)
|
||||
return "", nil, util.MsgError("err.backup.offbox_nincs_pillanatkep_a_z_alkalmazashoz", stack)
|
||||
}
|
||||
best := 0
|
||||
for i := 1; i < len(snaps); i++ {
|
||||
@@ -165,7 +166,7 @@ func (m *Manager) offboxSnapshotSize(ctx context.Context, id string) (int64, err
|
||||
TotalSize int64 `json:"total_size"`
|
||||
}
|
||||
if json.Unmarshal(out, &st) != nil || st.TotalSize <= 0 {
|
||||
return 0, fmt.Errorf("offbox: a(z) %s pillanatkép mérete ismeretlen", id)
|
||||
return 0, util.MsgError("err.backup.offbox_a_z_pillanatkep_merete_ismeretlen", id)
|
||||
}
|
||||
return st.TotalSize, nil
|
||||
}
|
||||
@@ -280,9 +281,7 @@ func (m *Manager) offboxScratchDirIn(stack string, rootFor func(string) string,
|
||||
// drives are physically fine and still mounted, it is their REGISTRATION that the destroyed guest
|
||||
// took with it — and until v0.207.0 it said only that a drive was missing, which reads like data
|
||||
// loss and offers nothing to do.
|
||||
return "", "", fmt.Errorf("nincs regisztrált adatmeghajtó, ezért nincs hová visszaállítani — " +
|
||||
"a meghajtók megvannak, csak újra kell csatolni őket a Tárhely → Meghajtók oldalon, utána " +
|
||||
"ez a visszaállítás működni fog")
|
||||
return "", "", util.MsgError("err.backup.no_registered_drive")
|
||||
}
|
||||
|
||||
// HasRestoreDestination reports whether an offsite restore has anywhere on this box to write.
|
||||
@@ -347,7 +346,7 @@ func (m *Manager) RestoreOffboxScratch(ctx context.Context, stack string, full b
|
||||
}
|
||||
unitPath := offboxUnitPathOf(paths, stack)
|
||||
if unitPath == "" {
|
||||
return fmt.Errorf("a(z) %s pillanatképében nincs mentési egység — a visszaállítás nem indítható", stack)
|
||||
return util.MsgError("err.backup.a_z_pillanatkepeben_nincs_mentesi_egyseg", stack)
|
||||
}
|
||||
scratch, nsRoot, err := m.offboxRestoreScratchDir(stack)
|
||||
if err != nil {
|
||||
@@ -611,7 +610,7 @@ func mapOffsiteRestorePaths(snapPaths []string, stack, scratch, liveNsRoot strin
|
||||
}
|
||||
}
|
||||
if oldNs == "" {
|
||||
return nil, fmt.Errorf("a pillanatképben nincs mentési egység (backups/primary/%s)", stack)
|
||||
return nil, util.MsgError("err.backup.a_pillanatkepben_nincs_mentesi_egyseg_backups", stack)
|
||||
}
|
||||
out := make([]placement, 0, len(snapPaths))
|
||||
for _, p := range snapPaths {
|
||||
@@ -619,17 +618,17 @@ func mapOffsiteRestorePaths(snapPaths []string, stack, scratch, liveNsRoot strin
|
||||
// catches p == oldNs (the namespace root itself — F-3a-3), which would otherwise map to a junk
|
||||
// placement nesting the whole old namespace under the live root.
|
||||
if !strings.HasPrefix(p, oldNs+"/") {
|
||||
return nil, fmt.Errorf("a pillanatkép egy útvonala a névtéren kívülre mutat: %s", p)
|
||||
return nil, util.MsgError("err.backup.a_pillanatkep_egy_utvonala_a_nevteren", p)
|
||||
}
|
||||
rel := strings.TrimPrefix(p, oldNs+"/")
|
||||
for _, seg := range strings.Split(rel, "/") {
|
||||
if seg == ".." {
|
||||
return nil, fmt.Errorf("a pillanatkép egy útvonala érvénytelen (..): %s", p)
|
||||
return nil, util.MsgError("err.backup.a_pillanatkep_egy_utvonala_ervenytelen", p)
|
||||
}
|
||||
}
|
||||
isUnit := rel == "backups/primary/"+stack
|
||||
if !isUnit && (rel == "backups" || strings.HasPrefix(rel, "backups/")) {
|
||||
return nil, fmt.Errorf("nem-egység útvonal a fenntartott backups zónában: %s", p)
|
||||
return nil, util.MsgError("err.backup.nem_egyseg_utvonal_a_fenntartott_backups", p)
|
||||
}
|
||||
out = append(out, placement{
|
||||
src: filepath.Join(scratch, p), // SP-3.1: abs source reconstructed under the target
|
||||
@@ -662,7 +661,7 @@ func (m *Manager) PlaceOffsiteRestore(ctx context.Context, stack string) error {
|
||||
return fmt.Errorf("invalid stack name")
|
||||
}
|
||||
if err := m.acquireRunning(); err != nil {
|
||||
return fmt.Errorf("egy másik mentési/visszaállítási művelet már fut")
|
||||
return util.MsgError("err.backup.egy_masik_mentesi_visszaallitasi_muvelet_mar")
|
||||
}
|
||||
defer m.releaseRunning()
|
||||
|
||||
@@ -671,7 +670,7 @@ func (m *Manager) PlaceOffsiteRestore(ctx context.Context, stack string) error {
|
||||
return err
|
||||
}
|
||||
if _, sErr := os.Stat(scratch); sErr != nil {
|
||||
return fmt.Errorf("nincs előkészített teljes visszaállítás — futtass előbb egy teljes visszaállítást")
|
||||
return util.MsgError("err.backup.nincs_elokeszitett_teljes_visszaallitas_futtass_elobb")
|
||||
}
|
||||
id, paths, err := m.offboxLatestSnapshot(ctx, stack)
|
||||
if err != nil {
|
||||
@@ -684,14 +683,13 @@ func (m *Manager) PlaceOffsiteRestore(ctx context.Context, stack string) error {
|
||||
// one, system data path otherwise). For the 13 needs_hdd apps nothing changes; for the 40 that
|
||||
// were never offered a drive the old test was permanently true and this merge was unreachable.
|
||||
if !m.isStackDeployed(stack) {
|
||||
return fmt.Errorf("a(z) %s nincs telepítve — előbb állítsd helyre az alkalmazást, utána az adatokat", stack)
|
||||
return util.MsgError("err.backup.a_z_nincs_telepitve_elobb_allitsd", stack)
|
||||
}
|
||||
hdd := strings.TrimSpace(m.GetAppDrivePath(stack))
|
||||
if hdd == "" {
|
||||
// Installed, but the box cannot name its own data root. Distinct reason ⇒ distinct sentence:
|
||||
// telling the customer to reinstall a running app would hide the real fault.
|
||||
return fmt.Errorf("a(z) %s telepítve van, de a vezérlő nem tudja megállapítani, hová tartoznak az adatai "+
|
||||
"(nincs beállítva rendszer-adatterület). Ellenőrizd a tárhely beállításait (Tárhely), utána indítsd újra a visszaállítást", stack)
|
||||
return util.MsgError("err.backup.no_data_root", stack)
|
||||
}
|
||||
liveNs := m.namespaceRoot(hdd)
|
||||
// F-3a-1b: headroom gate — a missing-only merge copies at most the scratch size; refuse before any
|
||||
@@ -707,7 +705,7 @@ func (m *Manager) PlaceOffsiteRestore(ctx context.Context, stack string) error {
|
||||
// unit-only restore, userdata srcs absent) refuses with ZERO copies, making "no partial writes" true.
|
||||
for _, pl := range placements {
|
||||
if _, sErr := os.Stat(pl.src); sErr != nil {
|
||||
return fmt.Errorf("a teljes visszaállítás hiányos (%s nincs meg) — futtass előbb egy teljes visszaállítást", filepath.Base(pl.src))
|
||||
return util.MsgError("err.backup.a_teljes_visszaallitas_hianyos_nincs_meg", filepath.Base(pl.src))
|
||||
}
|
||||
}
|
||||
copier := m.placeCopier()
|
||||
@@ -721,7 +719,7 @@ func (m *Manager) PlaceOffsiteRestore(ctx context.Context, stack string) error {
|
||||
}
|
||||
n, cErr := copier(pl.src, pl.dst)
|
||||
if cErr != nil {
|
||||
return fmt.Errorf("a(z) %s helyreállítása sikertelen: %w", stack, cErr) // scratch KEPT for retry
|
||||
return util.MsgError("err.backup.a_z_helyreallitasa_sikertelen", stack, cErr) // scratch KEPT for retry
|
||||
}
|
||||
placed += n
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -114,7 +114,7 @@ func (m *Manager) ListOffsiteRestoreCopies() []OffsiteRestoreCopy {
|
||||
// walk the delete out of the sandbox.
|
||||
func (m *Manager) DeleteOffsiteRestoreCopy(stack string) error {
|
||||
if !isSafeStackName(stack) {
|
||||
return fmt.Errorf("érvénytelen alkalmazásnév")
|
||||
return util.MsgError("err.backup.ervenytelen_alkalmazasnev")
|
||||
}
|
||||
for _, drive := range m.offsiteRestoreDriveRoots() {
|
||||
root := m.offsiteRestoreRootFor(drive)
|
||||
@@ -131,15 +131,15 @@ func (m *Manager) DeleteOffsiteRestoreCopy(stack string) error {
|
||||
cleanRoot := filepath.Clean(root) + string(filepath.Separator)
|
||||
if !strings.HasPrefix(cleanTarget+string(filepath.Separator), cleanRoot) {
|
||||
m.logger.Printf("[WARN] [offbox] refusing to delete verification copy outside %s: %s", root, cleanTarget)
|
||||
return fmt.Errorf("a törlés útvonala kívül esik az ellenőrző mappán")
|
||||
return util.MsgError("err.backup.a_torles_utvonala_kivul_esik_az")
|
||||
}
|
||||
if err := os.RemoveAll(cleanTarget); err != nil {
|
||||
return fmt.Errorf("a másolat törlése nem sikerült: %w", err)
|
||||
return util.MsgError("err.backup.a_masolat_torlese_nem_sikerult", err)
|
||||
}
|
||||
m.logger.Printf("[INFO] [offbox] deleted verification copy: %s", cleanTarget)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("nincs ilyen ellenőrző másolat")
|
||||
return util.MsgError("err.backup.nincs_ilyen_ellenorzo_masolat")
|
||||
}
|
||||
|
||||
// OffsiteRestoreScratchPath exposes WHERE a verification restore for stack would land, so the UI can
|
||||
|
||||
@@ -35,8 +35,8 @@ func r403Unit(t *testing.T, dbDumps, volDumps []string, extra map[string]string)
|
||||
// demo-hp: `"db_dumps": []`, `"volume_dumps": null`.
|
||||
func TestR403_ManifestWithNoDumpsIsHollow(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
dbs, vols []string
|
||||
name string
|
||||
dbs, vols []string
|
||||
}{
|
||||
{"both empty slices", []string{}, []string{}},
|
||||
{"both nil (the measured shape: db_dumps [] and volume_dumps null)", nil, nil},
|
||||
|
||||
@@ -2,6 +2,7 @@ package backup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -390,7 +391,7 @@ func (m *Manager) RestoreFromRecoveryUnitAtWith(stackName, unitDir string, opt U
|
||||
hasDumps := hasReplayableDump(dbDumpDir)
|
||||
if hasDumps && len(dbServices) == 0 {
|
||||
m.logger.Printf("[ERROR] [backup] Restore REFUSED for %s: a .sql dump exists but no database service is identifiable in the unit's compose", stackName)
|
||||
return res, fmt.Errorf("Az adatbázis-szolgáltatás nem azonosítható a(z) %s alkalmazásban — a visszaállítás biztonsági okból nem indult el.", stackName)
|
||||
return res, util.MsgError("err.backup.az_adatbazis_szolgaltatas_nem_azonosithato_a", stackName)
|
||||
}
|
||||
|
||||
// Stop, restore named-volume data, recreate the definition, replay the DB with ONLY the database
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -59,7 +60,7 @@ func defaultSharesPassdbRestore(tar []byte) error {
|
||||
// exists (the F-6C-1 ownership-fidelity lesson).
|
||||
func (m *Manager) sharesRestoreScratchDir() (scratch, nsRoot string, err error) {
|
||||
if m.settings == nil {
|
||||
return "", "", fmt.Errorf("nincs elérhető adatmeghajtó a visszaállításhoz")
|
||||
return "", "", util.MsgError("err.backup.nincs_elerheto_adatmeghajto_a_visszaallitashoz")
|
||||
}
|
||||
pick := func(networkOK bool) (string, string, bool) {
|
||||
for _, sp := range m.settings.GetSchedulableStoragePaths() {
|
||||
@@ -78,7 +79,7 @@ func (m *Manager) sharesRestoreScratchDir() (scratch, nsRoot string, err error)
|
||||
m.logger.Printf("[WARN] [shares] restore scratch on network storage — ownership fidelity not guaranteed under squash")
|
||||
return s, nr, nil
|
||||
}
|
||||
return "", "", fmt.Errorf("nincs elérhető adatmeghajtó a visszaállításhoz")
|
||||
return "", "", util.MsgError("err.backup.nincs_elerheto_adatmeghajto_a_visszaallitashoz")
|
||||
}
|
||||
|
||||
// RestoreSharesScratch restores the latest `_shares` snapshot into the scratch dir. Non-destructive:
|
||||
@@ -97,19 +98,19 @@ func (m *Manager) RestoreSharesScratch(ctx context.Context) error {
|
||||
}
|
||||
defer m.releaseRunning()
|
||||
if !m.OffboxConfigured() {
|
||||
return fmt.Errorf("a távoli mentés nincs beállítva")
|
||||
return util.MsgError("err.backup.a_tavoli_mentes_nincs_beallitva")
|
||||
}
|
||||
scratch, nsRoot, err := m.sharesRestoreScratchDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if free := m.offboxFree()(nsRoot); free > 0 && free < offboxUnitOnlyFreeFloor {
|
||||
return fmt.Errorf("Nincs elég szabad hely a visszaállításhoz (%s szükséges, %s szabad).",
|
||||
return util.MsgError("err.backup.nincs_eleg_szabad_hely_a_visszaallitashoz",
|
||||
humanizeBytes(offboxUnitOnlyFreeFloor), humanizeBytes(free))
|
||||
}
|
||||
id, _, err := m.offboxLatestSnapshot(ctx, SharesPseudoStack)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nincs visszaállítható megosztás-mentés: %w", err)
|
||||
return util.MsgError("err.backup.nincs_visszaallithato_megosztas_mentes", err)
|
||||
}
|
||||
if err := os.MkdirAll(scratch, 0o755); err != nil {
|
||||
return fmt.Errorf("restore dir: %w", err)
|
||||
@@ -121,7 +122,7 @@ func (m *Manager) RestoreSharesScratch(ctx context.Context) error {
|
||||
m.unlockStale(rctx, base, env)
|
||||
out, rerr := m.resticStep(rctx, env, base, "restore:"+SharesPseudoStack, "restore", id, "--target", scratch)
|
||||
if rerr != nil {
|
||||
return fmt.Errorf("a megosztások visszaállítása sikertelen: %w: %s", rerr, truncate(out))
|
||||
return util.MsgError("err.backup.a_megosztasok_visszaallitasa_sikertelen", rerr, truncate(out))
|
||||
}
|
||||
m.logger.Printf("[INFO] [shares] restored snapshot %s → %s", id, scratch)
|
||||
return nil
|
||||
@@ -197,10 +198,10 @@ func (m *Manager) readSharesManifestFrom(scratch string) (SharesManifest, error)
|
||||
p := filepath.Join(scratchJoin(scratch, m.sharesPayloadDir()), sharesManifestName)
|
||||
blob, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return mf, fmt.Errorf("a mentésben nincs megosztás-leíró: %w", err)
|
||||
return mf, util.MsgError("err.backup.a_mentesben_nincs_megosztas_leiro", err)
|
||||
}
|
||||
if err := json.Unmarshal(blob, &mf); err != nil {
|
||||
return mf, fmt.Errorf("a megosztás-leíró olvashatatlan: %w", err)
|
||||
return mf, util.MsgError("err.backup.a_megosztas_leiro_olvashatatlan", err)
|
||||
}
|
||||
return mf, nil
|
||||
}
|
||||
@@ -212,7 +213,7 @@ func (m *Manager) readSharesManifestFrom(scratch string) (SharesManifest, error)
|
||||
func (m *Manager) PlaceSharesRestore(ctx context.Context) (SharesRestoreResult, error) {
|
||||
var res SharesRestoreResult
|
||||
if err := m.acquireRunning(); err != nil {
|
||||
return res, fmt.Errorf("egy másik mentési/visszaállítási művelet már fut")
|
||||
return res, util.MsgError("err.backup.egy_masik_mentesi_visszaallitasi_muvelet_mar")
|
||||
}
|
||||
defer m.releaseRunning()
|
||||
|
||||
@@ -221,7 +222,7 @@ func (m *Manager) PlaceSharesRestore(ctx context.Context) (SharesRestoreResult,
|
||||
return res, err
|
||||
}
|
||||
if _, sErr := os.Stat(scratch); sErr != nil {
|
||||
return res, fmt.Errorf("nincs előkészített visszaállítás — futtass előbb egy megosztás-visszaállítást")
|
||||
return res, util.MsgError("err.backup.nincs_elokeszitett_visszaallitas_futtass_elobb_egy")
|
||||
}
|
||||
mf, err := m.readSharesManifestFrom(scratch)
|
||||
if err != nil {
|
||||
@@ -248,7 +249,7 @@ func (m *Manager) PlaceSharesRestore(ctx context.Context) (SharesRestoreResult,
|
||||
if _, sErr := os.Stat(src); sErr == nil {
|
||||
n, cErr := copier(src, sh.Path)
|
||||
if cErr != nil {
|
||||
return res, fmt.Errorf("a(z) „%s” megosztás fájljainak visszaállítása sikertelen: %w", sh.Name, cErr)
|
||||
return res, util.MsgError("err.backup.a_z_megosztas_fajljainak_visszaallitasa_sikertelen", sh.Name, cErr)
|
||||
}
|
||||
res.FilesRestored += n
|
||||
res.SharesPlaced = append(res.SharesPlaced, sh.Name)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -36,7 +37,7 @@ var (
|
||||
// errTier2NetworkOnly (F-6C-1): the only off-disk candidate is a NAS network share, which can never
|
||||
// be a tier-2 target — rsync -og chowns under a root_squash export and a wrong-owner restore is
|
||||
// silently broken. The message IS the customer-facing reason.
|
||||
errTier2NetworkOnly = errors.New("Hálózati tároló nem lehet a 2. mentés célja — a fájltulajdonos-adatok megőrzése ott nem garantálható.")
|
||||
errTier2NetworkOnly = util.MsgError("err.backup.halozati_tarolo_nem_lehet_a_2")
|
||||
)
|
||||
|
||||
// appDataDirNames resolves the app's real appdata dir name(s) under hddPath from its compose HDD
|
||||
|
||||
@@ -2,8 +2,8 @@ package backup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -25,26 +25,26 @@ import (
|
||||
|
||||
// Refusal reasons (customer-readable — they surface verbatim in the flash message).
|
||||
var (
|
||||
errNoTier2Copy = errors.New("nincs másodlagos fájlmásolat ehhez az alkalmazáshoz")
|
||||
errTier2DriveGone = errors.New("a másodlagos meghajtó nincs csatlakoztatva")
|
||||
errLiveDriveGone = errors.New("az alkalmazás meghajtója nincs csatlakoztatva")
|
||||
errLiveDriveDecommed = errors.New("az alkalmazás meghajtója le van szerelve")
|
||||
errNoTier2Copy = util.MsgError("err.backup.nincs_masodlagos_fajlmasolat_ehhez_az_alkalmazashoz")
|
||||
errTier2DriveGone = util.MsgError("err.backup.a_masodlagos_meghajto_nincs_csatlakoztatva")
|
||||
errLiveDriveGone = util.MsgError("err.backup.az_alkalmazas_meghajtoja_nincs_csatlakoztatva")
|
||||
errLiveDriveDecommed = util.MsgError("err.backup.az_alkalmazas_meghajtoja_le_van_szerelve")
|
||||
// errTier2OldLayout (3b, §7-G2): the recorded copy predates the v2 relpath-mirroring layout (no
|
||||
// marker). Refuse rather than read a flat layout we no longer understand — safe, because tier-2
|
||||
// restore is missing-file recovery and the live data still exists in that scenario.
|
||||
errTier2OldLayout = errors.New("A 2. mentés régi formátumú — futtass előbb egy új másodlagos mentést.")
|
||||
errTier2OldLayout = util.MsgError("err.backup.a_2_mentes_regi_formatumu_futtass")
|
||||
// ErrTier2NoRestorableData (C9-F1) — this app HAS a Tier-2 copy, but that copy contains no subtree
|
||||
// this restore can read: its data lives entirely in Docker named volumes, which are captured into
|
||||
// recovery-unit/ (db-dumps + volume-dumps) and NEVER read by this path. 43 of the 53 catalog apps
|
||||
// are in this class. Exported so the handler can refuse BEFORE stopping the app and name the action
|
||||
// that does work, instead of taking an outage and reporting "no missing files".
|
||||
ErrTier2NoRestorableData = errors.New("ennek az alkalmazásnak az adatai nem ebből a másolatból állíthatók vissza")
|
||||
ErrTier2NoRestorableData = util.MsgError("err.backup.ennek_az_alkalmazasnak_az_adatai_nem")
|
||||
// ErrTier2NoUnitInCopy (R-102) — the recorded Tier-2 copy holds no OPENABLE recovery unit: either
|
||||
// recovery-unit/ is absent, or it is a directory without a readable manifest.json. Exported so the
|
||||
// handler can refuse before beginning any op. FAIL CLOSED is the whole point of the second half:
|
||||
// a directory that exists is not a package, and reading a half-copied mirror as if it were one is
|
||||
// how a restore would overwrite live data with nothing.
|
||||
ErrTier2NoUnitInCopy = errors.New("a másodlagos másolatban nincs megnyitható mentési egység ehhez az alkalmazáshoz")
|
||||
ErrTier2NoUnitInCopy = util.MsgError("err.backup.a_masodlagos_masolatban_nincs_megnyithato_mentesi")
|
||||
)
|
||||
|
||||
// Tier2Coverage says what a Tier-2 restore can and cannot return for one app — the asymmetry C9-F1
|
||||
@@ -441,10 +441,10 @@ func (m *Manager) RestoreTier2Files(stackName string) (filesRestored int, err er
|
||||
}
|
||||
|
||||
if copyErr != nil {
|
||||
return filesRestored, fmt.Errorf("fájlmásolás sikertelen: %w", copyErr)
|
||||
return filesRestored, util.MsgError("err.backup.fajlmasolas_sikertelen", copyErr)
|
||||
}
|
||||
if startErr != nil {
|
||||
return filesRestored, fmt.Errorf("%d fájl visszaállítva, de az alkalmazás újraindítása sikertelen: %w", filesRestored, startErr)
|
||||
return filesRestored, util.MsgError("err.backup.fajl_visszaallitva_de_az_alkalmazas_ujrainditasa", filesRestored, startErr)
|
||||
}
|
||||
// Privacy: count + duration only — customer file names never at INFO.
|
||||
m.logger.Printf("[INFO] [backup] Tier-2 file restore completed for %s: %d file(s) restored (%s)",
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/util"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -312,7 +313,7 @@ func (m *Manager) UpdateBusy(stackName string) (bool, string) {
|
||||
|
||||
// ErrUpdateBackupNoUnit is returned when a "back up first" run completed but the app still has no
|
||||
// openable Tier-2 unit — typically Tier 2 is switched off for the app or has no second target.
|
||||
var ErrUpdateBackupNoUnit = errors.New("a frissítés előtti mentés lefutott, de nem jött létre visszaállítható másolat")
|
||||
var ErrUpdateBackupNoUnit = util.MsgError("err.backup.a_frissites_elotti_mentes_lefutott_de")
|
||||
|
||||
// RunAppBackupNow runs THIS app's backup legs now, in the nightly order, and then its Tier-2 copy:
|
||||
// database dump(s) → volume dump (if the app has named volumes) → recovery-unit capture → Tier-2
|
||||
@@ -327,7 +328,7 @@ func (m *Manager) RunAppBackupNow(ctx context.Context, stackName string) error {
|
||||
return fmt.Errorf("stack provider not configured")
|
||||
}
|
||||
if m.migrationActive() {
|
||||
return fmt.Errorf("adatáthelyezés folyamatban — a mentés most nem indítható")
|
||||
return util.MsgError("err.backup.adatathelyezes_folyamatban_a_mentes_most_nem")
|
||||
}
|
||||
if err := m.acquireRunning(); err != nil {
|
||||
return err
|
||||
@@ -341,13 +342,13 @@ func (m *Manager) RunAppBackupNow(ctx context.Context, stackName string) error {
|
||||
|
||||
drivePath := m.GetAppDrivePath(stackName)
|
||||
if drivePath == "" || !filepath.IsAbs(drivePath) {
|
||||
return fmt.Errorf("az alkalmazás meghajtója nem határozható meg")
|
||||
return util.MsgError("err.backup.az_alkalmazas_meghajtoja_nem_hatarozhato_meg")
|
||||
}
|
||||
if m.settings != nil && (m.settings.IsDisconnected(drivePath) || m.settings.IsDecommissioned(drivePath)) {
|
||||
return fmt.Errorf("az alkalmazás meghajtója nem elérhető (%s)", drivePath)
|
||||
return util.MsgError("err.backup.az_alkalmazas_meghajtoja_nem_elerheto", drivePath)
|
||||
}
|
||||
if !m.admitApp(stackName) {
|
||||
return fmt.Errorf("nincs elég szabad hely a mentéshez a(z) %s meghajtón", drivePath)
|
||||
return util.MsgError("err.backup.nincs_eleg_szabad_hely_a_menteshez", drivePath)
|
||||
}
|
||||
nsRoot = m.namespaceRoot(drivePath)
|
||||
|
||||
@@ -359,7 +360,7 @@ func (m *Manager) RunAppBackupNow(ctx context.Context, stackName string) error {
|
||||
}
|
||||
dbs, err := discover(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adatbázis-felderítés sikertelen: %w", err)
|
||||
return util.MsgError("err.backup.adatbazis_felderites_sikertelen", err)
|
||||
}
|
||||
dumped := 0
|
||||
for _, db := range dbs {
|
||||
@@ -368,7 +369,7 @@ func (m *Manager) RunAppBackupNow(ctx context.Context, stackName string) error {
|
||||
}
|
||||
res := DumpOne(ctx, db, AppDBDumpPath(nsRoot, stackName), m.logger, m.isDebug())
|
||||
if res.Error != nil {
|
||||
return fmt.Errorf("adatbázis-mentés sikertelen (%s): %w", db.ContainerName, res.Error)
|
||||
return util.MsgError("err.backup.adatbazis_mentes_sikertelen", db.ContainerName, res.Error)
|
||||
}
|
||||
dumped++
|
||||
m.logger.Printf("[INFO] [backup] update pre-backup for %s: database dump OK (%s, %s)", stackName, db.ContainerName, humanizeBytes(res.Size))
|
||||
@@ -380,13 +381,13 @@ func (m *Manager) RunAppBackupNow(ctx context.Context, stackName string) error {
|
||||
dump = m.DumpAppVolumesSafe
|
||||
}
|
||||
if err := dump(stackName); err != nil {
|
||||
return fmt.Errorf("kötetmentés sikertelen: %w", err)
|
||||
return util.MsgError("err.backup.kotetmentes_sikertelen", err)
|
||||
}
|
||||
m.logger.Printf("[INFO] [backup] update pre-backup for %s: volume dump OK", stackName)
|
||||
}
|
||||
|
||||
if err := m.CaptureRecoveryUnit(stackName); err != nil {
|
||||
return fmt.Errorf("a mentési egység rögzítése sikertelen: %w", err)
|
||||
return util.MsgError("err.backup.a_mentesi_egyseg_rogzitese_sikertelen", err)
|
||||
}
|
||||
m.logger.Printf("[INFO] [backup] update pre-backup for %s: recovery unit captured (%d database dump(s))", stackName, dumped)
|
||||
return nil
|
||||
@@ -437,7 +438,7 @@ func (m *Manager) updatePreBackupTail(stackName, nsRoot string, now time.Time) {
|
||||
func (m *Manager) WriteUpdateSafetyDump(ctx context.Context, stackName string) ([]string, error) {
|
||||
nsRoot := m.AppNamespaceRoot(stackName)
|
||||
if nsRoot == "" {
|
||||
return nil, fmt.Errorf("az alkalmazás mentési helye nem határozható meg")
|
||||
return nil, util.MsgError("err.backup.az_alkalmazas_mentesi_helye_nem_hatarozhato")
|
||||
}
|
||||
set, err := m.writeSafetyDump(ctx, stackName, nsRoot)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user