Skip to content

Self-update: open decisions and known gaps after #2886 #2918

Description

@sinelaw

Follow-up tracker for the install-provenance / self-update work in #2886. Everything below was found while building or reviewing that branch and is not fixed by it. Three items need a decision from you before anyone can write code; the rest are defects with an obvious shape.

Needs a decision

1. Flatpak can never run its own update command

registry.rs returns flatpak install --user --or-update --noninteractive <file> for Channel::Flatpak. A Flatpak build of fresh runs inside org.freedesktop.Platform, which ships no flatpak binary, so that command cannot execute where it is issued. flatpak-spawn appears nowhere in the tree (grep -rn flatpak-spawn crates/ is empty).

Two ways out:

  • Prefix with flatpak-spawn --host — works only if the manifest grants --talk-name=org.freedesktop.Flatpak, which is close to full host access and worth thinking about before granting.
  • Concede that Flatpak cannot self-update: route Channel::Flatpak to Manual with wording pointing at the user's own Flatpak tooling (GNOME Software / flatpak update on the host), never the releases page.

Today the command is generated, shown, and fails at exec time — the worst of the three.

2. snap, scoop, chocolatey are enum variants that target nothing

No packaging job writes those receipts, and no artifact is published for them. #2886 routes all three to Manual, which is honest but leaves dead variants in the public Channel enum. Deleting them is the real fix, and it changes the receipt wire format — a receipt written by a future installer naming a removed variant must still parse. Your call on whether to delete, and if so whether an unknown channel = value should degrade to Unknown rather than fail the parse.

3. FRESH_RELEASES_URL / FRESH_DOWNLOAD_BASE accept any scheme and host

These exist so the update path is reachable outside a real release (that is how #2886 was tested). They are read with no restriction: http:// is accepted, as is any host. The checksum sidecar comes from the same base as the payload, so an attacker who controls the base controls both and the verification proves nothing.

Options: require https://, pin the host to github.com, gate both behind a debug-only build flag, or leave as-is and document them as a testing affordance. Note the same override drives the background check, so a wrong value is silent.

Defects

4. Channel::Zypper is unreachable

Nothing writes a zypper receipt — the openSUSE instructions install the release .rpm directly, which produces a dnf/rpm receipt. The variant and its DownloadPackage route are correct but dead. Either have the openSUSE path record zypper, or fold it into the rpm route.

5. npm update -g cannot cross a 0.x minor

npm update respects the caret range, and ^0.4.6 does not admit 0.5.0. Every npm user is stuck at the current minor until fresh reaches 1.0. npm install -g fresh-editor@latest is the command that actually works.

6. nix profile upgrade silently no-ops on Nix < 2.20

Older Nix requires a match argument (nix profile upgrade '.*' or an index). On 2.19 and earlier the bare form exits 0 having done nothing, so the update reports success and nothing changed.

7. No CI job exercises the update flow

#2886 added a check that provenance resolves from an installed package (config paths asserting channel + Authoritative). Nothing runs an actual update. Every end-to-end verification in that PR was done by hand in containers, so a regression in package_update, the checksum step, or the install command reaches users unchallenged. A single job — install an older .deb, serve the newer one locally, run fresh --cmd update --yes --force, assert dpkg reports the new version — would cover the whole spine.

8. Existing .deb / .rpm installs cannot be reached by the fix

The receipt-lookup fix has to be running to work, so anyone on a package built before it merges stays on "installed manually, can't update itself" until they install a post-fix release by hand. Nothing can be done in code; it needs a release-note line.

9. The popup has never been driven by hand

The plan-derived rows and their exit codes are unit-tested and the underlying check was verified in containers, but nobody has clicked the indicator and looked at the rendered popup. Step 4 of #2886's description is the recipe.

10. Nits

  • updater.rs — the doc comment on package_update still says "elevating with sudo/doas". The doas fallback was deliberately removed; the comment is stale.
  • telemetry::stamp_file_path() lands at ~/.local/share/fresh/fresh/telemetry_stampDirectoryContext::from_system() already appends fresh and the stamp path appends it again. Pre-existing and harmless, but it makes the file hard to find.

Flaky e2e tests (unrelated, encountered throughout)

Each passed on a re-run of the identical commit with no code change. All are in fresh-editor::e2e_tests; nothing in fresh-update or on the update path has ever flaked.

Test Platform Failure
e2e::code_tour_dock::test_next_key_advances_step ubuntu TIMEOUT 180.474s
e2e::code_tour_dock::test_step_range_is_highlighted_in_the_editor windows TIMEOUT 180.093s
e2e::plugins::gutter::test_git_gutter_updates_after_save macos TIMEOUT 180.594s
e2e::plugins::live_diff::test_live_diff_virtual_line_anchored_to_correct_modified_line macos FAIL 1.058s
e2e::plugins::review_diff_ux_bugs::test_issue2117_discard_hunk_with_no_trailing_newline windows FAIL 2.342s

Two distinct shapes: 180s timeouts (plugin-startup timing) and sub-3s assertion failures (genuinely non-deterministic, not slow). test_git_gutter_updates_after_save is red on master independently of any branch. test_issue2117... has also flaked on other PRs, making it the most frequent repeat offender.


What #2886 already fixes

For context, so none of the above reads as still-broken:

  • .deb / .rpm installs can find their receipt at all. debian/rules writes to PREFIX/share/fresh-editor/; receipt::candidate_paths only looked in PREFIX/share/fresh/. Every packaged install resolved to unknown/Unknown and was told it couldn't update itself. Both spellings are now searched — brew, flatpak and nix genuinely use share/fresh/, so neither side could be renamed without stranding installs.
  • The update indicator no longer lies. It had two terminal states for three outcomes: it reported failure when nothing had failed, and success when nothing had been installed. SelfUpdatePhase::ActionRequired, keyed off a new exit code 2, is the third.
  • .deb/.rpm updates finish in one step. The old path downloaded the package and handed the user a dpkg -i to go and run. It now elevates with sudo inside the interactive update terminal, so the password prompt works where the user already is.
  • The confirmation popup is built from the resolved UpdatePlan — "Update to vX now" / "Show the command" / "Dismiss" — instead of one hardcoded row that meant something different in every channel.
  • One mechanism per provenance class; every runtime fallback removed. AUR no longer prefers yay when it happens to be installed (two identical receipts must not update by different routes) — it uses git clone + makepkg, which works for every AUR user. Privileged installs no longer probe for doas or silently run unprivileged. Asset names are no longer read from the receipt, since a name written at install time describes the version you already have.
  • Channels naming commands for packages that were never published are fixed or routed to Manual: zypper installs the release .rpm, pacman goes back to the AUR, mise uses github:sinelaw/fresh.
  • No path dead-ends at the releases page except genuinely unknown/source installs.
  • CI asserts resolved provenance from the installed binary rather than test -f plus grep — the old check proved the file was in the package, which is exactly why it stayed green through the bug above.
  • A local root escalation was closed. Packages were staged under a predictable name in world-writable /tmp and then read by sudo dpkg -i, so anything able to write that path between the checksum check and the install got its bytes installed as root. Staging is now a 0700 directory created atomically with an unpredictable name.
  • Non-2xx HTTP responses are errors. The download helper is built with http_status_as_error(false) and writes nothing on a 404, so a missing asset previously surfaced as "read download: no such file".

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions