<#955 YAML simplification and restart service when...
# osctrl
g
#955 YAML simplification and restart service when configuration changes Pull request opened by javuto Summary Adds an "Apply & Restart" button that triggers a graceful service restart so DB-edited config changes take effect, and makes the YAML file optional for non-connection sections. Problem Two gaps remained: 1. Changes didn't propagate. An operator could edit a config section in the frontend and save it to the DB, but the running service kept using the in-memory config loaded at boot. The change only took effect on the next manual restart. The "Saved ✓" feedback was misleading — it meant "persisted to DB," not "active." 2. YAML couldn't be simplified. Every config section had to be present in the YAML file, even sections the operator intended to manage from the database. Missing sections became zero-valued structs, indistinguishable from intentionally-zero values. Changes Optional YAML (
pkg/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/osctrl