GitHub
08/09/2026, 9:46 PMpkg/config/types.go, cmd/tls/utils.go, cmd/api/utils.go)
Changed TLSConfiguration and APIConfiguration so that required sections (service, db, redis) remain value types, while all optional sections (osquery, tls, logger, carver, debug, batchWriter, configEndpoints, osctrld, metrics, saml, oidc, jwt) are now pointer types. When the YAML omits a section, the pointer stays nil instead of pointing to a zero-valued struct. loadedYAMLToServiceParams only sets pointers for sections the YAML actually provides.
Existing deployments with full YAML files are unaffected — all sections are present, pointers are non-nil, behavior is identical.
Nil-safe Resolve (pkg/serviceconfig/serviceconfig.go)
applySection now returns (bool, error) and checks each ServiceParameters field for nil before unmarshaling. When a DB row with source=db exists for a section that the minimal YAML didn't provide (nil pointer), the value is skipped safely instead of panicking.
Apply & Restart endpoint (cmd/api/handlers/service_config.go)
POST /api/v1/service-config/apply — `ServiceConfigApplyHandler`:
• Requires AdminLevel + NoEnvironment permissions.
• Audit-logs the action via AuditLog.SettingsAction.
• Returns 202 immediately; signals a restart channel asynchronously so the HTTP response completes before shutdown begins.
• Returns 503 if RestartCh is not wired.
Graceful shutdown (cmd/api/main.go)
• Added a restartCh chan struct{} wired to the handlers via WithRestartCh.
• Restructured the HTTP server start from blocking http.ListenAndServe to http.Server running in a goroutine, with the main goroutine selecting on either a server error or the restart signal.
• On restart signal: calls srv.Shutdown() with a 10-second timeout, then os.Exit(0). The process manager (systemd, docker, k8s) restarts the service, and Resolve applies all source=db sections on boot.
• Added nil-safe TLS pointer check for the optional-YAML case.
Frontend (frontend/src/features/service-config/ServiceConfigPage.tsx)
• "Apply & Restart" button appears in the toolbar when any section has source=db (pending DB-edited changes that need a restart).
• Button shows "Restarting…" while the POST is in flight, then "Restart triggered ✓" on success.
• applyServiceConfig() API client function added to frontend/src/api/service-config.ts.
Tests
• Backend: TestSeed_NilOptionalSections_DoesNotPanic, TestResolve_NilOptionalSection_DBValueIgnored — verify nil section pointers don't crash Seed/Resolve.
• Frontend: 3 new tests — no Apply button when no source=db sections, Apply button appears when source=db exists, clicking Apply calls applyServiceConfig.
Validation
• go build ./... — clean
• go test ./... — all packages pass
• npm run check — TypeScript typecheck clean
• npm test — 34 test files / 206 tests pass
• golangci-lint — 0 issues on modified files
How it works end-to-end
1. Operator edits debug section in the frontend → PUT saves it to DB with source=db.
2. "Apply & Restart" button appears in the toolbar.
3. Operator clicks it → POST /api/v1/service-config/apply → 202 Accepted → restartCh signalled.
4. Main goroutine receives signal → srv.Shutdown() → os.Exit(0).
5. Process manager restarts the service → Seed (doesn't clobber source=db) → Resolve applies the DB value to ServiceParameters.
6. Service runs with the edited config.
Deployment impact
Existing deployments: unchanged. Full YAML files work as before.
New deployments: can ship a minimal YAML with only service, db, and redis. Optional sections are managed from the database via the API. On first boot, only required sections are seeded. The operator configures the rest through the frontend and clicks "Apply & Restart" when ready.
Breaking change: TLSConfiguration and APIConfiguration struct field types changed from values to pointers for optional sections. Internal to the repo; no external consumers known.
jmpsec/osctrlGitHub
08/10/2026, 9:44 AM