From fbec5cb49906b4fbca1816b4bcd29ed0845cf0bf Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 25 Oct 2026 23:32:01 +0100 Subject: [PATCH] R-1278: the install page says when a backup of an earlier install is kept and links the restore page (HU and EN). Red-proved. Commit only. --- CHANGELOG.md | 11 + controller/internal/backup/unit_owner.go | 20 + controller/internal/backup/unit_owner_test.go | 18 + controller/internal/i18n/locales/en.json | 2 + controller/internal/i18n/locales/hu.json | 2 + controller/internal/web/handlers.go | 2 + controller/internal/web/i18n_cases_a_test.go | 6 + .../web/r1278_kept_backup_offer_test.go | 36 + controller/internal/web/templates/deploy.html | 9 + .../deploy_new_kept_backup_r1278.html | 1262 +++++++++++++++++ 10 files changed, 1368 insertions(+) create mode 100644 controller/internal/web/r1278_kept_backup_offer_test.go create mode 100644 controller/internal/web/testdata/i18n_parity/deploy_new_kept_backup_r1278.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 0454a737..6972fbaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ **MinAgent: 0.158.1** (unchanged). The hold acts only on a box whose agent reports `waiting_for_quiet` (≥ 0.165.0). +### The install page says when a backup of an earlier install is kept (R-1278, P3) + +- `internal/web/handlers.go` + `templates/deploy.html`: for an app not installed, when the box keeps a backup of an + earlier install (`backup.KeptBackupsFor`: its set-aside units, D47, and the unit „remove — keep backups" left), an + info notice says the new install starts empty and links „A megőrzött mentések megnyitása" (`/backups/restore`). HU and + EN keys `deploy.kept_backup_offer{,_link}`; the hidden case renders byte for byte as before (the parity fixtures + unchanged); a new parity case `deploy_new_kept_backup_r1278`. Tests: `TestR1278_*` (render per branch, the wiring, the + count). Red-proof: felhom.eu `documentation/audits/night-2026-10-25/redproof-r1278.txt`. + ### FileBrowser is off the app network: only traefik (and a linked OnlyOffice) reach it (R-1281, P2, D44 #23) - **Measured before** (the night's rig, 0.344.0-rc.2): from the qBittorrent and Transmission containers (on @@ -13,6 +22,8 @@ login was refused (R-1126's gate, unchanged) — never traefik-public any more; one an earlier controller put on traefik-public leaves it. OnlyOffice joins `felhom-files` while — and only while — its FileBrowser link is on (it fetches and saves documents at `http://filebrowser:80`, R-1018) — the one stated exception. The route stays by name. +- **On the release every box re-creates FileBrowser once** (its rendered compose changed — a comment); files. + is away for a few seconds, then served on the files network (seen on the night's rig). - Tests: `TestR1281_*` (three) + the two R-1126 gate tests updated to the files network. Red-proofs: felhom.eu `documentation/audits/night-2026-10-25/redproof-r1281.txt`. diff --git a/controller/internal/backup/unit_owner.go b/controller/internal/backup/unit_owner.go index 9673c253..6fa1dd1a 100644 --- a/controller/internal/backup/unit_owner.go +++ b/controller/internal/backup/unit_owner.go @@ -240,6 +240,26 @@ func (m *Manager) driveOfNamespace(nsRoot string) string { return nsRoot } +// KeptBackupsFor (R-1278) counts the backups of an EARLIER install of stack that the box keeps: its set-aside units +// (D47) and the unit „remove — keep backups" left (a removed app's). The install page names them beside a fresh start. +func (m *Manager) KeptBackupsFor(stack string) int { + if m == nil || !validMirrorStackName(stack) { + return 0 + } + n := 0 + if m.settings != nil { + for _, k := range m.settings.GetKeptCopies() { + if k.Kind == KeptKindUnit && k.Source == stack { + n++ + } + } + } + if _, ok := m.RemovedAppUnitFor(stack); ok { + n++ + } + return n +} + // SetAsideDirsForApp lists the app's set-aside folders (/backups/set-aside/) that exist, on every // namespace root a unit of it can sit on. func (m *Manager) SetAsideDirsForApp(stack string) []string { diff --git a/controller/internal/backup/unit_owner_test.go b/controller/internal/backup/unit_owner_test.go index c05a8c3c..303c6665 100644 --- a/controller/internal/backup/unit_owner_test.go +++ b/controller/internal/backup/unit_owner_test.go @@ -373,3 +373,21 @@ func TestR1288_ARestoreOfASetAsideUnitRetiresTheReplacedUnitQuietly(t *testing.T t.Fatalf("the next backup set something aside again: alarms %v, set-aside %v", alarms, r.setAside(t)) } } + +// R-1278: the install page's count of an earlier install's kept backups — a set-aside unit counts once per copy. +func TestR1278_KeptBackupsForCountsTheSetAsideUnits(t *testing.T) { + r := d47Setup(t, "aaaa000000000001") + if n := r.m.KeptBackupsFor("tandoor"); n != 0 { + t.Fatalf("before any set-aside: %d", n) + } + r.reinstall("bbbb000000000002") + if err := r.m.captureRecoveryUnit("tandoor", false); err != nil { + t.Fatal(err) + } + if n := r.m.KeptBackupsFor("tandoor"); n != 1 { + t.Fatalf("after one set-aside: %d, want 1", n) + } + if n := r.m.KeptBackupsFor("other"); n != 0 { + t.Fatalf("another app counts nothing: %d", n) + } +} diff --git a/controller/internal/i18n/locales/en.json b/controller/internal/i18n/locales/en.json index 76262d2c..da1443f2 100644 --- a/controller/internal/i18n/locales/en.json +++ b/controller/internal/i18n/locales/en.json @@ -1156,6 +1156,8 @@ "deploy.email_kuldes": "Sending e-mail", "deploy.email_kuldes_engedelyezese_ennel_az": "Allow sending e-mail for this app", "deploy.engedelyezett_orszagok_ehhez_az_alkalmaz": "Allowed countries for this app", + "deploy.kept_backup_offer": "The box kept a backup of an earlier install of this app. The new install starts empty; afterwards you can load the backup on the Restore page.", + "deploy.kept_backup_offer_link": "Open the kept backups", "deploy.ennek_az_alkalmazasnak_van_korabbi": "This app has an earlier backup, so we filled in the fields below from its own backup.", "deploy.ez_az_alkalmazas_mar_telepitve": "This app is already installed. The settings below are read-only.", "deploy.ez_az_alkalmazas_tud_emailt": "This app can send e-mail (for example password resets, invitations) through Felhom,\n without setting up a separate provider. The sender address: {{.AppEmailFromAddress}}.", diff --git a/controller/internal/i18n/locales/hu.json b/controller/internal/i18n/locales/hu.json index 309f248f..775f5119 100644 --- a/controller/internal/i18n/locales/hu.json +++ b/controller/internal/i18n/locales/hu.json @@ -1151,6 +1151,8 @@ "deploy.email_kuldes": "Email-küldés", "deploy.email_kuldes_engedelyezese_ennel_az": "Email-küldés engedélyezése ennél az alkalmazásnál", "deploy.engedelyezett_orszagok_ehhez_az_alkalmaz": "Engedélyezett országok ehhez az alkalmazáshoz", + "deploy.kept_backup_offer": "A doboz megőrizte ennek az alkalmazásnak egy korábbi telepítéséből származó mentését. Az új telepítés üresen indul; a mentést utána a Visszaállítás oldalon töltheted vissza.", + "deploy.kept_backup_offer_link": "A megőrzött mentések megnyitása", "deploy.ennek_az_alkalmazasnak_van_korabbi": "Ennek az alkalmazásnak van korábbi mentése, ezért az alábbi mezőket a saját mentése alapján töltöttük ki.", "deploy.ez_az_alkalmazas_mar_telepitve": "Ez az alkalmazás már telepítve van. Az alábbi beállítások csak olvashatók.", "deploy.ez_az_alkalmazas_tud_emailt": "Ez az alkalmazás tud emailt küldeni (pl. jelszó-visszaállítás, meghívók) a Felhom-on keresztül,\n külön szolgáltató beállítása nélkül. A feladó címe: {{.AppEmailFromAddress}}.", diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go index 156f5b8e..1861c24b 100644 --- a/controller/internal/web/handlers.go +++ b/controller/internal/web/handlers.go @@ -531,9 +531,11 @@ func (s *Server) deployPageData(w http.ResponseWriter, r *http.Request, name str data["RestoreRecordedAddress"] = "" data["RestoreRecordedDeclaresPath"] = declaresDataPath data["RestoreHasRecord"] = false + data["KeptBackups"] = 0 // R-1278: backups of an earlier install the box keeps (set-aside or removed-app units) // Part 1's visibility line needs the real path, not a literal in a template. data["SystemDataPath"] = s.cfg.Paths.SystemDataPath if !alreadyDeployed && s.backupMgr != nil { + data["KeptBackups"] = s.backupMgr.KeptBackupsFor(name) if place, addr, ok := s.backupMgr.RecordedUnitForStack(name); ok { prefill := map[string]string{} if addr.Known() { diff --git a/controller/internal/web/i18n_cases_a_test.go b/controller/internal/web/i18n_cases_a_test.go index d092a462..c9b5ed57 100644 --- a/controller/internal/web/i18n_cases_a_test.go +++ b/controller/internal/web/i18n_cases_a_test.go @@ -181,6 +181,12 @@ func i18nCasesA() []i18nCase { {"deploy_new", "deploy", func() map[string]interface{} { return i18nDeployData(false, stacks.StateNotDeployed, 1) }}, + // R-1278 (2026-10-25 night): a backup of an earlier install is kept — the install page says so. + {"deploy_new_kept_backup_r1278", "deploy", func() map[string]interface{} { + d := i18nDeployData(false, stacks.StateNotDeployed, 1) + d["KeptBackups"] = 1 + return d + }}, // R-1125 (v0.327.0, D10 = A): this page shows the same memory sentence — re-captured under this name. {"deploy_new_blocked_r1125", "deploy", func() map[string]interface{} { return i18nDeployData(false, stacks.StateNotDeployed, 2) diff --git a/controller/internal/web/r1278_kept_backup_offer_test.go b/controller/internal/web/r1278_kept_backup_offer_test.go new file mode 100644 index 00000000..783a8b95 --- /dev/null +++ b/controller/internal/web/r1278_kept_backup_offer_test.go @@ -0,0 +1,36 @@ +package web + +import ( + "os" + "strings" + "testing" +) + +// R-1278: a volume app's reinstall said nothing about the backup of the earlier install the box keeps (set aside by +// D47, or left by „remove — keep backups"). The install page now says the install starts empty and links the restore +// page. One render per branch of the gate (the template-gate rule): kept + not installed → shown; none → not shown; +// installed → not shown. +// RED-PROOF: delete the {{if .KeptBackups}} block → the first case fails. +func TestR1278_TheInstallPageOffersTheKeptBackup(t *testing.T) { + shown := renderDeployWithFields(t, false, map[string]interface{}{"KeptBackups": 2}) + if !strings.Contains(shown, `data-kept-backups="2"`) || !strings.Contains(shown, `href="/backups/restore"`) { + t.Fatal("with a kept backup of an earlier install the page must say so and link the restore page") + } + if strings.Contains(renderDeployWithFields(t, false, map[string]interface{}{"KeptBackups": 0}), "kept-backup-offer") { + t.Fatal("no kept backup → no notice") + } + if strings.Contains(renderDeployWithFields(t, false, map[string]interface{}{"KeptBackups": 1, "AlreadyDeployed": true}), "kept-backup-offer") { + t.Fatal("an installed app shows no install-time notice") + } +} + +// The page reads the count from the backup manager (seam-built-but-never-wired is a recorded class). +func TestR1278_TheDeployPageAsksTheBackupManager(t *testing.T) { + b, err := os.ReadFile("handlers.go") + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(b), `data["KeptBackups"] = s.backupMgr.KeptBackupsFor(name)`) { + t.Fatal("deployPageData must set KeptBackups from the backup manager") + } +} diff --git a/controller/internal/web/templates/deploy.html b/controller/internal/web/templates/deploy.html index 9e9eaaeb..d9c9583e 100644 --- a/controller/internal/web/templates/deploy.html +++ b/controller/internal/web/templates/deploy.html @@ -78,6 +78,15 @@ {{T "deploy.ha_most_mashova_szeretned_telepiteni"}} {{end}} + {{- if and (not .AlreadyDeployed) .KeptBackups}}{{/* R-1278: a backup of an earlier install is kept (set aside, D47, + or left by „remove — keep backups"). The install starts empty; the backup is loaded afterwards on the + restore page („Megőrzött másolatok"). */}} +
+ + {{T "deploy.kept_backup_offer"}} + {{T "deploy.kept_backup_offer_link"}} +
+ {{- end}} {{/* R-351 / Part 1 — WHERE THE DATA WILL LIVE, before the button is pressed. Measured 2026-08-21: 13 of 53 catalog templates declare a storage field; the other 40 have none and their data goes to the system drive, which no screen said. This states it. It changes no diff --git a/controller/internal/web/testdata/i18n_parity/deploy_new_kept_backup_r1278.html b/controller/internal/web/testdata/i18n_parity/deploy_new_kept_backup_r1278.html new file mode 100644 index 00000000..ecfd2627 --- /dev/null +++ b/controller/internal/web/testdata/i18n_parity/deploy_new_kept_backup_r1278.html @@ -0,0 +1,1262 @@ + + + + + + + + Private Bin telepítése — Felhom.eu + + + + + + + + +
+ + +
+ + +
+ + + + + + + + + + +← Vissza + +
+
A mentés sikeresen elkészült.
+
A művelet most nem sikerült.
+ +
+ +
+

Private Bin — Telepítés

+

Egy teszt alkalmazás leírása

+
+ ~128M RAM + HDD szükséges + Magyar felület + Részletes leírás, képernyőképek +
+
+
+ + + + +
+ +
+ Memória + 3000 MB / 4000 MB (75%) +
+
+
+
+
+
+ Jelenlegi használat (2800 MB) + Private Bin (+128 MB) +
+ +
+ Az alkalmazások csúcsterhelése meghaladhatja a rendelkezésre álló memóriát. Ilyenkor a doboz nagy feltöltések alatt lelassulhat, és akár percekig sem válaszol. Ha ez gyakran előfordul, távolíts el egy alkalmazást. A gépen nincs elég cserehely (swap), ezért nagy feltöltéskor a doboz lelassulhat, és percekig sem válaszolhat. Szólj a Felhomnak, mi rendbe tesszük. +
+ + +
+ + + + + +
+ + +
+ + Ennek az alkalmazásnak van korábbi mentése, ezért az alábbi mezőket a saját mentése alapján töltöttük ki. + Korábbi webcím: paste.example.hu. + Az adatai itt voltak: HDD egy. + Ha most máshová szeretnéd telepíteni, nyugodtan átírhatod — a visszaállítás előtt jelezni fogjuk az eltérést. +
+ +
+ + A doboz megőrizte ennek az alkalmazásnak egy korábbi telepítéséből származó mentését. Az új telepítés üresen indul; a mentést utána a Visszaállítás oldalon töltheted vissza. + A megőrzött mentések megnyitása +
+ + +
+ + Hol lesznek az adatok: ennél az alkalmazásnál nincs külön adatmeghajtó-választás, + ezért az adatai a rendszermeghajtóra kerülnek (/mnt/sys_drive/data). A mentései így is elkészülnek. + +
+ + + + + +
+

Automatikusan generált értékek

+

Ezek az értékek a telepítéssel együtt mentésre kerülnek. Jegyezd fel a szükséges jelszavakat!

+ + + +
+ + + +
+ + +
+ + + + + +
+ + +
+ + + + + + + +
+ + +
+ + +
+ +
+ + + +
+

Beállítások

+ +
+ + + +
+ + .example.hu +
+ Az aldomain telepítés után nem módosítható. Megváltoztatáshoz az alkalmazás eltávolítása és újratelepítése szükséges (minden adat törlődik). + + + +
+ +
+ + + + + + + +
+ +
+ + + +
+ + + +
+
+ +
+ + + +
+ +
+ + + + + + + + Egy kapcsoló leírása + +
+ +
+ + + + + + +
+ A kiválasztott meghajtón az alkalmazás fájljai (média, dokumentumok) tárolódnak. + Az adatbázis a gyors belső SSD-n fut — és az alkalmazással együtt készül róla biztonsági mentés. +
+ + + + +
+ +
+ + + + + + + +
+ +
+ + + + +
+ + Mégsem +
+ +
+ + +
+ + + + +
+ + + +