{"record":{"id":"921d57f55df3268c","repo":"OpenNHP/opennhp","slug":"relay-privatekeybase64-must-be-set-in-config","errorCode":null,"errorMessage":"relay: privateKeyBase64 must be set in config","messagePattern":"relay: privateKeyBase64 must be set in config","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/relay/config.go","lineNumber":178,"sourceCode":"\tif err := toml.Unmarshal(data, cfg); err != nil {\n\t\treturn nil, fmt.Errorf(\"relay: failed to parse config %s: %w\", path, err)\n\t}\n\n\tif err := cfg.normalize(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.Info(\"[Relay] loaded config from %s with %d server(s)\", path, len(cfg.Servers))\n\treturn cfg, nil\n}\n\n// normalize validates the configuration and applies legacy-field migration so\n// that the rest of the relay only has to look at Config.Servers. It is\n// exported as a method (not a function) to make it directly testable on a\n// hand-built Config in unit tests without round-tripping through TOML.\nfunc (cfg *Config) normalize() error {\n\tif cfg.PrivateKeyBase64 == \"\" {\n\t\treturn fmt.Errorf(\"relay: privateKeyBase64 must be set in config\")\n\t}\n\n\thasLegacy := cfg.NHPServerHost != \"\" ||\n\t\tcfg.NHPServerPort != 0 ||\n\t\tcfg.NHPServerPublicKeyBase64 != \"\"\n\n\tswitch {\n\tcase hasLegacy && len(cfg.Servers) == 0:\n\t\t// Auto-migrate: promote the legacy fields into a single server\n\t\t// so existing demo configs keep working through phase 1.\n\t\tlog.Warning(\"[Relay] nhpServerHost/nhpServerPort/nhpServerPublicKeyBase64 are deprecated; \" +\n\t\t\t\"migrate to [[Servers]] / [[Servers.Instances]] in config.toml\")\n\t\tcfg.Servers = []Server{{\n\t\t\tPubKeyBase64: cfg.NHPServerPublicKeyBase64,\n\t\t\tInstances: []ServerInstance{{\n\t\t\t\tHost: cfg.NHPServerHost,\n\t\t\t\tPort: cfg.NHPServerPort,\n\t\t\t}},","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/config.go#L160-L196","documentation":"Config.normalize validates the relay configuration and migrates legacy fields; this error is returned when PrivateKeyBase64 is empty. The relay needs its own private key to participate in the NHP protocol, so a config without it is unusable and startup is aborted.","triggerScenarios":"normalize (called from LoadConfig at startup, or directly in unit tests on a hand-built Config) encounters cfg.PrivateKeyBase64 == \"\" — the key was never written to config.toml, is commented out, or the key name is misspelled so TOML leaves the field at its zero value.","commonSituations":"Fresh deployment where the operator skipped the keygen step; copying an example config without filling in privateKeyBase64; renaming the key in config.toml (e.g. privateKey) so it no longer maps to the struct field; an empty value produced by a config-rendering template with a missing env var.","solutions":["Generate keys with the daemon's keygen command (e.g. ./nhp-relayd keygen --curve) and paste the base64 private key into privateKeyBase64 in config.toml","Check the field is spelled privateKeyBase64 and not commented out in config.toml","If rendering configs from templates, verify the source secret/env var is populated before deployment"],"exampleFix":"// before (config.toml)\n# privateKeyBase64 = \"\"\n// after\nprivateKeyBase64 = \"<base64 key from keygen>\"","handlingStrategy":"validation","validationCode":"data, err := toml.ParseFile(path)\nif err != nil { return err }\nif v, ok := data.Get(\"privateKeyBase64\").(string); !ok || v == \"\" {\n\treturn errors.New(\"relay config: privateKeyBase64 missing or empty\")\n}","typeGuard":"func hasPrivateKey(cfg *relay.Config) bool { return cfg != nil && cfg.PrivateKeyBase64 != \"\" }","tryCatchPattern":"if err := cfg.Normalize(); err != nil {\n\tif strings.Contains(err.Error(), \"privateKeyBase64 must be set\") {\n\t\treturn fmt.Errorf(\"run keygen and set privateKeyBase64 in config: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Always run the daemon's keygen step as part of provisioning","Render configs from secrets templates and fail the deploy if any secret is empty","Lint configs with a script asserting required top-level keys are non-empty"],"tags":["relay","config","validation","missing-field"],"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"}