{"record":{"id":"7e447f665aab8500","repo":"OpenNHP/opennhp","slug":"s-no-servers-configured","errorCode":null,"errorMessage":"%s: no [[Servers]] configured","messagePattern":"(.+?): no \\[\\[Servers\\]\\] configured","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"nhp/common/clusterconfig/clusterconfig.go","lineNumber":140,"sourceCode":"// auto-upgrade in-place. After it returns, every cluster has at least\n// one Instances entry and a normalised LoadBalance. Returned errors are\n// surfaced to the operator at load time; warnings (deprecation,\n// recoverable inconsistencies) go through deprecate.\n//\n// deprecate is a callback so callers control how the warning is\n// surfaced — production code passes log.Warning; tests can capture\n// invocations for assertions. A nil deprecate is treated as no-op.\nfunc Normalize(clusters []*ClusterConfig, opts Options, deprecate func(string, ...any)) error {\n\tif deprecate == nil {\n\t\tdeprecate = func(string, ...any) {}\n\t}\n\tlabel := opts.ConsumerLabel\n\tif label == \"\" {\n\t\tlabel = \"cluster\"\n\t}\n\n\tif len(clusters) == 0 {\n\t\treturn fmt.Errorf(\"%s: no [[Servers]] configured\", label)\n\t}\n\tfor i, c := range clusters {\n\t\tif c == nil {\n\t\t\treturn fmt.Errorf(\"%s: [[Servers]][%d] is nil\", label, i)\n\t\t}\n\t\tif c.PubKeyBase64 == \"\" {\n\t\t\treturn fmt.Errorf(\"%s: [[Servers]][%d] missing PubKeyBase64\", label, i)\n\t\t}\n\t\tif opts.RequireName {\n\t\t\tif c.Name == \"\" {\n\t\t\t\treturn fmt.Errorf(\"%s: [[Servers]][%d] (%s) missing Name — clusters are referenced from resource.toml by Name\",\n\t\t\t\t\tlabel, i, c.PubKeyBase64)\n\t\t\t}\n\t\t\tif len(c.Name) > NameMaxLen {\n\t\t\t\treturn fmt.Errorf(\"%s: [[Servers]][%d] Name %q exceeds %d chars\",\n\t\t\t\t\tlabel, i, c.Name, NameMaxLen)\n\t\t\t}\n\t\t\tif !clusterNameRegex.MatchString(c.Name) {","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/nhp/common/clusterconfig/clusterconfig.go#L122-L158","documentation":"clusterconfig.Normalize validates a cluster list and rejects an empty slice outright — at least one [[Servers]] entry is mandatory. The label prefix (consumer label or \"cluster\") identifies which config section failed validation.","triggerScenarios":"Calling Normalize with clusters == nil or len(clusters) == 0, typically from normalizeAndExpand/normalizeClusters/updateServerPeers when a TOML file contained no [[Servers]] array entries.","commonSituations":"config/server.toml with the [[Servers]] table commented out or deleted; a template rendering that dropped all server entries; parsing a file with only top-level keys and no clusters array.","solutions":["Add at least one [[Servers]] block with PubKeyBase64 to the config file","Re-run the deploy/config template rendering so [[Servers]] entries are emitted","Check that the TOML is being read from the expected path (an empty/missing file yields zero clusters)","Fix indentation/table syntax so the [[Servers]] entries actually parse as array-of-tables"],"exampleFix":"// before (server.toml)\n# [[Servers]]\n# PubKeyBase64 = \"...\"\n// after\n[[Servers]]\nName = \"nhp-server-1\"\nPubKeyBase64 = \"<base64 server public key>\"","handlingStrategy":"validation","validationCode":"var clusters []clusterconfig.ClusterConfig\nif err := toml.Unmarshal(data, &clusters); err != nil { return err }\nif len(clusters) == 0 {\n    return errors.New(\"config must define at least one [[Servers]] block\")\n}\nerr := clusterconfig.Normalize(clusters, opts)","typeGuard":null,"tryCatchPattern":"if err := clusterconfig.Normalize(clusters, opts); err != nil {\n    return fmt.Errorf(\"cluster config invalid: %w\", err)\n}","preventionTips":["Never deploy a rendered config without checking that [[Servers]] blocks exist","Keep a default non-empty server.toml in the image","Assert the config template renders at least one cluster in CI"],"tags":["config","toml","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}