GitHub
08/15/2026, 4:34 PMApply & Restart picked up those edits into the running process, but a fresh deploy that reloads from the YAML file on disk would silently lose them. This closes that gap without changing how Apply & Restart works.
Backend
• pkg/serviceconfig/filestatus.go (new): ConfigFileStatus table + `ReportFile`/`GetFileStatus`/`HasPendingChanges`/`PersistToFile`. Each service reports at boot whether its own process can write its config file (CheckWritable opens for write, no `O_CREATE`/`O_TRUNC`, to test the exact permission needed). PersistToFile overlays DB-edited sections onto a freshly loaded copy of the YAML (never the live running config, so writing to disk never affects the running process ahead of a restart), writes it, then flips affected rows back to source=yaml.
• New endpoints: GET /api/v1/service-config/status/{service} (pending changes + file writability) and POST /api/v1/service-config/persist (perform the write). Both admin-gated and rate-limited alongside apply.
• osctrl-api persists inline (same process owns the YAML loader). osctrl-tls cannot be written from osctrl-api's process, so the persist request is queued through pkg/servicecommands (extended with a generic `Request`/`ActionPersistConfig` alongside the existing restart action) and consumed by the TLS process's existing command-poll loop, which no longer exits after handling a command — persisting doesn't restart, so it keeps polling.
• `pkg/config/utils.go`: ServiceParameters.ConfigFilePath() returns the file path only when the service was actually started with -config (vs. flags/env vars); `GenerateTLSConfigFile`/`GenerateAPIConfigFile` now also carry RateLimits through.
Frontend
• `ServiceConfigPage`: new "Write to Disk" button next to "Apply & Restart", driven by the status endpoint. Disabled quietly while status is loading (avoids a false-red flash), disabled with a visible reason when the target file isn't writable. Polls status after a queued (TLS) persist since it completes asynchronously.
• Tracks persistedThisSession (session-local, not persisted) so Apply & Restart stays available after a write flips sections back to source=yaml — otherwise the operator could write to disk but lose the ability to also restart in the same session.
Docs
• osctrl-api.yaml regenerated with the two new paths/schemas.
Not done / deliberately deferred: persistedThisSession is in-memory only, so a page reload after writing-to-disk-without-restarting forgets that a restart is still worth offering — noted in code as a ponytail: comment; fix would be comparing service boot time against section UpdatedAt if this turns out to matter in practice.
jmpsec/osctrlGitHub
08/15/2026, 5:30 PM