package api import ( "encoding/json" "fmt" ) // R-1192 follow-up (night 2026-10-21). The guest swap fix (agent: every guest may swap as much as its RAM) moves a // memory burst onto the HOST's swap. Our installer gives an 8 GB machine 7.75 GB of it (measured on demo-hp); a host // with none — a BYO install, or swap turned off — gets nothing from the fix, and the household's first big upload can // freeze the box again. The agent reports both numbers; the hub tells the OPERATOR once per host when the host's swap // is below its guests' allowance (host_swap_short), and once when it is covered again (host_swap_ok). Edge-triggered // from the host's own newest swap event, so a hub restart does not mail again. Operator-only (notify.operatorOnlyEvents): // the household cannot add swap to its host. const ( eventHostSwapShort = "host_swap_short" eventHostSwapOK = "host_swap_ok" ) func (h *Handler) checkHostSwap(hostID, custID string, hostMB, guestMB *int64) { if hostMB == nil || guestMB == nil || custID == "" { return // an older agent: not evaluated } short := *guestMB > 0 && *hostMB < *guestMB prev, err := h.store.LatestHostSwapEvent(custID, hostID) if err != nil { h.logger.Printf("[WARN] host %s swap check: reading the last swap event failed: %v", hostID, err) return } wasShort := prev != nil && prev.EventType == eventHostSwapShort var typ, sev, msg string switch { case short && !wasShort: typ, sev = eventHostSwapShort, "warning" msg = fmt.Sprintf("Host %s: its swap (%d MB) is below its guests' swap allowance (%d MB). The guest swap fix needs swap on the host behind it; "+ "without it a memory burst — a big first upload on an 8 GB box — can freeze the box for minutes (R-1192). Add swap on the host "+ "(our installer gives an 8 GB machine 7.75 GB) or lower the guest's RAM. The household cannot fix this.", hostID, *hostMB, *guestMB) case !short && wasShort: typ, sev = eventHostSwapOK, "info" msg = fmt.Sprintf("Host %s: its swap (%d MB) covers its guests' swap allowance (%d MB) again.", hostID, *hostMB, *guestMB) default: return } h.logger.Printf("[WARN] host %s: %s (host swap %d MB, guests' swap %d MB)", hostID, typ, *hostMB, *guestMB) details, _ := json.Marshal(map[string]any{"host_id": hostID, "host_swap_mb": *hostMB, "guest_swap_mb": *guestMB}) if _, eerr := h.store.SaveEvent(custID, typ, sev, msg, string(details), "hub"); eerr != nil { h.logger.Printf("[WARN] %s event save FAILED for %s: %v", typ, hostID, eerr) return // the audit row first: a mail without its event row lies } if h.dispatcher != nil { go h.dispatcher.ProcessEvent(custID, typ, sev, msg, string(details), "hub") } }