Skip to content

fix(config): do not start setup over live Settings - #1175

Merged
frahlg merged 2 commits into
masterfrom
fix/config-seed-not-clobber
Sep 8, 2026
Merged

fix(config): do not start setup over live Settings#1175
frahlg merged 2 commits into
masterfrom
fix/config-seed-not-clobber

Conversation

@frahlg

@frahlg frahlg commented Sep 8, 2026

Copy link
Copy Markdown
Member

Closes #1153.

A missing leftover config.yaml started the setup wizard even when state.db already held Settings. Finishing the wizard wrote a new YAML, and InitializeStorage treated the hash mismatch as an old-Core rollback save and committed that document over live settings/config_v1. The same path fired for an edited leftover seed that no longer carried config_database.

YAML is a locator and rollback seed after schema 2. Live Settings stay in SQLite.

Fix

All in go/internal/config (no main.go, no state package, no DuckDB files). Open PRs touch config.go field surface and main.go; none touch storage.go. Load only adds the missing-seed recovery path.

  • Load: if the seed file is missing, read settings/config_v1 from sibling state.db. First-run (no settings row) still fails, so the wizard still starts on a new box. isConfigMissing is unchanged; main.go never reaches it when Load succeeds.
  • InitializeStorage: a recovered boot with ConfigDatabase set rewrites the missing locator from SQLite. legacySave now requires a real old-Core replacement YAML (no locator, no SQLite comments, not a wizard/default document). Wizard output is detected from the hardcoded setup.js loop defaults (control_interval_s: 5, slew_rate_w: 500, min_dispatch_interval_s: 5). In that case the locator is rewritten from live Settings instead of keeping the wizard bytes as a rollback seed.

Tests

go test ./internal/config -count=1 -timeout 180s

  • Delete config.yaml, keep state.db with Settings → Load succeeds, Settings unchanged, locator rewritten.
  • Empty sibling database still fails Load (first-run setup remains).
  • Edited leftover without config_database does not override Settings.
  • Wizard SaveAtomic document does not override Settings.
  • Existing old-Core retry import (TestUpgradeRetryImportsSettingsSavedByTheRolledBackCore) still imports.

go test ./cmd/ftw -run 'TestConfigStorage|TestMissingDatabase' -count=1 passes. Full ./cmd/ftw has unrelated nova key-directory mode failures on this host.

Not changed


Note

Medium Risk
Changes config bootstrap and import precedence at every Core start; mistakes could still clobber or block settings, though behavior is heavily regression-tested.

Overview
Fixes a path where a missing or misleading config.yaml could treat an already-configured box as first-run and overwrite live Settings in SQLite (e.g. after the setup wizard or an edited leftover seed).

Load now falls back to settings/config_v1 in sibling state.db when the seed file is absent; empty DB with no settings still fails so true first-run setup is unchanged.

InitializeStorage treats YAML as a locator/rollback seed after schema 2: SQLite wins unless the on-disk bytes look like a genuine old-Core rollback save (legacyCoreSave). Wizard/default seeds (hardcoded 5 / 500 / 5 site loop defaults) and edited leftovers no longer re-import; writeSettingsLocator rewrites the seed from live Settings instead. Recovery with a missing locator file also recreates the pointer YAML without touching stored config.

New storage_test cases cover deleted seed, empty DB, edited leftover, and wizard SaveAtomic output.

Reviewed by Cursor Bugbot for commit edb727c. Bugbot is set up for automated code reviews on this repo. Configure here.

A missing leftover config.yaml is not first-run when state.db already
holds settings/config_v1. Load recovers from the sibling database and
InitializeStorage rewrites the locator. An edited leftover seed or a
wizard document cannot replace live Settings; only an old-Core rollback
save is imported.

Closes #1153

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@cursor

cursor Bot commented Sep 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_92598562-9a2f-49af-8c6a-77a2ddff57ad)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-08T09:38:15.769257Z 7068552 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70685528b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// loadSettingsBesideSeed reads live Settings from state.db next to the seed.
// Schema 2 stores Settings in SQLite; a missing leftover YAML is not first-run.
func loadSettingsBesideSeed(seedPath string) (*Config, bool) {
database, err := filepath.Abs(filepath.Join(filepath.Dir(seedPath), "state.db"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Probe the actual runtime state directory for missing seeds

On the standard native deployment, deploy/ftw.service runs from /var/lib/ftw while passing /etc/ftw/config.yaml, so the default live database opened by main.go is /var/lib/ftw/state.db, not /etc/ftw/state.db. If that seed is deleted, this hard-coded sibling lookup misses the live Settings, Load returns the missing-file error, and main.go enters the setup wizard instead of recovering; the service cannot resume automatically and may be unable to recreate the root-owned /etc/ftw seed. The recovery lookup must use the deployment's runtime state location rather than assuming it is beside the config file.

Useful? React with 👍 / 👎.

return false
}
// setup.js buildConfig hardcodes these; applyDefaults uses 2 / 3000 / 2.
return probe.Site.ControlIntervalS == 5 && probe.Site.SlewRateW == 500 && probe.Site.MinDispatchIntervalS == 5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve rollback saves that resemble wizard defaults

When a site was originally configured through the wizard, its legitimate configuration retains 5/500/5; an older Core that saves settings after rollback removes the locator and header but serializes those same nonzero values along with the operator's changes. This predicate therefore classifies that real rollback save as a wizard document, causing InitializeStorage to reload stale SQLite Settings and overwrite the YAML, silently discarding the changes the rollback-import path is intended to preserve. Wizard detection needs provenance that cannot also occur in an ordinary saved configuration.

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Sep 8, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_51f7e2ce-3991-49c5-96d3-9f728aada41f)

@frahlg
frahlg merged commit 2cfad06 into master Sep 8, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(config): a missing seed YAML must not start setup over live Settings

1 participant