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.
gates / gates (push) Successful in 1m33s
gates / gates (push) Successful in 1m33s
This commit is contained in:
@@ -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).
|
**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)
|
### 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
|
- **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
|
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
|
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.
|
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.<domain>
|
||||||
|
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
|
- 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`.
|
`documentation/audits/night-2026-10-25/redproof-r1281.txt`.
|
||||||
|
|
||||||
|
|||||||
@@ -240,6 +240,26 @@ func (m *Manager) driveOfNamespace(nsRoot string) string {
|
|||||||
return nsRoot
|
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 (<root>/backups/set-aside/<stack>) that exist, on every
|
// SetAsideDirsForApp lists the app's set-aside folders (<root>/backups/set-aside/<stack>) that exist, on every
|
||||||
// namespace root a unit of it can sit on.
|
// namespace root a unit of it can sit on.
|
||||||
func (m *Manager) SetAsideDirsForApp(stack string) []string {
|
func (m *Manager) SetAsideDirsForApp(stack string) []string {
|
||||||
|
|||||||
@@ -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))
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1156,6 +1156,8 @@
|
|||||||
"deploy.email_kuldes": "Sending e-mail",
|
"deploy.email_kuldes": "Sending e-mail",
|
||||||
"deploy.email_kuldes_engedelyezese_ennel_az": "Allow sending e-mail for this app",
|
"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.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.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_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: <strong>{{.AppEmailFromAddress}}</strong>.",
|
"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: <strong>{{.AppEmailFromAddress}}</strong>.",
|
||||||
|
|||||||
@@ -1151,6 +1151,8 @@
|
|||||||
"deploy.email_kuldes": "Email-küldés",
|
"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.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.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.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_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: <strong>{{.AppEmailFromAddress}}</strong>.",
|
"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: <strong>{{.AppEmailFromAddress}}</strong>.",
|
||||||
|
|||||||
@@ -531,9 +531,11 @@ func (s *Server) deployPageData(w http.ResponseWriter, r *http.Request, name str
|
|||||||
data["RestoreRecordedAddress"] = ""
|
data["RestoreRecordedAddress"] = ""
|
||||||
data["RestoreRecordedDeclaresPath"] = declaresDataPath
|
data["RestoreRecordedDeclaresPath"] = declaresDataPath
|
||||||
data["RestoreHasRecord"] = false
|
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.
|
// Part 1's visibility line needs the real path, not a literal in a template.
|
||||||
data["SystemDataPath"] = s.cfg.Paths.SystemDataPath
|
data["SystemDataPath"] = s.cfg.Paths.SystemDataPath
|
||||||
if !alreadyDeployed && s.backupMgr != nil {
|
if !alreadyDeployed && s.backupMgr != nil {
|
||||||
|
data["KeptBackups"] = s.backupMgr.KeptBackupsFor(name)
|
||||||
if place, addr, ok := s.backupMgr.RecordedUnitForStack(name); ok {
|
if place, addr, ok := s.backupMgr.RecordedUnitForStack(name); ok {
|
||||||
prefill := map[string]string{}
|
prefill := map[string]string{}
|
||||||
if addr.Known() {
|
if addr.Known() {
|
||||||
|
|||||||
@@ -181,6 +181,12 @@ func i18nCasesA() []i18nCase {
|
|||||||
{"deploy_new", "deploy", func() map[string]interface{} {
|
{"deploy_new", "deploy", func() map[string]interface{} {
|
||||||
return i18nDeployData(false, stacks.StateNotDeployed, 1)
|
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.
|
// 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{} {
|
{"deploy_new_blocked_r1125", "deploy", func() map[string]interface{} {
|
||||||
return i18nDeployData(false, stacks.StateNotDeployed, 2)
|
return i18nDeployData(false, stacks.StateNotDeployed, 2)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,6 +78,15 @@
|
|||||||
{{T "deploy.ha_most_mashova_szeretned_telepiteni"}}
|
{{T "deploy.ha_most_mashova_szeretned_telepiteni"}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{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"). */}}
|
||||||
|
<div class="alert alert-info kept-backup-offer" data-kept-backups="{{.KeptBackups}}">
|
||||||
|
<svg class="ico ico-sm"><use href="#i-info"/></svg>
|
||||||
|
{{T "deploy.kept_backup_offer"}}
|
||||||
|
<a href="/backups/restore" class="alert-link">{{T "deploy.kept_backup_offer_link"}}</a>
|
||||||
|
</div>
|
||||||
|
{{- end}}
|
||||||
{{/* R-351 / Part 1 — WHERE THE DATA WILL LIVE, before the button is pressed. Measured
|
{{/* 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
|
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
|
their data goes to the system drive, which no screen said. This states it. It changes no
|
||||||
|
|||||||
+1262
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user