diff --git a/SPEC.md b/SPEC.md index 51a60e1..40abc2b 100644 --- a/SPEC.md +++ b/SPEC.md @@ -237,7 +237,7 @@ override file values. A missing file is fine; a malformed one is fatal. | `UPDATE_REPO` | `https://git.rambossek.at/PUBLIC/gpu-turnstile` | repository to check for releases | | `UPDATE_ASSET` | `gpu-turnstile.exe` | release asset to download | | `APP_VER` | `stable` | version to run: `dev` disables updates, `stable` tracks the latest release, or an exact `vX.Y.Z` pin (up- or downgraded to) | -| `CFG_VER` | _(installer-managed)_ | config format reference written by `--install-service` (always a concrete `vX.Y.Z`; a dev build stamps `v0.0.0`); missing = the file is replaced with a fresh sample (backup `.bak`) | +| `CFG_VER` | _(installer-managed)_ | config format reference written by `--install-service` (always a concrete `vX.Y.Z`; a dev build stamps `v0.0.0`); missing = the file is replaced with a fresh sample (backup `.bak`). New settings are appended (commented out) at install and at every startup after an update changed the version | Startup fails fast on unparsable values and when neither consumer URL is set. Enabled upstreams are probed once at start (`/api/version`, diff --git a/cmd/gpu-turnstile/main.go b/cmd/gpu-turnstile/main.go index 7c27324..0c7514d 100644 --- a/cmd/gpu-turnstile/main.go +++ b/cmd/gpu-turnstile/main.go @@ -172,6 +172,7 @@ func main() { } log, logOut, logCloser := newLogger(cfg) defer logCloser.Close() + syncEnvFile(resolveConfigPath(configPath), cfg.LogFile, log) if service.IsService() { if err := service.Run(func(ctx context.Context) error { return run(ctx, cfg, log, logOut, true) }); err != nil { @@ -188,6 +189,27 @@ func main() { } } +// syncEnvFile upgrades an installer-written config file after an update: +// settings added since its CFG_VER are appended (commented out) and CFG_VER +// is bumped. Files not written by the installer (no CFG_VER), up-to-date +// files and dev builds are left untouched; a write failure is logged, not +// fatal. +func syncEnvFile(path, logFile string, log *slog.Logger) { + data, err := os.ReadFile(path) + if err != nil { + return // no config file; nothing to upgrade + } + synced, changed := config.SyncSample(string(data), version, logFile) + if !changed { + return + } + if err := os.WriteFile(path, []byte(synced), 0o644); err != nil { + log.Warn("could not append new settings to the config file", "path", path, "err", err) + return + } + log.Warn("config file updated: new settings appended", "path", path, "version", version) +} + // defaultConfigPath returns gpu-turnstile.env next to the executable. func defaultConfigPath() string { exe, err := os.Executable() diff --git a/internal/config/sample.go b/internal/config/sample.go index 92e0223..050514e 100644 --- a/internal/config/sample.go +++ b/internal/config/sample.go @@ -66,7 +66,8 @@ func sampleEntries(logFile string) []sampleEntry { // comment line. Everything is commented out — so all defaults apply — // except the CFG_VER/APP_VER header and LOG_FILE when logFile is non-empty // (a Windows service has no console). CFG_VER records the version that -// wrote the file so later installs can upgrade it. +// wrote the file so installs — and startups after an update — can upgrade +// it. func SampleEnv(version, logFile string) string { // CFG_VER is always a concrete vX.Y.Z — never "dev". A dev build // stamps v0.0.0, which sorts older than any release, so the next