{"record":{"id":"5c05c41aee169145","repo":"OpenNHP/opennhp","slug":"server-peer-config-invalid-on-initial-load-w","errorCode":null,"errorMessage":"server peer config invalid on initial load: %w","messagePattern":"server peer config invalid on initial load: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"endpoints/ac/udpac.go","lineNumber":163,"sourceCode":"\ta.remoteConnectionMap = make(map[string]*UdpConn)\n\ta.serverPeerMap = make(map[string]*core.UdpPeer)\n\ta.tokenStore = common.NewTokenStore[*AccessEntry]()\n\n\tif a.etcdConn != nil {\n\t\t_ = a.loadRemoteConfig()\n\t} else {\n\t\t// load http config and turn on http server if needed\n\t\t_ = a.loadHttpConfig()\n\n\t\t// load peers. A non-nil error here means the initial\n\t\t// expandServerPeers parse failed and the running peerMap is\n\t\t// empty. Starting the daemon in that state lets it drop\n\t\t// AOL/AOP traffic silently (no peer matches), which is much\n\t\t// harder to diagnose than a startup refusal. Reloads still\n\t\t// keep the previous peer table on parse error — that's the\n\t\t// right call once a good table is live.\n\t\tif loadErr := a.loadPeers(); loadErr != nil {\n\t\t\treturn fmt.Errorf(\"server peer config invalid on initial load: %w\", loadErr)\n\t\t}\n\t}\n\n\tif a.config.FilterMode == FilterMode_EBPFXDP {\n\t\t// Snapshot the expanded peer list under serverPeerMutex so we\n\t\t// don't race the file/etcd watcher path that rewrites\n\t\t// a.config.Servers on reload (see config.go: updateServerPeers).\n\t\t// Keep the lock window minimal — copy the slice header, release,\n\t\t// then issue eBPF syscalls outside the lock so a slow kernel\n\t\t// syscall can't block a pending reload.\n\t\ta.serverPeerMutex.Lock()\n\t\tservers := make([]*core.UdpPeer, len(a.config.Servers))\n\t\tcopy(servers, a.config.Servers)\n\t\ta.serverPeerMutex.Unlock()\n\t\tfor _, server := range servers {\n\t\t\t// XDP rules key on the source IP; instances configured\n\t\t\t// with a DNS-only Host (Ip == \"\" — legitimate under the\n\t\t\t// new schema, see clusterconfig.Normalize) have nothing","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/ac/udpac.go#L145-L181","documentation":"UdpAC.Start returns \"server peer config invalid on initial load: %w\" when loadPeers() fails while loading the server peer table for the first time at startup. Unlike reloads (which keep the previous good table), an empty initial table would make the AC silently drop AOP/ART traffic, so startup is refused deliberately.","triggerScenarios":"server.toml (or etcd peer config) is missing, unparseable TOML, has no valid server peer entries, or peer public keys fail base64/validation at first boot; the wrapped loadErr from loadPeers is included via %w.","commonSituations":"Fresh deployment where server.toml was never rendered (envsubst template failure); malformed TOML after manual edit; peer key fields empty because key generation step in the deploy pipeline did not run.","solutions":["Read the wrapped cause (loadErr) in the log to see the exact parse/validation failure","Validate server.toml parses as TOML and contains at least one peer with a valid base64 public key before starting","Re-run the deploy config rendering step (deploy/config-templates via envsubst, scripts/generate-nhp-keys.sh) to produce a complete server.toml","Mount/copy the rendered file to the path the watcher expects and restart nhp-ac"],"exampleFix":"// before (server.toml missing peer key)\n[[serverPeer]]\nname = \"nhp-server-1\"\ninstanceUrlList = [\"udp://10.0.0.5:5555\"]\n// after\n[[serverPeer]]\nname = \"nhp-server-1\"\npublicKeyBase64 = \"<base64 curve key from nhp_server_public_key>\"\ninstanceUrlList = [\"udp://10.0.0.5:5555\"]","handlingStrategy":"validation","validationCode":"// preflight before starting AC\nvar tbl map[string]any\nif _, err := toml.DecodeFile(\"server.toml\", &tbl); err != nil {\n\treturn fmt.Errorf(\"server.toml unreadable: %w\", err)\n}\npeers, _ := tbl[\"serverPeer\"].([]map[string]any)\nif len(peers) == 0 { return fmt.Errorf(\"server.toml has no [[serverPeer]] entries\") }","typeGuard":null,"tryCatchPattern":"if loadErr := a.loadPeers(); loadErr != nil {\n\treturn fmt.Errorf(\"server peer config invalid on initial load: %w\", loadErr)\n} // fix the wrapped cause in server.toml, then restart","preventionTips":["Render server.toml via envsubst templates in CI, never by hand","Check the wrapped loadErr log line for the exact field at fault","Add a config preflight check to deployment scripts before launch"],"tags":["config","peers","startup","toml"],"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"}