v0.243.0: FileBrowser generated admin password (R-513); per-tier whole-guest backup truth (R-517); skip absent-storage tiers (R-518); OOM-killed worker visible (R-514)
gates / gates (push) Successful in 14s

MinAgent: 0.131.0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-15 10:12:08 +02:00
parent 406755fa8f
commit 843b319f35
23 changed files with 1095 additions and 8 deletions
+51
View File
@@ -180,6 +180,7 @@ func (s *Server) dashboardHandler(w http.ResponseWriter, r *http.Request) {
data["SettingsWarning"] = s.settings.LoadWarning // non-empty if settings.json was recovered from corruption
data["Stacks"] = deployedStacks
data["MissingStorage"] = s.missingStorageMap(deployedStacks)
data["OOMKilled"] = s.stackMgr.OOMKilledStacks() // R-514
nw, ns := s.networkStorageWarnings(deployedStacks) // NAS unreachable (recoverable) / guest-side stub (defect)
data["NetworkWarnings"] = nw
data["NetworkStubs"] = ns
@@ -741,6 +742,24 @@ func (s *Server) appDetailHandler(w http.ResponseWriter, r *http.Request, slug s
}
}
// R-513 (v0.243.0): the file manager's login lives with the other app logins. The controller set a
// generated password (state "generated") or found one set by hand ("operator"). Same R-254 rule:
// only the username and a boolean reach the page; the value comes from the reveal endpoint.
if found.Name == fileBrowserStack && s.settings != nil {
state, enc, _ := s.settings.GetFileBrowserAdmin()
switch state {
case settings.FileBrowserAdminGenerated:
data["HasAppInfo"] = true
data["InitialCreds"] = &stacks.ExtractedCreds{Available: true, Username: "admin",
Note: "A Fájlkezelő belépése. A jelszót a Felhom állította be ezen a gépen."}
data["InitialCredsHasPassword"] = enc != ""
case settings.FileBrowserAdminOperator:
data["HasAppInfo"] = true
data["InitialCreds"] = &stacks.ExtractedCreds{Available: true, Username: "admin"}
data["InitialCredsOperatorSet"] = true
}
}
// Per-app migration (B1): offer to move this app's data to another connected drive (≠ current).
if found.Deployed {
current := ""
@@ -2261,6 +2280,11 @@ func (s *Server) appInitialCredsRevealHandler(w http.ResponseWriter, r *http.Req
escrowJSON(w, http.StatusNotFound, nil, "Ismeretlen alkalmazás.")
return
}
// R-513: the file manager's password is the controller's own stored value, not a container file.
if found.Name == fileBrowserStack {
s.fileBrowserPasswordReveal(w, r)
return
}
creds, err := s.readInitialCreds(found.Name)
if err != nil {
// Never swallowed, and never surfaced raw — the error can name a container/path.
@@ -2279,6 +2303,33 @@ func (s *Server) appInitialCredsRevealHandler(w http.ResponseWriter, r *http.Req
escrowJSON(w, http.StatusOK, map[string]any{"password": creds.Password}, "")
}
// fileBrowserStack is the protected infra stack whose admin password R-513 manages.
const fileBrowserStack = "filebrowser"
// fileBrowserPasswordReveal serves the generated FileBrowser admin password (R-513) under the same
// rules as every initial-credential reveal: POST + CSRF (router), no-store, logged as an act, and a
// refusal that says why when there is nothing to show.
func (s *Server) fileBrowserPasswordReveal(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
if s.settings == nil {
escrowJSON(w, http.StatusServiceUnavailable, nil, "A beállítások nem elérhetők.")
return
}
state, enc, _ := s.settings.GetFileBrowserAdmin()
if state != settings.FileBrowserAdminGenerated || enc == "" {
escrowJSON(w, http.StatusNotFound, nil, "A Fájlkezelő jelszavát nem a Felhom állította be ezen a gépen, ezért nem tudjuk megmutatni.")
return
}
pw, err := crypto.Decrypt(s.encKey, enc)
if err != nil || strings.TrimSpace(pw) == "" {
s.logger.Printf("[ERROR] [web] filebrowser password reveal: decrypt failed: %v", err)
escrowJSON(w, http.StatusInternalServerError, nil, "A jelszó most nem olvasható ki.")
return
}
s.logger.Printf("[INFO] [web] filebrowser admin password revealed from %s (value never logged)", clientIP(r))
escrowJSON(w, http.StatusOK, map[string]any{"password": pw}, "")
}
func (s *Server) settingsHandler(w http.ResponseWriter, r *http.Request) {
s.executeTemplate(w, r, "settings_system", s.systemPageData())
}