faef8f8a91
Operator ruling 2026-10-12 (S27, 09 §3 decision 222). The operator approves the Hungarian text before release. A box whose first ingestion failed now serves one bilingual waiting page and retries every minute instead of the wizard. Own commit so the lead can hold it back. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
78 lines
2.4 KiB
Go
78 lines
2.4 KiB
Go
package recovery
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
|
|
)
|
|
|
|
// Info holds the data needed for the recovery info file.
|
|
type Info struct {
|
|
CustomerID string
|
|
RetrievalPassword string
|
|
HubURL string
|
|
SupportEmail string
|
|
SupportURL string
|
|
// DashboardURL is the household's dashboard, e.g. https://felhom.<domain> (R-554, v0.308.0): the file
|
|
// points at its Restore page and its „Adatok visszaszerzése" page, not at the deleted setup wizard.
|
|
DashboardURL string
|
|
// Lang is the box's language; the file is written in it (Hungarian when empty or unknown).
|
|
Lang string
|
|
}
|
|
|
|
// recoveryInfoKey is the file's text in the bundle (hu.json / en.json), with ten %s, in order:
|
|
// customer id, hub URL, retrieval password, support e-mail, support web, dashboard, restore page,
|
|
// recovery page, generated-at, controller version.
|
|
const recoveryInfoKey = "recovery_info.body"
|
|
|
|
// Render returns the text of recovery-info.txt (R-554, `09` §3 decision 222): what to do when something must
|
|
// be restored — the dashboard's Backup → Restore page, and after a reinstall the recovery code on the
|
|
// „Adatok visszaszerzése" page. Pinned by TestRecoveryInfo_PointsAtDashboardNotWizard.
|
|
func Render(info Info, version string, now time.Time) (string, error) {
|
|
b, err := i18n.Shared()
|
|
if err != nil {
|
|
return "", fmt.Errorf("loading message bundle: %w", err)
|
|
}
|
|
dash := strings.TrimRight(info.DashboardURL, "/")
|
|
return b.Msgf(i18n.Normalize(info.Lang), recoveryInfoKey,
|
|
info.CustomerID,
|
|
info.HubURL,
|
|
info.RetrievalPassword,
|
|
info.SupportEmail,
|
|
info.SupportURL,
|
|
dash,
|
|
dash+"/backups/restore",
|
|
dash+"/recovery",
|
|
now.Format("2006-01-02 15:04:05"),
|
|
version,
|
|
), nil
|
|
}
|
|
|
|
// GenerateRecoveryFile writes a plain text recovery-info.txt to the given directory (0600: it holds the
|
|
// retrieval password).
|
|
func GenerateRecoveryFile(info Info, version, outputDir string) error {
|
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
|
return fmt.Errorf("creating recovery info directory: %w", err)
|
|
}
|
|
content, err := Render(info, version, time.Now())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
path := filepath.Join(outputDir, "recovery-info.txt")
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, []byte(content), 0600); err != nil {
|
|
os.Remove(tmp)
|
|
return err
|
|
}
|
|
if err := os.Rename(tmp, path); err != nil {
|
|
os.Remove(tmp)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|