{"record":{"id":"9051091a3a1d2c42","repo":"OpenNHP/opennhp","slug":"cluster-q-missing-publickeybase64","errorCode":null,"errorMessage":"cluster %q: missing publicKeyBase64","messagePattern":"cluster %q: missing publicKeyBase64","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/agent/cluster.go","lineNumber":133,"sourceCode":"func (sc *ServerCluster) FindInstanceByAddr(addr string) *ServerInstance {\n\tif sc == nil {\n\t\treturn nil\n\t}\n\tfor _, inst := range sc.instances {\n\t\tif inst.hostPort == addr {\n\t\t\treturn inst\n\t\t}\n\t}\n\treturn nil\n}\n\n// buildCluster turns a parsed ClusterConfig into a runtime cluster.\n// The returned cluster's representativePeer is NOT yet registered on a\n// device — callers (updateServerPeers) are responsible for that, so\n// they can also handle peer removal on reload.\nfunc buildCluster(cfg *ClusterConfig) (*ServerCluster, error) {\n\tif cfg.PubKeyBase64 == \"\" {\n\t\treturn nil, fmt.Errorf(\"cluster %q: missing publicKeyBase64\", cfg.Name)\n\t}\n\tif len(cfg.Instances) == 0 {\n\t\treturn nil, fmt.Errorf(\"cluster %q (%s): no instances configured\",\n\t\t\tcfg.Name, cfg.PubKeyBase64)\n\t}\n\tif err := cfg.LoadBalance.Validate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"cluster %q (%s): %w\",\n\t\t\tcfg.Name, cfg.PubKeyBase64, err)\n\t}\n\n\tsc := &ServerCluster{\n\t\tPublicKeyBase64: cfg.PubKeyBase64,\n\t\tName:            cfg.Name,\n\t\tSticky:          cfg.StickyOrDefault(),\n\t\tinstances:       make([]*ServerInstance, 0, len(cfg.Instances)),\n\t}\n\n\tfor i, ic := range cfg.Instances {","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/agent/cluster.go#L115-L151","documentation":"buildCluster in endpoints/agent/cluster.go rejects a ClusterConfig whose PubKeyBase64 is empty with \"cluster %q: missing publicKeyBase64\". Each server cluster needs the shared server public key to encrypt knock packets; without it the cluster cannot be constructed. Called from updateServerPeers during config load/reload.","triggerScenarios":"A clusters entry in agent config omits publicKeyBase64 (or names a different field, leaving PubKeyBase64 unset) when updateServerPeers parses and builds clusters at startup or on config reload.","commonSituations":"Hand-writing clusters in agent.toml/config.json and forgetting the key; template rendering with an unset env var (e.g. nhp_server_public_key missing from secrets) leaving the field blank; renaming fields across versions.","solutions":["Add the server's base64 public key to every cluster entry in the agent config","Re-run key generation/deploy rendering (scripts/generate-nhp-keys.sh) if the key field came up empty from a template","Add startup validation of cluster configs before use, listing which cluster is missing the key","Check for field-name typos (publicKeyBase64 vs pubkey) against the ClusterConfig struct"],"exampleFix":"// before\n[[clusters]]\nname = \"nhp-server\"\ninstances = [\"udp://10.0.0.5:5555\"]\n// after\n[[clusters]]\nname = \"nhp-server\"\npublicKeyBase64 = \"<nhp_server_public_key>\"\ninstances = [\"udp://10.0.0.5:5555\"]","handlingStrategy":"validation","validationCode":"for _, c := range clusterConfigs {\n\tif c.PubKeyBase64 == \"\" {\n\t\treturn fmt.Errorf(\"cluster %q: publicKeyBase64 required\", c.Name)\n\t}\n\tif _, err := base64.StdEncoding.DecodeString(c.PubKeyBase64); err != nil { return err }\n}","typeGuard":null,"tryCatchPattern":"cl, err := buildCluster(cfg)\nif err != nil {\n\treturn fmt.Errorf(\"skipping cluster: %w\", err) // or fail startup\n}","preventionTips":["Render cluster configs from templates so keys are never blank","Decode base64 fields at parse time to catch empties early","Keep field names in templates in sync with ClusterConfig struct tags"],"tags":["config","agent","cluster","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"}