hub v0.41.0: OffsiteChecker (fill 90/95 + staleness >48h) + operator freeze lever (SLICE 4)
Sibling checker over the controller report's offsite object: quota-fill warn/crit + the silently-stuck staleness detector (escrowed-only, red-proofed; nil-safe on pre-v0.109 reports; same-second tie-guard). SetOffsiteFrozen flips ONLY readonly on the exactly-1 labelled sub-account (SSH preserved); Freeze/Unfreeze buttons — manual only, never automatic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -581,6 +581,46 @@ func (s *Server) handleOffsiteReissue(w http.ResponseWriter, r *http.Request, cu
|
||||
http.Redirect(w, r, "/customers/"+customerID+"?flash=offsite_reissued", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// handleOffsiteFreeze (SLICE 4) freezes/unfreezes the customer's shared sub-account (readonly) — an
|
||||
// OPERATOR lever, never automatic (freezing also blocks prune, the customer's only way down from
|
||||
// over-quota). Shared model only; the exactly-1 label guard lives in the provisioner. Action logged,
|
||||
// no secrets involved.
|
||||
func (s *Server) handleOffsiteFreeze(w http.ResponseWriter, r *http.Request, customerID string, frozen bool) {
|
||||
if s.offsite == nil {
|
||||
http.Error(w, "Offsite provisioning is not configured on this hub", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
cfg, err := s.store.GetCustomerConfig(customerID)
|
||||
if err != nil || cfg == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
var overrides struct {
|
||||
Offsite struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Type string `json:"type"`
|
||||
} `json:"offsite"`
|
||||
}
|
||||
_ = json.Unmarshal([]byte(cfg.ConfigJSON), &overrides)
|
||||
if !overrides.Offsite.Enabled || overrides.Offsite.Type != "shared" {
|
||||
http.Error(w, "Freeze applies to a provisioned SHARED offsite tier only", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), 2*time.Minute)
|
||||
defer cancel()
|
||||
if err := s.offsite.SetOffsiteFrozen(ctx, customerID, frozen); err != nil {
|
||||
s.logger.Printf("[ERROR] offsite freeze(%v) for %s: %v", frozen, customerID, err)
|
||||
http.Error(w, "Offsite freeze/unfreeze failed: "+err.Error(), http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
s.logger.Printf("[INFO] offsite frozen=%v (readonly) for %s (operator action)", frozen, customerID)
|
||||
flash := "offsite_frozen"
|
||||
if !frozen {
|
||||
flash = "offsite_unfrozen"
|
||||
}
|
||||
http.Redirect(w, r, "/customers/"+customerID+"?flash="+flash, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// handleConfigDelete deletes a customer config.
|
||||
func (s *Server) handleConfigDelete(w http.ResponseWriter, r *http.Request, customerID string) {
|
||||
if err := s.store.DeleteCustomerConfig(customerID); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user