hub v0.13.0: DR recipe — assemble + store + view the secret-free reconstruction recipe
DR recipe slice (hub half), grounded in SPIKE-dr-recipe-2026-06-16. The hub
receives two additive dr_recipe halves on the existing report paths (agent
storage/guest/PBS on host-report; controller customer/apps on the controller
report), stores them PLAINTEXT in a DEDICATED dr_recipe table keyed by customer
(each half preserves the other), and AssembleDRRecipe stitches them into one
operator-readable recipe (ignore-unknown + version-skew tolerant).
View: a DR-recipe panel on the customer page + GET /customers/{id}/dr-recipe.json
download (operator-auth, no secrets to redact). Plaintext-at-rest is correct —
the recipe is the clean inverse of the retired infra-backup.
Tests: store round-trip (each half preserves the other), assemble-matches-golden,
ignore-unknown + version skew, partial halves, no-secrets sweep. Manifest tag
bumped to v0.13.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -254,12 +254,28 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
|
||||
AppTelemetry []store.CustomerAppSummary
|
||||
HasAppTelemetry bool
|
||||
|
||||
HasDRRecipe bool
|
||||
DRRecipeUpdatedAt string
|
||||
DRRecipeHasHost bool
|
||||
DRRecipeHasApps bool
|
||||
|
||||
Flash string
|
||||
ActiveNav string
|
||||
CSRFField template.HTML
|
||||
CSRFToken string
|
||||
}
|
||||
|
||||
// DR recipe presence — show the secret-free reconstruction recipe panel + download link when
|
||||
// either half has landed (host-report and/or controller report).
|
||||
var hasDR, drHost, drApps bool
|
||||
var drUpdated string
|
||||
if rec, err := s.store.GetDRRecipe(customerID); err == nil && rec != nil {
|
||||
hasDR = rec.HostHalfJSON != "" || rec.AppHalfJSON != ""
|
||||
drHost = rec.HostHalfJSON != ""
|
||||
drApps = rec.AppHalfJSON != ""
|
||||
drUpdated = rec.UpdatedAt
|
||||
}
|
||||
|
||||
data := pageData{
|
||||
CustomerID: customerID,
|
||||
CustomerName: name,
|
||||
@@ -293,6 +309,11 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
|
||||
AppTelemetry: appTelemetry,
|
||||
HasAppTelemetry: len(appTelemetry) > 0,
|
||||
|
||||
HasDRRecipe: hasDR,
|
||||
DRRecipeUpdatedAt: drUpdated,
|
||||
DRRecipeHasHost: drHost,
|
||||
DRRecipeHasApps: drApps,
|
||||
|
||||
Flash: r.URL.Query().Get("flash"),
|
||||
ActiveNav: "configs",
|
||||
CSRFField: s.csrfField(r),
|
||||
@@ -741,10 +762,10 @@ func flattenYAML(m map[string]interface{}, prefix string) map[string]string {
|
||||
|
||||
// configDiff represents a single key-value difference between two configs.
|
||||
type configDiff struct {
|
||||
Key string `json:"key"`
|
||||
HubValue string `json:"hub"`
|
||||
CtrlValue string `json:"controller"`
|
||||
Status string `json:"status"` // "changed", "hub_only", "controller_only"
|
||||
Key string `json:"key"`
|
||||
HubValue string `json:"hub"`
|
||||
CtrlValue string `json:"controller"`
|
||||
Status string `json:"status"` // "changed", "hub_only", "controller_only"
|
||||
}
|
||||
|
||||
// compareYAMLValues parses two YAML strings and returns their value differences.
|
||||
|
||||
Reference in New Issue
Block a user