Skip to content

fix(updater): apply new manifest and version pins in a single Update - #362

Open
gmmcosta15 wants to merge 2 commits into
devfrom
fix/updater-single-pass-update
Open

gmmcosta15 wants to merge 2 commits into
devfrom
fix/updater-single-pass-update

Conversation

@gmmcosta15

@gmmcosta15 gmmcosta15 commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Description

  • Bug Fix

One Update now applies everything a BlocksScreen release ships, including components.yaml pin or branch changes and new updater code. Previously a new Spoolman pin needed a second Update and, because of a status bug, was never applied at all.

Changes

  • executor.py: a pinned version reports 1 pending update while HEAD is off the pin (it was always 0, so a new pin never counted as dirty); remote_version describes the pin; new git_show_file (sha refs only).
  • models.py: ComponentStatus.needs_update replaces the inline dirty predicate in _update_all_locked, so the batch and the follow-up share it.
  • hooks/BlocksScreen.sh: an updater/ change writes code to the restart sentinel (the existing _apply_deferred_restart path: import self-test, then post-batch self-restart). A components.yaml change also writes the pre-update hash to ~/.cache/blockscreen/updater_followup, keeping the oldest base across chained updates.
  • components.py: load_components(bundled_text) parses a given manifest instead of the bundled file.
  • service.py: run_followup runs once after a daemon restart. It diffs the manifest at the marker hash against the loaded one, then updates the changed entries that are also dirty. BlocksScreen itself is excluded, so it cannot loop.
    • It defers while printing, while a deploy runs, or while the process lock is held.
    • It also defers while a target's remote is unreachable, up to the provisioning offline cap; the entry stays dirty for a later Update.
    • The marker is cleared before the update runs, so a failing entry never re-runs on every boot.
  • dbus_service.py: the boot loop calls the follow-up after provisioning and re-polls at the retry interval while it is deferred. A follow-up that ran also triggers the background apt sweep that the restart skipped.

Motivation

Root causes:

  • Stale manifest: components.yaml is loaded once in UpdateService.__init__, and update_all runs BlocksScreen last under the old manifest.
  • Pin never dirty: a pinned component always reported commits_behind=0.
  • No sentinel writer: nothing wrote the code restart sentinel, so new updater code was only adopted on the next deploy or reboot.

The fix follows Homebrew's update.sh: it pulls brew and the taps first, then runs brew update-report in a new process, so the new manifest is applied by new code.

Old daemons are covered too. The daemon runs from the repo, so a Release-2.0 daemon executes the new hook after staging, and its _read_clear_sentinel already handles code.

Tests

  • New unit tests:
    • pin dirty and clean
    • git_show_file sha-only guard
    • needs_update
    • run_followup: no marker, malformed marker, printing, deploy, lock, apply, one-shot on failure, nothing dirty, offline cap
    • _followup_targets: changed ∩ dirty, BlocksScreen excluded, missing base
    • dbus follow-up pass and re-poll intervals
    • BlocksScreen hook run against a real temp git repo

@gmmcosta15 gmmcosta15 self-assigned this Oct 22, 2026
@gmmcosta15
gmmcosta15 requested a review from a team October 22, 2026 09:26

This branch has not been deployed

No deployments
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