{"record":{"id":"0e53aa571700eba3","repo":"OpenNHP/opennhp","slug":"relay-server-d-missing-publickeybase64","errorCode":null,"errorMessage":"relay: server #%d missing publicKeyBase64","messagePattern":"relay: server #(.+?) missing publicKeyBase64","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/relay/config.go","lineNumber":229,"sourceCode":"\n\tseenFP := make(map[string]int, len(cfg.Servers))\n\t// seenAddr catches a server+instance pair duplicated under the SAME\n\t// pubkey — the \"operator copied a [[Servers]] block and forgot to\n\t// change the instance\" mistake. The dedupe key is (fingerprint, addr),\n\t// NOT addr alone: resolveTarget routes by PeerPk, so two DISTINCT\n\t// pubkeys legitimately sharing one host:port (a SNI/header-routed\n\t// front-end, or port-multiplexed identities) is a valid topology and\n\t// must not be a hard config-load failure. Only same-pubkey + same-addr\n\t// is the unambiguous copy-paste error.\n\ttype addrOrigin struct {\n\t\tserver   int\n\t\tinstance int\n\t}\n\tseenAddr := make(map[string]addrOrigin)\n\tfor i := range cfg.Servers {\n\t\tc := &cfg.Servers[i]\n\t\tif c.PubKeyBase64 == \"\" {\n\t\t\treturn fmt.Errorf(\"relay: server #%d missing publicKeyBase64\", i)\n\t\t}\n\t\tfp, err := utils.PubKeyFingerprintFromBase64(c.PubKeyBase64)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"relay: server #%d publicKeyBase64 invalid: %w\", i, err)\n\t\t}\n\t\tif dup, ok := seenFP[fp]; ok {\n\t\t\treturn fmt.Errorf(\"relay: server #%d and #%d share the same publicKeyBase64 (fingerprint %s)\", dup, i, fp)\n\t\t}\n\t\tseenFP[fp] = i\n\n\t\tif len(c.Instances) == 0 {\n\t\t\treturn fmt.Errorf(\"relay: server #%d (fingerprint %s) has no [[Servers.Instances]]\", i, fp)\n\t\t}\n\t\tfor j := range c.Instances {\n\t\t\tinst := &c.Instances[j]\n\t\t\tif inst.Host == \"\" {\n\t\t\t\treturn fmt.Errorf(\"relay: server #%d instance #%d missing host\", i, j)\n\t\t\t}","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/config.go#L211-L247","documentation":"normalize validates every [[Servers]] entry and requires each server's publicKeyBase64; this error names the zero-based index of the offending server. The public key identifies the upstream NHP server for fingerprint dedup and crypto, so an empty one makes the server entry unusable.","triggerScenarios":"normalize iterates cfg.Servers and hits a c.PubKeyBase64 == \"\" entry — a [[Servers]] block exists in config.toml but omits publicKeyBase64, misspells it (e.g. publicKey), or leaves it empty/commented.","commonSituations":"Copied a [[Servers]] block template and forgot to fill in the server's public key; key name drift after a config schema change; template rendering with a missing secret producing an empty string; deleted the key while rotating credentials.","solutions":["Set publicKeyBase64 in the Nth [[Servers]] block to the base64 public key of that NHP server (from the server's keygen output or its config)","Check for typos — the field must be exactly publicKeyBase64","If rendering from templates, verify the server public key secret is present at deploy time"],"exampleFix":"// before (config.toml)\n[[Servers]]\n[[Servers.Instances]]\nhost = \"10.0.0.5\"\nport = 10161\n// after\n[[Servers]]\npubKeyBase64 = \"<server public key>\"\n[[Servers.Instances]]\nhost = \"10.0.0.5\"\nport = 10161","handlingStrategy":"validation","validationCode":"data, _ := toml.ParseFile(path)\nservers, ok := data.Get(\"Servers\").([]toml.Primitive) // or iterate array of tables\nfor i := range serversArray {\n\tif serversArray[i].PubKeyBase64 == \"\" {\n\t\treturn fmt.Errorf(\"server #%d missing publicKeyBase64\", i)\n\t}\n}","typeGuard":"func serverKeyPresent(s relay.Server) bool { return s.PubKeyBase64 != \"\" }","tryCatchPattern":"if err := cfg.Normalize(); err != nil {\n\tif strings.Contains(err.Error(), \"missing publicKeyBase64\") {\n\t\treturn fmt.Errorf(\"fill in each server's public key: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Keep a checklist when adding server entries: pubkey, host, port","Never commit placeholder [[Servers]] blocks; fill keys at render time","Validate the full config in CI with normalize before shipping"],"tags":["relay","config","validation","public-key"],"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"}