Repository navigation
feat(updates): background update check, status API and docs (off by default) - #108
Merged
Merged
Conversation
UpdateCheckService is its own hosted service: off by default it idles on an options-change token and makes no network call; on, it checks on a jittered schedule with 15 min / 1 h / 4 h backoff, honours rate-limit resets, runs one check at a time and catches every exception. UpdateStatusProvider keeps the last result in memory and computes status and announce from it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GET reads memory only; POST check (202/429/409); POST settings persists UpdateCheckEnabled atomically into appsettings.local.json. The API key middleware and JSON options are split out of ApiServer.Build unchanged so tests can run the real rule against the real routes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Manual clock and counting fake handler: off makes zero calls for a simulated day, flipping the option on starts checks, backoff, cooldown, one-at-a-time, loop survival, stop token. Endpoint tests run over loopback HTTP through the real auth middleware, JSON options and route mapping. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rewriting it Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…notes, CLAUDE.md Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tch rewrites it AtomicFile renames a staging file over the target, which makes a new file that inherits the folder ACL and drops any tightened ACEs. The file can hold the API key, so use File.Replace (same atomic swap, merges the old file's ACL and attributes into the replacement) when the target exists, a plain move when it does not, and delete the staging file on failure. AtomicFile itself is unchanged for its other callers. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A failed check no longer switches an announcement off. The last good result stays, status is recomputed from the current installed version, and the error is returned beside it. With no good result there is no latest release, so nothing announces. Also removes the three new test-code warnings (blocking waits became WaitAsync, nullability fixed). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… swap blocked by a reader The service tests no longer wait for a quiet period. The loop counts each time it parks on the clock or on a change, and the tests wait for that count, so a clock step is followed by exactly the reaction it causes. The two race tests (settings write atomicity, provider publish/read) now prove the overlap before asserting and fail with a clear message if it never happens. The hang tests wait for the handler to report the request in flight. A configuration reload can hold appsettings.local.json open for a moment, which made the swap fail about 1 in 30 under load. The swap is now retried (6 times, growing pause) before the 500, with a test seam that proves a retry happened. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on the update-check core. This PR makes it run, exposes it, and documents it. Still off by default: with
UpdateCheckEnabledfalse the service makes no network call.What changes
UpdateCheckService: its own hosted service. Off, it idles on an options-change token. On, it checks about every 12 h (clamped 1 to 168, ±10% jitter), first check 10 to 20 min after start, backoff 15 min / 1 h / 4 h, rate-limit resets honoured, one check at a time, never throws into the host.UpdateStatusProvider: holds the last result in memory and computesstatusandannounce. It never calls GitHub. A failed check no longer switches an "update available" notice off: the last good result stays, the status is recomputed from the installed version (so it ends once you update), and the error is shown beside it. With no good result there is nothing to announce.GET /api/system/updates,POST /api/system/updates/check(202 / 429 / 409; the check runs inside the request, bounded to 30 s),POST /api/system/updates/settings({ "enabled": bool }). All behind the API key when auth is on; none is public.appsettings.local.jsonwithFile.Replace: still one atomic swap, but the file keeps its existing permissions. The file can hold the API key and an administrator may have tightened its ACL, so a plain rename-over would have widened it. If something briefly holds the file open (the configuration reload, a scanner), the swap is retried a few times with a short, growing pause before it reports 500. A failed write changes nothing and the staging file is deleted.AtomicFileitself is unchanged.ApiServer: the auth middleware and JSON options are split out unchanged (so tests can use them); nothing else changes.security-posture.md"Outbound connections", README config rows and "Updating",CLAUDE.md, and the 0.6.20 release notes.Safety: GitHub text (
html_url, release notes) is never forwarded; links are built from the repo name and the parsed tag. The check never downloads, installs or runs anything.Tests: service behaviour on a fake clock, status and announce rules, and the API contract over loopback HTTP through the real auth middleware and routes. Timing-dependent tests prove their overlap instead of hoping for it. Each group was mutation-checked. Full suite 1444 passed, 18 skipped; the Updates group passed 20 runs in a row.
Live-host checks still needed: TLS and proxy from SYSTEM, config reload in the running service, the ACL on
update-check.json, andFile.Replaceonto the realappsettings.local.json.🤖 Generated with Claude Code