70 lines
3.9 KiB
Go
70 lines
3.9 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
|
)
|
|
|
|
// R-1176 (operator ruling 2026-10-21): the hub re-attaches a household's sealed package to its rebuilt box. The
|
|
// store does the move (store.ReattachRetainedEscrow — conditions and reasons there); this half records it.
|
|
//
|
|
// Called on BOTH report paths, before anything reads the escrow: the agent's host report (the first thing a new
|
|
// box sends after enrolling, long before its controller exists) and the controller's report (before the ACK's
|
|
// escrow status is built, so the very first ACK already says „identity present" and the box never mints a fresh
|
|
// repository password). Idempotent and cheap: one query once the box holds a package.
|
|
//
|
|
// The audit names host ids and the non-reversible hash only — never the blob (03 §8a: the hub never opens, re-seals
|
|
// or logs the package).
|
|
const (
|
|
eventEscrowCarried = "escrow_carried"
|
|
eventEscrowCarryRefused = "escrow_carry_refused"
|
|
escrowCarryHashLogPrefix = 12
|
|
)
|
|
|
|
func (h *Handler) carryEscrow(customerID string) {
|
|
res, err := h.store.ReattachRetainedEscrow(customerID)
|
|
if err != nil {
|
|
h.logger.Printf("[ERROR] escrow carry for %s failed (the rebuilt box sees no package until this succeeds; retried on the next report): %v", customerID, err)
|
|
return
|
|
}
|
|
switch res.Outcome {
|
|
case store.CarryDone:
|
|
short := res.ResticPwSHA256
|
|
if len(short) > escrowCarryHashLogPrefix {
|
|
short = short[:escrowCarryHashLogPrefix]
|
|
}
|
|
h.logger.Printf("[WARN] escrow CARRIED for %s: the sealed package of deleted host %s is now the current package of %s (repo-key hash %s…); the box will ask for the recovery code",
|
|
customerID, res.FromHost, res.ToHost, short)
|
|
msg := fmt.Sprintf("Recovery package carried: the sealed package of the deleted host %s now belongs to the household's new host %s. "+
|
|
"The box will offer the household its earlier off-site backups and ask for the recovery code. The hub did not open the package.",
|
|
res.FromHost, res.ToHost)
|
|
details, _ := json.Marshal(map[string]any{"from_host": res.FromHost, "to_host": res.ToHost, "restic_pw_sha256": res.ResticPwSHA256})
|
|
if _, eerr := h.store.SaveEvent(customerID, eventEscrowCarried, "info", msg, string(details), "hub"); eerr != nil {
|
|
h.logger.Printf("[WARN] %s event save failed for %s: %v", eventEscrowCarried, customerID, eerr)
|
|
}
|
|
case store.CarryRefusedOtherLiveHost:
|
|
// Once per deleted host: the report repeats every few minutes, the operator needs one mail.
|
|
if prev, perr := h.store.GetLatestEventByType(customerID, eventEscrowCarryRefused); perr == nil && prev != nil &&
|
|
strings.Contains(prev.DetailsJSON, `"from_host":"`+res.FromHost+`"`) {
|
|
return
|
|
}
|
|
live := strings.Join(res.LiveHosts, ", ")
|
|
h.logger.Printf("[WARN] escrow carry REFUSED for %s: the package of deleted host %s waits, but the customer has %d live hosts (%s) — the operator must delete the one that is not the household's",
|
|
customerID, res.FromHost, len(res.LiveHosts), live)
|
|
msg := fmt.Sprintf("Recovery package NOT carried: customer %s has %d live hosts (%s), so the hub cannot tell which box is the household's. "+
|
|
"The sealed package of the deleted host %s is kept. Delete the host that is not in use on the Hosts page; the package then goes to the remaining box on its next report.",
|
|
customerID, len(res.LiveHosts), live, res.FromHost)
|
|
details, _ := json.Marshal(map[string]any{"from_host": res.FromHost, "live_hosts": res.LiveHosts})
|
|
if _, eerr := h.store.SaveEvent(customerID, eventEscrowCarryRefused, "warning", msg, string(details), "hub"); eerr != nil {
|
|
h.logger.Printf("[WARN] %s event save failed for %s: %v", eventEscrowCarryRefused, customerID, eerr)
|
|
return // audit row first: a mail without its event row lies
|
|
}
|
|
if h.dispatcher != nil {
|
|
go h.dispatcher.ProcessEvent(customerID, eventEscrowCarryRefused, "warning", msg, string(details), "hub")
|
|
}
|
|
}
|
|
}
|