R-516: the formal forms in Go literals move into the bundle in the te-form
gates / gates (push) Successful in 50s
gates / gates (push) Successful in 50s
22 Go-literal messages (escrow handler, share password page, export upload, network-storage uid/remove/attach failures) are bundle keys with te-form Hungarian and English; a detached attach failure renders in the reader's language. The NAS refusal says „Válassz". Setup wizard, recovery-info.txt (R-554) and the SMART/fill-watch wire texts are left. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
**MinAgent: 0.131.0** (unchanged — no new agent route is read).
|
||||
|
||||
- **R-542:** `GET /api/disks/candidates` no longer offers a drive that backs a REGISTERED storage path — not under `initialize` (the format wizard renders that list as-is, so a household's in-use data drive was offered for formatting) and not again under `attach`. The join is the guest's own mount table (a registered path's mount source is the host device). Fail-safe: an unreadable mount table empties `initialize`; `attach` passes through. Tests `TestR542_*` (two red-proofs observed).
|
||||
- **R-516 (the Go-literal leftover):** 22 messages that lived as Go string literals — the escrow handler (6: no password set, too many attempts, staging failed, a code already being made, a code not shown, no active job), the share-page password (2), the export upload size mismatch, the network-storage uid refusal and in-use removal, and the network-storage attach failures (10 categories + the Windows-name tip) — are bundle keys now (`api.escrow.*`, `api.share.*`, `api.export.*`, `api.netstorage.*`), in the te-form in Hungarian (14 of them were in the formal „ön" form: „próbálja", „állítson be", „várja meg", „indítsa", „töltse", „ellenőrizze", „használja", „kapcsolja be", „állítsa", „jelezze", „távolítsa el", „adjon meg"), with English. A failed attach job is rendered in the READER's language at the status read (it ran detached; it keeps its category, server and uid). The NAS app-namespace refusal says „Válassz" (stays a settings constant). Left on purpose: the setup wizard and `recovery-info.txt` (R-554), and the SMART and fill-watch texts — they are the hub event's message, mailed as is (wire text). Tests `TestR516_GoLiteralFormalFormsAreGone` (keys in both languages + a source scan with a positive control), `TestR516_NetAddFailureFollowsTheReader`; red-proofs `felhom.eu/documentation/audits/night-burndown-2026-10-06/ctrl/R-516-red*.txt`.
|
||||
|
||||
## v0.301.0 — one short stop per backup tier; restores replay before the app starts; the family window reopens a command-closed sign-up (R-518, R-638, R-717; `09` §3 154, 156) (2026-10-06)
|
||||
|
||||
|
||||
@@ -2549,6 +2549,28 @@
|
||||
"err.kept.not_listed": "This is not kept data, so nothing was touched.",
|
||||
"err.kept.occupied": "The app's folder already holds data, so the kept files were not put back. Nothing changed.",
|
||||
"api.kept.busy": "A backup or a restore is running now. Try again when it has finished.",
|
||||
"api.escrow.dashboard_password_missing": "The dashboard password is not set — set a password first.",
|
||||
"api.escrow.too_many_attempts": "Too many failed attempts, try again in 1 minute.",
|
||||
"api.escrow.stage_failed": "Preparing the remote backup password for escrow failed — the process did not start. Try again.",
|
||||
"api.escrow.already_running": "A code is already being created — wait until it finishes.",
|
||||
"api.escrow.code_not_shown": "The code was created but not shown — for security it cannot be fetched again. Start the process again: the new code replaces the old one.",
|
||||
"api.escrow.no_active_job": "There is no active recovery process — start creating the code first.",
|
||||
"api.share.form_invalid": "Invalid form — reload the page.",
|
||||
"api.share.too_many_attempts": "Too many failed attempts, try again in 1 minute",
|
||||
"api.export.upload_size_mismatch": "The uploaded size does not match — try again.",
|
||||
"api.netstorage.uid_range": "The user id (uid) of the app is invalid. Enter a value between 1 and 65533 — most media apps use 1000.",
|
||||
"api.netstorage.remove_in_use": "The storage cannot be removed while an app uses it: %s. Remove or move the app first.",
|
||||
"api.netstorage.add.unreachable": "The server cannot be reached (%s). Check the IP address and that the NAS is switched on.",
|
||||
"api.netstorage.add.flat_name_tip": " Tip: »%s« looks like a Windows network name — use the IP address of the device.",
|
||||
"api.netstorage.add.nfs_export": "The share was not found, or the NAS does not allow this machine access. Check the export path and that the NAS allows the IP address of the Felhom machine.",
|
||||
"api.netstorage.add.smb_auth": "Wrong SMB user name or password.",
|
||||
"api.netstorage.add.smb_share": "The SMB share was not found on the server. Check the share name.",
|
||||
"api.netstorage.add.timeout": "Timed out while mounting — the server is reachable on the network, but the share cannot be mounted. Check the NFS/SMB service of the NAS.",
|
||||
"api.netstorage.add.not_writable": "The share can be mounted, but apps cannot write to it. For NFS, turn on „map all users” (all squash) for the share on the NAS — or set the owner of the files to id %d. For SMB, check that the user may write to the share.",
|
||||
"api.netstorage.add.not_network_fs": "The network storage was not mounted correctly inside the system. Try connecting again; if it happens again, tell the operator.",
|
||||
"api.netstorage.add.probe_io": "Write error on the share (the data could not be read back intact). Check the share and the network.",
|
||||
"api.netstorage.add.busy": "A connection is already in progress. Wait until it finishes.",
|
||||
"api.netstorage.add.failed": "Mounting failed. Details below.",
|
||||
"page.title.kept_data": "Kept data",
|
||||
"note.restore.older_version": "%s is back from the backup of %s, at version %s. An update is available: the box brings it up to date one step at a time.",
|
||||
"note.restore.older_version_no_step": "%s is back from the backup of %s, at version %s. No tested update step leads on from this version, so the box does not update it by itself.",
|
||||
|
||||
@@ -2537,6 +2537,28 @@
|
||||
"err.kept.not_listed": "Ez nem megőrzött adat, ezért nem nyúltunk hozzá.",
|
||||
"err.kept.occupied": "Az alkalmazás mappájában már vannak adatok, ezért a megőrzött fájlokat nem tettük vissza. Nem változott semmi.",
|
||||
"api.kept.busy": "Most egy mentés vagy visszaállítás fut. Ha befejeződött, próbáld újra.",
|
||||
"api.escrow.dashboard_password_missing": "A vezérlőpult jelszava nincs beállítva — előbb állíts be jelszót.",
|
||||
"api.escrow.too_many_attempts": "Túl sok sikertelen próbálkozás, próbáld újra 1 perc múlva.",
|
||||
"api.escrow.stage_failed": "A távoli mentés jelszavának letéti előkészítése nem sikerült — a folyamat nem indult el. Próbáld újra.",
|
||||
"api.escrow.already_running": "Egy kódkészítés már folyamatban van — várd meg, míg befejeződik.",
|
||||
"api.escrow.code_not_shown": "A kód létrejött, de nem lett megjelenítve — biztonsági okból újra nem kérhető le. Indítsd újra a folyamatot: az új kód a régit érvényteleníti.",
|
||||
"api.escrow.no_active_job": "Nincs aktív helyreállítási folyamat — előbb indítsd el a kódkészítést.",
|
||||
"api.share.form_invalid": "Érvénytelen űrlap — töltsd újra az oldalt.",
|
||||
"api.share.too_many_attempts": "Túl sok sikertelen próbálkozás, próbáld újra 1 perc múlva",
|
||||
"api.export.upload_size_mismatch": "A feltöltött méret nem egyezik — próbáld újra.",
|
||||
"api.netstorage.uid_range": "Az alkalmazás felhasználói azonosítója (uid) érvénytelen. Adj meg 1 és 65533 közötti értéket — a legtöbb médiaalkalmazás az 1000-est használja.",
|
||||
"api.netstorage.remove_in_use": "A tároló nem távolítható el, amíg alkalmazás használja: %s. Előbb távolítsd el vagy költöztesd át az alkalmazást.",
|
||||
"api.netstorage.add.unreachable": "A szerver nem érhető el (%s). Ellenőrizd az IP-címet, és hogy a NAS be van-e kapcsolva.",
|
||||
"api.netstorage.add.flat_name_tip": " Tipp: a(z) »%s« Windows-hálózati névnek tűnik — használd az eszköz IP-címét.",
|
||||
"api.netstorage.add.nfs_export": "A megosztás nem található, vagy a NAS nem engedélyezi ennek a gépnek a hozzáférését. Ellenőrizd az export útvonalát, és hogy a NAS engedélyezi-e a Felhom gép IP-címét.",
|
||||
"api.netstorage.add.smb_auth": "Hibás SMB felhasználónév vagy jelszó.",
|
||||
"api.netstorage.add.smb_share": "A megadott SMB-megosztás nem található a szerveren. Ellenőrizd a megosztás nevét.",
|
||||
"api.netstorage.add.timeout": "Időtúllépés a csatolás közben — a szerver elérhető a hálózaton, de a megosztás nem csatolható. Ellenőrizd a NAS NFS/SMB szolgáltatását.",
|
||||
"api.netstorage.add.not_writable": "A megosztás csatolható, de az alkalmazások nem tudnak rá írni. NFS esetén kapcsold be a NAS-on a „minden felhasználó leképezése” (map all users / all squash) beállítást a megosztáson — vagy állítsd a fájlok tulajdonosát a(z) %d azonosítóra. SMB esetén ellenőrizd, hogy a felhasználónak írási joga van a megosztáson.",
|
||||
"api.netstorage.add.not_network_fs": "A hálózati tárhely csatolása a rendszeren belül nem jött létre megfelelően. Próbáld újra a csatlakoztatást; ha a hiba ismétlődik, jelezd az üzemeltetőnek.",
|
||||
"api.netstorage.add.probe_io": "Írási hiba a megosztáson (az adat nem olvasható vissza hibátlanul). Ellenőrizd a megosztást és a hálózatot.",
|
||||
"api.netstorage.add.busy": "Már folyamatban van egy csatlakoztatás. Várd meg, amíg befejeződik.",
|
||||
"api.netstorage.add.failed": "A csatolás sikertelen. Részletek alább.",
|
||||
"page.title.kept_data": "Megőrzött adatok",
|
||||
"note.restore.older_version": "A(z) %s visszaállt a(z) %s-i mentésből, a(z) %s verzióra. Elérhető frissítés: a doboz lépésenként hozza naprakészre.",
|
||||
"note.restore.older_version_no_step": "A(z) %s visszaállt a(z) %s-i mentésből, a(z) %s verzióra. Ettől a verziótól nem vezet kipróbált frissítési lépés, ezért a doboz magától nem frissíti.",
|
||||
|
||||
@@ -33,8 +33,9 @@ func TestRefuseAppNamespace_DoesNotNameAnImpossibleAction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// The NAS refusal is a different situation and keeps its own wording — it points at a list that
|
||||
// genuinely does have entries, so it is not the same defect.
|
||||
if !strings.Contains(refuseAppNamespaceNetwork, "Válasszon csatlakoztatott meghajtót") {
|
||||
// genuinely does have entries, so it is not the same defect. (R-516, 2026-10-06 night: the same
|
||||
// sentence in the product's te-form — „Válassz", not „Válasszon".)
|
||||
if !strings.Contains(refuseAppNamespaceNetwork, "Válassz csatlakoztatott meghajtót") {
|
||||
t.Fatal("the NAS refusal was changed; it is a different situation and was not part of R-220")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2139,7 +2139,7 @@ func (s *Settings) DriveOfSubPath(path string) (string, bool) {
|
||||
// an internal path or field name.
|
||||
const (
|
||||
refuseAppNamespaceNetwork = "Hálózati tárhelyen (NAS) nem futtatható alkalmazás adatkönyvtára — " +
|
||||
"a NAS megosztás tallózásra és médiatárolásra használható. Válasszon csatlakoztatott meghajtót."
|
||||
"a NAS megosztás tallózásra és médiatárolásra használható. Válassz csatlakoztatott meghajtót."
|
||||
// ⚠ R-220 / SCENARIO G — THIS SENTENCE USED TO NAME AN IMPOSSIBLE ACTION.
|
||||
//
|
||||
// It said *"Válasszon a listából csatlakoztatott meghajtót"* — choose an attached drive from the
|
||||
|
||||
@@ -181,13 +181,13 @@ func (s *Server) escrowStartAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// this state; a legacy-open box must claim/set a password first).
|
||||
hash := s.effectivePasswordHash()
|
||||
if hash == "" {
|
||||
escrowJSON(w, http.StatusForbidden, nil, "A vezérlőpult jelszava nincs beállítva — előbb állítson be jelszót.")
|
||||
escrowJSON(w, http.StatusForbidden, nil, s.msg(r, "api.escrow.dashboard_password_missing"))
|
||||
return
|
||||
}
|
||||
ip := rateKey(r)
|
||||
if s.escrowRateLimited(ip) {
|
||||
s.logger.Printf("[WARN] [web] escrow start rate limited for %s", ip)
|
||||
escrowJSON(w, http.StatusTooManyRequests, nil, "Túl sok sikertelen próbálkozás, próbálja újra 1 perc múlva.")
|
||||
escrowJSON(w, http.StatusTooManyRequests, nil, s.msg(r, "api.escrow.too_many_attempts"))
|
||||
return
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(r.FormValue("password"))); err != nil {
|
||||
@@ -212,7 +212,7 @@ func (s *Server) escrowStartAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if s.backupMgr != nil && s.backupMgr.OffboxConfigured() {
|
||||
if err := s.escrowStage(r.Context()); err != nil {
|
||||
s.logger.Printf("[WARN] [web] escrow start: re-stage failed (ceremony NOT started): %v", err) // err carries no secret
|
||||
escrowJSON(w, http.StatusBadGateway, nil, "A távoli mentés jelszavának letéti előkészítése nem sikerült — a folyamat nem indult el. Próbálja újra.")
|
||||
escrowJSON(w, http.StatusBadGateway, nil, s.msg(r, "api.escrow.stage_failed"))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -232,7 +232,7 @@ func (s *Server) escrowStartAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
resp, status, err := agent.EscrowCeremonyStart(r.Context())
|
||||
if err != nil {
|
||||
if status == http.StatusConflict {
|
||||
escrowJSON(w, http.StatusConflict, nil, "Egy kódkészítés már folyamatban van — várja meg, míg befejeződik.")
|
||||
escrowJSON(w, http.StatusConflict, nil, s.msg(r, "api.escrow.already_running"))
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[WARN] [web] escrow start: agent trigger: %v", err)
|
||||
@@ -277,11 +277,11 @@ func (s *Server) escrowClaimAPIHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
switch status {
|
||||
case http.StatusGone: // 410 — the code was minted but never shown; permanently void.
|
||||
escrowJSON(w, http.StatusGone, nil, "A kód létrejött, de nem lett megjelenítve — biztonsági okból újra nem kérhető le. Indítsa újra a folyamatot: az új kód a régit érvényteleníti.")
|
||||
escrowJSON(w, http.StatusGone, nil, s.msg(r, "api.escrow.code_not_shown"))
|
||||
case http.StatusNotFound: // 404 — no ceremony has run (e.g. phase:none post-reboot). F-C:
|
||||
// this used to fall through to a 502; a bad-gateway class code for "nothing to claim"
|
||||
// is wrong. Relay a clean, honest 4xx.
|
||||
escrowJSON(w, http.StatusNotFound, nil, "Nincs aktív helyreállítási folyamat — előbb indítsa el a kódkészítést.")
|
||||
escrowJSON(w, http.StatusNotFound, nil, s.msg(r, "api.escrow.no_active_job"))
|
||||
case http.StatusConflict: // 409 — the ceremony state doesn't allow a claim right now. F-C.
|
||||
escrowJSON(w, http.StatusConflict, nil, "A folyamat jelenlegi állapotában a kód nem kérhető le.")
|
||||
default: // status 0 (agent unreachable / transport error) or a genuine agent 5xx — a real
|
||||
|
||||
@@ -319,7 +319,7 @@ func (s *Server) apiUploadFinalize(w http.ResponseWriter, r *http.Request) {
|
||||
logx.Warnf(s.logger, "[web] fab upload finalize size mismatch: got %d, declared %d — part deleted",
|
||||
job.received, job.declared)
|
||||
uploadJSON(w, http.StatusUnprocessableEntity, map[string]interface{}{
|
||||
"ok": false, "error": "A feltöltött méret nem egyezik — próbálja újra.",
|
||||
"ok": false, "error": s.msg(r, "api.export.upload_size_mismatch"),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package web
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -35,8 +34,8 @@ const (
|
||||
// validMappedID reports whether id is a valid container uid/gid (F4 range check).
|
||||
func validMappedID(id int) bool { return id >= mappedIDMin && id <= mappedIDMax }
|
||||
|
||||
// netAddUIDRangeMsg is the friendly F4 refusal for an out-of-range mapped uid/gid.
|
||||
const netAddUIDRangeMsg = "Az alkalmazás felhasználói azonosítója (uid) érvénytelen. Adjon meg 1 és 65533 közötti értéket — a legtöbb médiaalkalmazás az 1000-est használja."
|
||||
// netAddUIDRangeKey is the friendly F4 refusal for an out-of-range mapped uid/gid (bundle key, R-516).
|
||||
const netAddUIDRangeKey = "api.netstorage.uid_range"
|
||||
|
||||
// netAddOutdatedMsg is the sync add-time refusal (machine code "agent_outdated") when the agent
|
||||
// predates the coupled verify-before-commit add semantics (pre-v0.81.0).
|
||||
@@ -130,7 +129,7 @@ func (s *Server) handleNetStorageAdd(w http.ResponseWriter, r *http.Request) {
|
||||
// here with a friendly Hungarian 400 — nothing is installed.
|
||||
if !validMappedID(uid) || !validMappedID(gid) {
|
||||
logx.Debugf(s.logger, "[web] netstorage add %q refused by validation: uid/gid out of range (uid=%d gid=%d)", name, uid, gid)
|
||||
writeDiskJSON(w, http.StatusBadRequest, false, netAddUIDRangeMsg, nil)
|
||||
writeDiskJSON(w, http.StatusBadRequest, false, s.msg(r, netAddUIDRangeKey), nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -179,7 +178,7 @@ func (s *Server) handleNetStorageAddStatus(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
writeDiskJSON(w, http.StatusOK, true, "", map[string]any{
|
||||
"phase": job.Phase, "name": job.Name, "category": job.Category,
|
||||
"message": job.Message, "detail": trimNetDetail(job.Detail), "warn": job.Warn,
|
||||
"message": s.netAddJobMessage(r, job), "detail": trimNetDetail(job.Detail), "warn": job.Warn,
|
||||
"path": job.Path, "started_at": job.StartedAt, "updated_at": job.UpdatedAt,
|
||||
})
|
||||
}
|
||||
@@ -306,9 +305,7 @@ func (s *Server) handleNetStorageRemove(w http.ResponseWriter, r *http.Request)
|
||||
// files deleted anyway → an unreapable autofs mount until host reboot — the C6B-F2 orphan).
|
||||
if apps := s.deployedAppsOnPath(where); len(apps) > 0 {
|
||||
s.logger.Printf("[WARN] [web] netstorage remove %q refused: deployed app(s) on the share: %s", name, strings.Join(apps, ", "))
|
||||
writeDiskJSON(w, http.StatusConflict, false, fmt.Sprintf(
|
||||
"A tároló nem távolítható el, amíg alkalmazás használja: %s. Előbb távolítsa el vagy költöztesse át az alkalmazást.",
|
||||
strings.Join(apps, ", ")), nil)
|
||||
writeDiskJSON(w, http.StatusConflict, false, s.msg(r, "api.netstorage.remove_in_use", strings.Join(apps, ", ")), nil)
|
||||
return
|
||||
}
|
||||
agent, err := s.netAgentForAdd()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -41,13 +42,17 @@ type netAgent interface {
|
||||
|
||||
// netAddJob is the poll-visible orchestration state (GET /api/storage/netstorage/add/status).
|
||||
type netAddJob struct {
|
||||
Name string `json:"name"`
|
||||
Phase string `json:"phase"` // agent_add | verifying | probing | registering | done | failed
|
||||
Category string `json:"category,omitempty"` // failure category (agent classifier vocabulary + not_writable/probe_io)
|
||||
Message string `json:"message,omitempty"` // the category's Hungarian customer message (§3.2)
|
||||
Detail string `json:"detail,omitempty"` // raw English/journal detail (the collapsible)
|
||||
Warn string `json:"warn,omitempty"` // non-fatal note on a successful add (e.g. probe cleanup)
|
||||
Path string `json:"path,omitempty"` // the registered in-guest path (done only)
|
||||
Name string `json:"name"`
|
||||
Phase string `json:"phase"` // agent_add | verifying | probing | registering | done | failed
|
||||
Category string `json:"category,omitempty"` // failure category (agent classifier vocabulary + not_writable/probe_io)
|
||||
Message string `json:"message,omitempty"` // the category's Hungarian customer message (§3.2)
|
||||
Detail string `json:"detail,omitempty"` // raw English/journal detail (the collapsible)
|
||||
Warn string `json:"warn,omitempty"` // non-fatal note on a successful add (e.g. probe cleanup)
|
||||
Path string `json:"path,omitempty"` // the registered in-guest path (done only)
|
||||
// server and mappedUID (R-516) parameterize the failure message, which the status handler renders
|
||||
// in the reader's language.
|
||||
server string
|
||||
mappedUID int
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -183,7 +188,8 @@ func (s *Server) runNetAdd(agent netAgent, req agentapi.AddNetStorageRequest, la
|
||||
fail := func(category, detail string) {
|
||||
job.Phase = netAddPhaseFailed
|
||||
job.Category = category
|
||||
job.Message = netAddMessage(category, req.Server, req.MappedUID)
|
||||
job.server, job.mappedUID = req.Server, req.MappedUID
|
||||
job.Message = s.netAddMessage(s.boxLang(), category, req.Server, req.MappedUID)
|
||||
job.Detail = detail
|
||||
job.UpdatedAt = time.Now().UTC()
|
||||
s.netAdd.set(job)
|
||||
@@ -321,41 +327,40 @@ func (s *Server) pollAgentVerify(ctx context.Context, agent netAgent, jobID stri
|
||||
}
|
||||
}
|
||||
|
||||
// netAddMessage maps a failure category to the EXACT Hungarian customer message (§3.2). server and
|
||||
// mappedUID parameterize the unreachable/not_writable texts (host id = mapped uid + 100000).
|
||||
func netAddMessage(category, server string, mappedUID int) string {
|
||||
// netAddMessage maps a failure category to the customer message (§3.2) in lang. server and mappedUID
|
||||
// parameterize the unreachable/not_writable texts (host id = mapped uid + 100000). R-516: the texts
|
||||
// live in the bundle (api.netstorage.add.*), in the te-form, in both languages.
|
||||
func (s *Server) netAddMessage(lang, category, server string, mappedUID int) string {
|
||||
switch category {
|
||||
case "unreachable":
|
||||
msg := "A szerver nem érhető el (" + server + "). Ellenőrizze az IP-címet, és hogy a NAS be van-e kapcsolva."
|
||||
msg := s.msgLang(lang, "api.netstorage.add.unreachable", server)
|
||||
// R-66 Leg C: a single-label non-IP server (»FELHOM«) is almost always a Windows/NetBIOS
|
||||
// network name, which this box generally cannot resolve — name the trap instead of letting
|
||||
// the generic text teach nothing. Purely lexical on the submitted value: NO NetBIOS/mDNS
|
||||
// resolution is ever attempted, and an IP or dotted DNS name never gets nagged about this.
|
||||
if looksLikeFlatNetworkName(server) {
|
||||
msg += " Tipp: a(z) »" + server + "« Windows-hálózati névnek tűnik — használja az eszköz IP-címét."
|
||||
msg += s.msgLang(lang, "api.netstorage.add.flat_name_tip", server)
|
||||
}
|
||||
return msg
|
||||
case "nfs_export":
|
||||
return "A megosztás nem található, vagy a NAS nem engedélyezi ennek a gépnek a hozzáférését. Ellenőrizze az export útvonalát, és hogy a NAS engedélyezi-e a Felhom gép IP-címét."
|
||||
case "smb_auth":
|
||||
return "Hibás SMB felhasználónév vagy jelszó."
|
||||
case "smb_share":
|
||||
return "A megadott SMB-megosztás nem található a szerveren. Ellenőrizze a megosztás nevét."
|
||||
case "timeout":
|
||||
return "Időtúllépés a csatolás közben — a szerver elérhető a hálózaton, de a megosztás nem csatolható. Ellenőrizze a NAS NFS/SMB szolgáltatását."
|
||||
case "nfs_export", "smb_auth", "smb_share", "timeout", "not_network_fs", "probe_io", "busy":
|
||||
return s.msgLang(lang, "api.netstorage.add."+category)
|
||||
case "not_writable":
|
||||
return "A megosztás csatolható, de az alkalmazások nem tudnak rá írni. NFS esetén kapcsolja be a NAS-on a „minden felhasználó leképezése” (map all users / all squash) beállítást a megosztáson — vagy állítsa a fájlok tulajdonosát a(z) " + fmt.Sprint(mappedUID+100000) + " azonosítóra. SMB esetén ellenőrizze, hogy a felhasználónak írási joga van a megosztáson."
|
||||
case "not_network_fs":
|
||||
return "A hálózati tárhely csatolása a rendszeren belül nem jött létre megfelelően. Próbálja újra a csatlakoztatást; ha a hiba ismétlődik, jelezze az üzemeltetőnek."
|
||||
case "probe_io":
|
||||
return "Írási hiba a megosztáson (az adat nem olvasható vissza hibátlanul). Ellenőrizze a megosztást és a hálózatot."
|
||||
case "busy":
|
||||
return "Már folyamatban van egy csatlakoztatás. Várja meg, amíg befejeződik."
|
||||
return s.msgLang(lang, "api.netstorage.add.not_writable", mappedUID+100000)
|
||||
default: // mount_failed + agent_error + register_failed + any future agent code
|
||||
return "A csatolás sikertelen. Részletek alább."
|
||||
return s.msgLang(lang, "api.netstorage.add.failed")
|
||||
}
|
||||
}
|
||||
|
||||
// netAddJobMessage renders a failed job's message in the READER's language (the job ran detached,
|
||||
// with no request; it keeps the category and its parameters). A job with no category passes its own
|
||||
// Message through.
|
||||
func (s *Server) netAddJobMessage(r *http.Request, job *netAddJob) string {
|
||||
if job.Category == "" {
|
||||
return job.Message
|
||||
}
|
||||
return s.netAddMessage(s.langFor(r), job.Category, job.server, job.mappedUID)
|
||||
}
|
||||
|
||||
// looksLikeFlatNetworkName reports whether a submitted server value is a single-label non-IP name
|
||||
// (no dot, not an IP literal) — the NetBIOS-name shape. `nas.local` (dotted) and any parseable IP
|
||||
// (v4 or v6 — colons carry the v6 case through ParseIP) are NOT flagged: the hint must never nag
|
||||
|
||||
@@ -176,7 +176,7 @@ func TestNetAdd_AgentVerifyFailed_MappedMessage(t *testing.T) {
|
||||
t.Fatalf("phase/category = %s/%s, want failed/nfs_export", job.Phase, job.Category)
|
||||
}
|
||||
// The EXACT §3.2 merged message (NFSv4 cannot distinguish not-found from not-permitted).
|
||||
want := "A megosztás nem található, vagy a NAS nem engedélyezi ennek a gépnek a hozzáférését. Ellenőrizze az export útvonalát, és hogy a NAS engedélyezi-e a Felhom gép IP-címét."
|
||||
want := "A megosztás nem található, vagy a NAS nem engedélyezi ennek a gépnek a hozzáférését. Ellenőrizd az export útvonalát, és hogy a NAS engedélyezi-e a Felhom gép IP-címét."
|
||||
if job.Message != want {
|
||||
t.Errorf("nfs_export message:\n got %q\nwant %q", job.Message, want)
|
||||
}
|
||||
|
||||
@@ -152,6 +152,8 @@ func TestDebugDumpNetworkSection(t *testing.T) {
|
||||
|
||||
// R-66 Leg C — the NetBIOS trap named on an unreachable-class add failure.
|
||||
func TestNetAddMessageNetBIOSHint(t *testing.T) {
|
||||
s := testServer(t)
|
||||
netAddMessage := func(c, srv string, uid int) string { return s.netAddMessage("hu", c, srv, uid) }
|
||||
const hintMark = "Windows-hálózati névnek tűnik"
|
||||
|
||||
// C1: single-label non-IP name + unreachable → hint present, naming the submitted value.
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
|
||||
)
|
||||
|
||||
var goStringLit = regexp.MustCompile(`"(?:[^"\\]|\\.)*"`)
|
||||
|
||||
// R-516 (2026-10-06 night) — the formal („ön") forms that lived in GO LITERALS, where the bundle gate
|
||||
// cannot read them: the escrow and share handlers, the export upload, the network-storage attach and
|
||||
// remove errors, the uid refusal. They are bundle keys now, te-form in Hungarian, with English.
|
||||
//
|
||||
// Left on purpose (named so the scan's exemptions are not a wish): internal/setup and
|
||||
// internal/recovery (the setup wizard and the household's recovery-info.txt — R-554, the operator's
|
||||
// call); internal/notify and internal/fillwatch (the SMART and fill-watch texts are the hub event's
|
||||
// MESSAGE — wire text the hub mails as is; converting them needs the household-copy producer shape).
|
||||
//
|
||||
// COMPANION RED-PROOF (observed): put back „próbálja újra" in share_handlers.go → the source scan fails
|
||||
// naming the file and the stem. Restored.
|
||||
func TestR516_GoLiteralFormalFormsAreGone(t *testing.T) {
|
||||
b, err := i18n.Shared()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := map[string]string{
|
||||
"api.escrow.too_many_attempts": "Túl sok sikertelen próbálkozás, próbáld újra 1 perc múlva.",
|
||||
"api.escrow.no_active_job": "Nincs aktív helyreállítási folyamat — előbb indítsd el a kódkészítést.",
|
||||
"api.share.form_invalid": "Érvénytelen űrlap — töltsd újra az oldalt.",
|
||||
"api.export.upload_size_mismatch": "A feltöltött méret nem egyezik — próbáld újra.",
|
||||
"api.netstorage.add.smb_share": "A megadott SMB-megosztás nem található a szerveren. Ellenőrizd a megosztás nevét.",
|
||||
"api.netstorage.add.busy": "Már folyamatban van egy csatlakoztatás. Várd meg, amíg befejeződik.",
|
||||
"api.netstorage.uid_range": "Az alkalmazás felhasználói azonosítója (uid) érvénytelen. Adj meg 1 és 65533 közötti értéket — a legtöbb médiaalkalmazás az 1000-est használja.",
|
||||
"api.netstorage.add.not_network_fs": "A hálózati tárhely csatolása a rendszeren belül nem jött létre megfelelően. Próbáld újra a csatlakoztatást; ha a hiba ismétlődik, jelezd az üzemeltetőnek.",
|
||||
}
|
||||
for key, w := range want {
|
||||
if got, fellBack, ok := b.Text("hu", key); !ok || fellBack || got != w {
|
||||
t.Errorf("hu %s: %q, want %q", key, got, w)
|
||||
}
|
||||
if _, fellBack, ok := b.Text("en", key); !ok || fellBack {
|
||||
t.Errorf("en %s: missing (fell back to Hungarian)", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Source scan over the Go literals this row converted. ASCII-folded, so an accent cannot hide one.
|
||||
fold := strings.NewReplacer("á", "a", "é", "e", "í", "i", "ó", "o", "ö", "o", "ő", "o", "ú", "u", "ü", "u", "ű", "u")
|
||||
stems := []string{"probalja", "toltse ujra", "allitson be", "varja meg", "inditsa", "ellenorizze",
|
||||
"hasznalja az eszkoz", "kapcsolja be", "allitsa a", "jelezze az", "tavolitsa el vagy", "adjon meg", "valasszon csatlakoztatott"}
|
||||
skip := map[string]bool{"setup": true, "recovery": true, "notify": true, "fillwatch": true}
|
||||
root := filepath.Join("..")
|
||||
scanned := 0
|
||||
err = filepath.Walk(root, func(p string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if skip[info.Name()] || info.Name() == "testdata" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !strings.HasSuffix(p, ".go") || strings.HasSuffix(p, "_test.go") {
|
||||
return nil
|
||||
}
|
||||
data, rerr := os.ReadFile(p)
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
scanned++
|
||||
for i, line := range strings.Split(string(data), "\n") {
|
||||
tl := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(tl, "//") || !strings.Contains(tl, `"`) {
|
||||
continue
|
||||
}
|
||||
// Only literals with a space are sentences; a bundle KEY (no space) may carry a stem in its name.
|
||||
var sentences []string
|
||||
for _, m := range goStringLit.FindAllString(tl, -1) {
|
||||
if strings.Contains(m, " ") {
|
||||
sentences = append(sentences, m)
|
||||
}
|
||||
}
|
||||
lv := fold.Replace(strings.ToLower(strings.Join(sentences, " ")))
|
||||
for _, st := range stems {
|
||||
if strings.Contains(lv, st) {
|
||||
t.Errorf("%s:%d still carries the formal %q: %s", p, i+1, st, tl)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if scanned < 50 {
|
||||
t.Fatalf("the scan read only %d Go files — it judged nothing", scanned)
|
||||
}
|
||||
// Positive control: the fold turns a formal literal into a matching stem.
|
||||
if lv := fold.Replace(strings.ToLower(`"Próbálja újra."`)); !strings.Contains(lv, "probalja") {
|
||||
t.Fatal("the ASCII fold does not work — the scan proves nothing")
|
||||
}
|
||||
}
|
||||
|
||||
// A detached attach job's failure is rendered in the READER's language, not frozen in the language of
|
||||
// the moment it failed.
|
||||
//
|
||||
// COMPANION RED-PROOF (observed): make handleNetStorageAddStatus return job.Message again → the English
|
||||
// reader gets the Hungarian sentence and this fails. Restored.
|
||||
func TestR516_NetAddFailureFollowsTheReader(t *testing.T) {
|
||||
s := testServer(t)
|
||||
job := &netAddJob{Name: "media", Phase: netAddPhaseFailed, Category: "unreachable", server: "FELHOM", mappedUID: 1000}
|
||||
job.Message = s.netAddMessage("hu", job.Category, job.server, job.mappedUID)
|
||||
s.netAdd.set(job)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
s.handleNetStorageAddStatus(rr, httptest.NewRequest("GET", "/api/storage/network/add/status?lang=en", nil))
|
||||
body := rr.Body.String()
|
||||
if !strings.Contains(body, "The server cannot be reached (FELHOM)") || !strings.Contains(body, "looks like a Windows network name") {
|
||||
t.Fatalf("an English reader must get the English failure with its tip: %s", body)
|
||||
}
|
||||
rr = httptest.NewRecorder()
|
||||
s.handleNetStorageAddStatus(rr, httptest.NewRequest("GET", "/api/storage/network/add/status?lang=hu", nil))
|
||||
if !strings.Contains(rr.Body.String(), "Ellenőrizd az IP-címet") {
|
||||
t.Fatalf("a Hungarian reader must get the te-form sentence: %s", rr.Body.String())
|
||||
}
|
||||
}
|
||||
@@ -110,13 +110,13 @@ func (s *Server) shareGuestPasswordHandler(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
_ = r.ParseForm()
|
||||
if !s.validShareCSRF(r) {
|
||||
s.renderSharePasswordPage(w, r, "Érvénytelen űrlap — töltse újra az oldalt.")
|
||||
s.renderSharePasswordPage(w, r, s.msg(r, "api.share.form_invalid"))
|
||||
return
|
||||
}
|
||||
ip := rateKey(r)
|
||||
if s.shareRateLimited(ip) {
|
||||
s.logger.Printf("[WARN] [web] share password rate limited for %s", ip)
|
||||
s.renderSharePasswordPage(w, r, "Túl sok sikertelen próbálkozás, próbálja újra 1 perc múlva")
|
||||
s.renderSharePasswordPage(w, r, s.msg(r, "api.share.too_many_attempts"))
|
||||
return
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(pwHash), []byte(r.FormValue("password"))) != nil {
|
||||
|
||||
@@ -188,7 +188,29 @@
|
||||
"appstop.reason.app_export": "BORN AS A KEY (R-585) -- the operation's name inside the interrupted-operation alert, rendered in the sentence's own language; a NEW phrase, never a Go literal. Pinned in both languages by TestR585_InterruptedOperationFollowsTheHousehold.",
|
||||
"flash.offbox.mgr_unavailable": "R-256 -- REWORDED on purpose: the old „A mentéskezelő nem …\" named an internal component and no route; it now says what is wrong in plain words and where to go (try again, then support), so byte parity with the base literal cannot hold. Pinned in both languages by TestR256_R257_OffboxRefusalsNameARoute.",
|
||||
"flash.offbox.mgr_unreachable": "R-256 -- REWORDED on purpose: the old „A mentéskezelő nem …\" named an internal component and no route; it now says what is wrong in plain words and where to go (try again, then support), so byte parity with the base literal cannot hold. Pinned in both languages by TestR256_R257_OffboxRefusalsNameARoute.",
|
||||
"flash.offbox.not_orphaned": "R-257 -- REWORDED on purpose: the old sentence put an English loanword and the internal state name „elárvult\" in front of the household; it now says why the action does not apply and where to go, so byte parity with the base literal cannot hold. Pinned in both languages by TestR256_R257_OffboxRefusalsNameARoute."
|
||||
"flash.offbox.not_orphaned": "R-257 -- REWORDED on purpose: the old sentence put an English loanword and the internal state name „elárvult\" in front of the household; it now says why the action does not apply and where to go, so byte parity with the base literal cannot hold. Pinned in both languages by TestR256_R257_OffboxRefusalsNameARoute.",
|
||||
"api.escrow.dashboard_password_missing": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.escrow.too_many_attempts": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.escrow.stage_failed": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.escrow.already_running": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.escrow.code_not_shown": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.escrow.no_active_job": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.share.form_invalid": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.share.too_many_attempts": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.export.upload_size_mismatch": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.uid_range": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.remove_in_use": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.unreachable": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.flat_name_tip": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.nfs_export": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.smb_auth": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.smb_share": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.timeout": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.not_writable": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.not_network_fs": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.probe_io": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.busy": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone.",
|
||||
"api.netstorage.add.failed": "R-516 (2026-10-06 night) -- REWORDED on purpose: a Go literal in the formal („ön\") form (escrow/share/export-upload/network-storage handlers) moved into the bundle in the product's te-form, with English; the row is about exactly these bytes, so byte parity with the base literal cannot hold. Pinned by TestR516_GoLiteralFormalFormsAreGone."
|
||||
},
|
||||
"flash.share.already_on": "A megosztás már be van kapcsolva.",
|
||||
"flash.share.enable_failed": "A megosztás bekapcsolása nem sikerült.",
|
||||
|
||||
Reference in New Issue
Block a user