Skip to content

feat(config): reload config.json on SIGHUP - #175

Open
gg582 wants to merge 1 commit into
gosuda:mainfrom
gg582:sighup-config-reload
Open

gg582 wants to merge 1 commit into
gosuda:mainfrom
gg582:sighup-config-reload

Conversation

@gg582

@gg582 gg582 commented Oct 22, 2026

Copy link
Copy Markdown
Member

Reload config.json on SIGHUP

A running host read config.json once at start. An operator who edited the
file directly had to restart the host, and until then the console refused
settings saves because the file's hash no longer matched the settings ETag.

SIGHUP now reloads the file on a running host, without interrupting serving:

  • Validation is all-or-nothing: strict parsing, the documented cross-key
    checks, schema_version equality in either direction, and the host binding
    (host.instance_id / host.data_dir never change on a live host). A
    refused reload keeps the current settings and ETag and logs the reason.
  • On adoption the settings ETag advances to the file's hash, so console
    saves work again.
  • Keys with a live path take effect with the same side effects as a console
    save (rate limit push, quota enforcement, portal.hide, proxy template,
    external origins, default providers, preferred console).
  • network.permitted and both console-domain lists reconcile the way the
    next start would: added providers record their grant and start their
    backend, removed providers lose their routes while flats keep their
    permissions, and console domains are served or retired accordingly.
  • Restart-required keys (host.*, metrics.*, portal.relays, …) are
    adopted and logged as awaiting a restart; run overrides and legacy flag
    pins keep their running values.
  • Reloads hold the settings lock against console saves, SIGHUPs coalesce,
    and Close waits out an in-flight reload before teardown.

New spec: docs/spec/config-reload.md (the key table in
docs/configuration.md now names the reload path where it applies).

Verification

  • go test ./... -count=1: all packages pass except the pre-existing,
    environment-caused internal/launchd TestPlistContent (macOS plutil
    on a Linux host; fails identically on clean main).
  • 18 new/extended tests in internal/core and internal/app, including
    adoption and ETag advance, refusal paths (invalid JSON, schema mismatch
    both directions, host binding), rate-limit push, pins and run overrides,
    provider add/remove reconcile, both console-domain reconciles, signal
    coalescing, reload/save serialization, post-adoption side-effect failure,
    and Close waiting out an in-flight reload (with a demonstrated
    fail-without-fix check). -race clean on the reload tests.
  • Manual end-to-end with the built binary: file edit, real kill -HUP, live
    application, console save unblocked, invalid file refused while serving,
    restart-required key logged and not applied.

Review record (development protocol)

Design, implementation and testing each passed adversarial review by an
eligible different model (author: kimi-code/k3; reviewer:
claude-sonnet-4-5, effective identity from CLI execution metadata):

  • Design: ACCEPTED after 6 rounds (25 findings dispositioned, one rebuttal
    accepted by the reviewer; final: "No blocking defects found.")
  • Implementation: ACCEPTED after 2 rounds (shutdown-race blocker fixed and
    re-verified).
  • Testing: ACCEPTED after 1 correction round (3 critical evidence gaps
    closed with new tests) and a rebuttal round where the reviewer withdrew
    its finding after verifying the diff evidence ("The implementation
    approval carries forward to snapshot be268a27").

Full task report with receipts: local-only at
~/.local/share/flats-dev/sighup-config-reload/task-report-final.md
(not accessible to remote reviewers; verdicts are quoted above).

Known coverage limits: real signal delivery is verified manually only;
Tailscale/NetBird reconcile paths use test seams, not live networks.

A running host read config.json only at start: an outside edit applied at
the next restart and blocked console saves on the ETag mismatch until then.
SIGHUP now reloads the file on a running host. Validation is all-or-nothing
(strict parse, cross-key checks, schema_version equality, host binding); on
adoption the settings ETag advances to the file hash. Keys with a live path
take effect with the console save's side effects, network.permitted and both
console-domain lists reconcile the way a start would, restart-required keys
are adopted but apply at the next start, and run overrides and legacy pins
survive. Reloads hold the settings lock against console saves, coalesce,
and Close waits out an in-flight reload before teardown.

Spec: docs/spec/config-reload.md.
@gg582
gg582 requested a review from oesni October 22, 2026 21:52
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.

1 participant