hub v0.51.0: DR-tier-by-default — per-customer dr_tier flag (default ON, legacy backfill from reality), cascade stages, WG-registration auto-provision hook, offsite-requires-DR guard (F-6 policy), host-page capability chips (inactive=neutral)
Claude-Session: https://claude.ai/code/session_01NptTCFtu7dz2Ru89qHRagN
This commit is contained in:
@@ -473,13 +473,15 @@ func (s *Server) configFormData(r *http.Request, isNew bool, cfg *store.Customer
|
||||
ActiveNav: "configs",
|
||||
Error: errMsg,
|
||||
CSRFField: s.csrfField(r),
|
||||
PBSDR: s.pbsDRViewFor(cfg.CustomerID),
|
||||
PBSDR: s.pbsDRViewFor(cfg.CustomerID, cfg.DRTier),
|
||||
}
|
||||
}
|
||||
|
||||
// handleConfigNewForm shows the form to create a new customer config.
|
||||
// handleConfigNewForm shows the form to create a new customer config. DRTier starts ON —
|
||||
// DR-tier-by-default is the new-customer default (operator decision 2026-07-12 #2); opting out
|
||||
// is the per-customer exception.
|
||||
func (s *Server) handleConfigNewForm(w http.ResponseWriter, r *http.Request) {
|
||||
s.renderConfigForm(w, r, true, &store.CustomerConfig{}, nil, "")
|
||||
s.renderConfigForm(w, r, true, &store.CustomerConfig{DRTier: true}, nil, "")
|
||||
}
|
||||
|
||||
// handleConfigCreate processes the form submission to create a new config.
|
||||
@@ -534,6 +536,9 @@ func (s *Server) handleConfigCreate(w http.ResponseWriter, r *http.Request) {
|
||||
RetrievalPassword: retrievalPassword,
|
||||
APIKey: apiKey,
|
||||
ConfigJSON: configJSON,
|
||||
// v0.51.0: the DR-tier flag (the form checkbox defaults ON for new customers). Set
|
||||
// BEFORE applyOffsite — offsite provisioning is refused without the DR tier (F-6).
|
||||
DRTier: formBool(r, "dr_tier"),
|
||||
}
|
||||
|
||||
// Offsite provisioning (fail-closed): a provisioning error must NOT save a half-enabled config.
|
||||
@@ -588,6 +593,9 @@ func (s *Server) handleConfigUpdate(w http.ResponseWriter, r *http.Request, cust
|
||||
cfg.CustomerName = strings.TrimSpace(r.FormValue("customer_name"))
|
||||
cfg.Domain = strings.TrimSpace(r.FormValue("domain"))
|
||||
cfg.Email = strings.TrimSpace(r.FormValue("email"))
|
||||
// v0.51.0: the DR-tier flag — set BEFORE applyOffsite (offsite requires the tier) and
|
||||
// applyPBSDR (which converges the descriptor toward it).
|
||||
cfg.DRTier = formBool(r, "dr_tier")
|
||||
|
||||
// Server-side twin of the form's required attributes (v0.48.0 — B3). The error re-render is
|
||||
// the STANDALONE page and carries the SUBMITTED overrides, so nothing the operator typed is
|
||||
@@ -1040,6 +1048,13 @@ func (s *Server) applyOffsite(ctx context.Context, r *http.Request, cfg *store.C
|
||||
if v := r.FormValue("offsite_enabled"); v != "on" && v != "true" {
|
||||
return nil // not enabled → disabled by omission
|
||||
}
|
||||
// v0.51.0 (decision 3, drill F-6 closed by policy): offsite app backup REQUIRES the DR tier —
|
||||
// fork-4 needs the escrow ceremony, and the ceremony hard-requires the PBS key K. Without the
|
||||
// tier, provisioning would run straight into the F-6 dead end (EscrowState pending forever).
|
||||
// The guard reads cfg.DRTier, which the handlers set from the form BEFORE calling here.
|
||||
if !cfg.DRTier {
|
||||
return fmt.Errorf("Offsite backup requires the DR tier — enable it first (the escrow ceremony depends on the PBS key)")
|
||||
}
|
||||
if s.offsite == nil {
|
||||
return fmt.Errorf("offsite provisioning is not configured on this hub (no Hetzner token)")
|
||||
}
|
||||
@@ -1110,6 +1125,13 @@ func buildConfigJSON(r *http.Request) string {
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// formBool reads a checkbox-style form value ("on"/"true" → true; anything else, incl. absent,
|
||||
// → false).
|
||||
func formBool(r *http.Request, name string) bool {
|
||||
v := r.FormValue(name)
|
||||
return v == "on" || v == "true"
|
||||
}
|
||||
|
||||
// handleGeoDisable removes all [felhom-geo] WAF rules from Cloudflare for a customer. The Cloudflare
|
||||
// WAF rules ARE the geo enforcement, so removing them disables geo-restriction. This is a hub→Cloudflare
|
||||
// call (NOT into the box) and stays. The old secondary inbound notify to the controller
|
||||
|
||||
Reference in New Issue
Block a user