feat(host): the daily update check and the staged update (P11a step 4)
While the books are open the host looks at the release feed at most once a day (knob update_check, noted in updates/checked so a restart does not look again), and only downloads and verifies a newer release into <root>/updates/staged/ (sha256 AND minisign signature, as for update). It never swaps: the staged program is applied (a) by an opener just before it starts a host, when no host runs, so no call is cut off, after a second check against the key (a staged file that fails or does not start is dropped), and (b) by wanderbooks update, the owner's say-so, which reuses the staged copy instead of downloading again. A local release can be named in <root>/updates/feed (file:// only, with its key file, same rule as --trusted-key). The program's version is set by main since packages rewrite only the cli's. No new dependency. Tests (crates/ledger-cli/tests/update.rs): the_host_stages_a_daily_update_and_the_next_start_applies_it, a_daily_update_that_is_not_ours_is_not_staged, update_installs_the_copy_the_host_staged_without_downloading_again, a_staged_update_that_was_tampered_with_is_dropped_not_applied. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
1 parent
562b893c64
commit
6e7deb15c7
6 files changed
+454
-15
No files matched your search
@@ -500,6 +500,12 @@ impl Host {
|
||||
let started_as = running_identity();
|
||||
let mut exe_poll = tokio::time::interval(self.knobs.exe_poll);
|
||||
exe_poll.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||
// The daily look for a newer release (P11a step 4): the work is
|
||||
// a thread of its own, one at a time, and only stages a file.
|
||||
let mut update_poll =
|
||||
tokio::time::interval((self.knobs.update_check / 4).max(Duration::from_millis(10)));
|
||||
update_poll.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||
let looking = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
|
||||
let mut idle_at = Some(Instant::now() + self.knobs.close_after);
|
||||
let server_start = tokio::time::sleep(self.knobs.server_delay);
|
||||
tokio::pin!(server_start);
|
||||
@@ -657,6 +663,20 @@ impl Host {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = update_poll.tick(), if matches!(self.phase, Phase::Open) => {
|
||||
use std::sync::atomic::Ordering;
|
||||
if !looking.swap(true, Ordering::SeqCst) {
|
||||
let (root, every, looking) = (self.root.clone(), self.knobs.update_check, looking.clone());
|
||||
std::thread::spawn(move || {
|
||||
match crate::update::stage_if_due(&root, crate::update::program_version(), every) {
|
||||
Ok(Some(version)) => eprintln!("wanderbooks host: version {version} is downloaded and checked; it starts the next time the books open, or at `wanderbooks update`"),
|
||||
Ok(None) => {}
|
||||
Err(why) => eprintln!("wanderbooks host: the look for a newer version failed: {why}"),
|
||||
}
|
||||
looking.store(false, Ordering::SeqCst);
|
||||
});
|
||||
}
|
||||
}
|
||||
_ = exe_poll.tick(), if matches!(self.phase, Phase::Open) => {
|
||||
if started_as.is_some() && file_identity().is_some_and(|now| Some(now) != started_as) {
|
||||
eprintln!("wanderbooks host: a new version is installed: restarting");
|
||||
|
||||
Reference in new issue
Block a user