Skip to content

feat(linux): port transactional installer upgrade mode and in-app updater - #360

Merged
KnifeLemon merged 10 commits into
KnifeLemon:mainfrom
manish0467:feat/linux-updater-2.0.0
Oct 23, 2026
Merged

KnifeLemon merged 10 commits into
KnifeLemon:mainfrom
manish0467:feat/linux-updater-2.0.0

Conversation

@manish0467

Copy link
Copy Markdown

What does this change?

Adds a Linux in-app updater for Filee 2.0.0, including transactional installation, rollback on failure, release checksum verification, and safe application restart handling.

The updater supports Linux x64 and ARM64 releases and aims to preserve user settings and downloaded conversion engines during upgrades.

How was it tested?

  • dotnet format --verify-no-changes
  • Targeted updater and Linux installer tests
  • ShellCheck
  • Full dotnet test run in a clean CI environment
  • End-to-end in-app update and restart test on a Linux desktop

Additional checks passed locally, including Core, CLI, Platform, and non-render App test suites. Some engine tests failed in the local environment, and render tests were not run in the final audit.

Implementation notes

  • Validates release archive checksums before extraction.
  • Hardens archive extraction and temporary update workspace handling.
  • Uses a staging directory, backup, and rollback flow for installation.
  • Verifies the installed version before reporting update success.
  • Keeps Windows installation detection and updater behavior intact.

Review notes

Please review the Linux update lifecycle, archive extraction safeguards, rollback behavior, and restart handling. This branch has been tested locally; its CI status should be verified after submission.

@github-actions github-actions Bot added the feature New features label Oct 21, 2026
@KnifeLemon KnifeLemon self-assigned this Oct 22, 2026

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, @manish0467! A Linux in-app updater is something Filee is missing, and the work shows. The tar extraction is defensive, install.sh --upgrade checks the marker, stages on the same filesystem and rolls back from a trap, and there are plenty of tests. The FindFiles change that stops Windows from picking a -linux-/-osx- SHA256SUMS file also fixes a latent bug on main. Thank you.

Before we can merge, a few things need another pass:

  1. Quit first, then swap. The in-app path skips --quit (FILEE_IN_APP_UPDATE=1), so install.sh moves the running app’s folder aside and deletes it while the old Filee is still running. After that, the old process verifies the version and shuts down while loading any assemblies it hadn’t loaded yet from the new files, and running conversions lose their bundled engines. Please follow the Windows order instead: start a small detached helper (it can be the same /bin/sh watcher), let Filee exit, have the helper run install.sh --upgrade once the PID is gone, then start the new Filee. Start it with --background --whats-new as Setup does, so the “What’s new” slides show.
  2. No test hooks in the shipped installer. Please move the FILEE_TEST* fault injection (install.sh lines 43–52 and 218–278) out of install.sh into the tests, for example by putting a failing mv shim first on PATH or by changing permissions from the test itself.
  3. Please split out the LibreOffice/Ghostscript --version changes. They’re unrelated to the updater and not in the description. Running soffice --version inside GetStatus() is a blocking external call of up to 3 s, and on Windows soffice.exe prints nothing anyway. A separate PR is welcome.
  4. Keep the Windows path as it was. Using a fresh random workspace on Windows means a successful update leaves the ~120 MB Setup.exe in %TEMP% every time, because Setup closes Filee before the cleanup runs. It also breaks reuse of the download after a declined UAC prompt. Please keep %TEMP%\Filee-update on Windows, or clean up old workspaces at startup.
  5. Don’t stage in XDG_RUNTIME_DIR. It’s a RAM-backed tmpfs that is usually 10% of RAM, and 119 MB plus the unpacked tree can fill it on small machines. Something like $XDG_DATA_HOME/filee/.update-* (same filesystem as the install) or ~/.cache works better.
  6. FindLinuxRecoveryInfo gets AppContext.BaseDirectory with a trailing /, so Path.GetDirectoryName returns the application folder itself and never finds .backup-*. Please trim the separator first.
  7. Please capture install.sh’s stdout/stderr into Filee’s log. Right now only the exit code is logged, so rollback messages are lost.
  8. Smaller items:
  • Drop the unused --restart argument, the undocumented FILEE_INSTALL_DIR and the .filee-version fallback.
  • Mask tar modes to rwx bits so no setuid/setgid survives.
  • Reuse App.Quit().
  • Use Assert.Skip(...) rather than return in LinuxInstallerTests on other OSes.
  • Skip DeleteSecureWorkspace_refuses_to_follow_symlink when symlinks can’t be created (it fails on Windows without Developer Mode).
  • Add a short “Linux updates” note to docs/ARCHITECTURE.md next to the Windows installer section.

Note: #367 also rewrites the top of installer/linux/install.sh, in a way that conflicts with this. We’ll sort that out on our side.

CI hasn’t run on this branch yet. It’s waiting for approval, and we’ll start it once the next push lands. Thanks again; happy to help if any of this is unclear!

Alenbosh added 3 commits October 22, 2026 12:48
…ater and installer

- Start detached helper to wait for old Filee PID to exit before running install.sh --upgrade
- Launch new Filee instance with --background --whats-new upon successful update
- Reuse App.Quit() for graceful application shutdown before file swap
- Remove FILEE_TEST_* fault-injection hooks from shipped install.sh
- Implement fault injection in LinuxInstallerTests via external mv shim on PATH
- Remove --restart option, FILEE_INSTALL_DIR override, and .filee-version fallback
- Preserve Windows update directory %TEMP%\Filee-update and download reuse
- Securely stage Linux update archives in XDG_DATA_HOME/filee/.update-* (0700)
- Mask extracted tar entry permissions to RwxMask (0x1FF) stripping setuid/setgid/sticky
- Fix FindLinuxRecoveryInfo trailing path separator handling
- Redirect and append install.sh stdout and stderr to Filee daily log
- Update LinuxInstallerTests to use Assert.SkipUnless(OperatingSystem.IsLinux(), ...)
- Document Linux installer and update lifecycle in docs/ARCHITECTURE.md
@manish0467
manish0467 requested a review from KnifeLemon October 22, 2026 08:43
@manish0467

Copy link
Copy Markdown
Author

Thanks for the detailed review! I've pushed the follow-up fixes in c6e9d7e and the documentation cleanup in 26640d5.

I've addressed the requested updater lifecycle, installer test hooks, Windows workspace reuse, Linux staging and recovery handling, logging, archive permissions, and test portability changes. I've also separated the LibreOffice/Ghostscript version-probing changes into a separate local branch for a future PR.

I've requested another review. The workflow is still awaiting maintainer approval, so its CI results remain to be verified. Please let me know if anything needs another pass.

@KnifeLemon

Copy link
Copy Markdown
Owner

Thanks @manish0467! We'll start the CI run for 26640d5 and go through the changes again.

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @manish0467 for the quick and careful follow-up. I checked the eight points against 26640d5:

  1. Quit first, then swap: done. The helper waits for Filee's PID, runs install.sh --upgrade, and starts the new Filee with --background --whats-new; App.Quit is reused.
  2. No test hooks left in install.sh (and the test that sets the old variables is a nice touch).
  3. The LibreOffice/Ghostscript --version changes are out.
  4. Windows keeps %TEMP%\Filee-update.
  5. Staging is under $XDG_DATA_HOME/filee/.update-*.
  6. The trailing separator is trimmed.
  7. install.sh's output goes into the day's log.
  8. All the small items are done.

What's left:

  1. A failed update leaves Filee closed. When install.sh --upgrade fails and rolls back, the helper only writes "Update failed" to the log and exits, so Filee just disappears. The rollback puts the old version back at the same path, so the helper can start "$3" in both cases (--whats-new only on success). Ideally the restarted Filee then says the update didn't go through, for example from a small marker file the helper writes and UpdateService reads at start, pointing to the log.
  2. Dead code, and a test that fails on Windows. VerifyInstalledVersion, GetInstalledVersion (which starts filee-cli) and FindLinuxRecoveryInfo are no longer called by the app, only by tests. VerifyInstalledVersion_detects_mismatch runs a #!/bin/sh script, so it fails on Windows (UpdateServiceTests here: 44 passed, 1 skipped, this one failed), and CI's build job runs on Windows. Please remove the three methods and their tests.
  3. Leftovers. If the old Filee doesn't exit within 60 s, the helper exits before its rm -rf, so the ~125 MB archive and the unpacked payload stay in ~/.local/share/filee/.update-*. Please remove the workspace on that path too, and sweep stale .update-* folders at start (as JobQueue.DeleteStaleWorkFolders does for work folders).
  4. Concurrent_secure_workspaces_are_isolated calls CreateSecureWorkspace() without a base folder, so on Linux it creates folders in the developer's real ~/.local/share/filee. Please give it a temp folder.

Not blocking: the workspace is now on the same filesystem as the installation, but install.sh still copies the payload into .staging-*, so an update briefly needs the unpacked payload, the staging copy and the backup at once. Moving instead of copying when the payload comes from the updater's own workspace would halve that.

About #408: it's now limited to the Windows progress dialog, as we asked, and Linux in-app updating stays here. Both PRs change UpdateAsync and FindFiles, so whichever lands second gets rebased; we'll take care of that. The checksum rule here (exactly -SHA256SUMS.txt on Windows, skipping -linux-/-osx- files) is the one we want.

@manish0467

Copy link
Copy Markdown
Author

Thanks for the follow-up review! I've pushed 213bd40, addressing all four remaining points: restarting Filee after a failed update with a failure notification, removing the obsolete methods and shell-dependent tests, cleaning up timed-out and stale update workspaces, and isolating the concurrent-workspace test.

The targeted updater and installer tests, non-render App test suite, formatting, and ShellCheck all pass locally. The new GitHub Actions run is currently awaiting maintainer approval, so I'll verify the CI results once it runs.

Please let me know if anything else needs attention.

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @manish0467! I checked 213bd40 against the four points:

  1. A failed update now brings Filee back. After the rollback the helper starts "$3" --background and leaves .update-failed with the log path, which the next start reads and reports. On success it still starts with --whats-new.
  2. VerifyInstalledVersion, GetInstalledVersion and FindLinuxRecoveryInfo are gone, together with their tests.
  3. The timeout path removes the workspace too, and stale .update-* folders are swept at start.
  4. Concurrent_secure_workspaces_are_isolated uses a temp folder.

CI has run on this commit now. The App tests pass on Windows, UpdateServiceTests included. The build job is red only because Filee.Cli.Tests timed out while xUnit was discovering the tests ("Test process did not respond within 60 seconds"), which has nothing to do with this PR. We'll re-run it.

One thing to change:

The startup sweep runs on every platform. App.Start calls UpdateService.DeleteStaleWorkspaces() on Windows and macOS too, where the default root is Path.GetTempPath(). There it deletes every folder in the user's temp folder whose name starts with .update- and that is older than a day, including folders other programs made. A name without a number, such as .update-cache, skips the PID check and goes straight to the age check. Workspaces are only created on Linux, so please run the sweep only there, and only for names Filee creates (.update-<pid>-<32 hex characters>), with a test that a foreign .update-something folder is left alone.

On our side, nothing needed from you:

  • After a rolled-back update, the restarted Filee shows update.failed_alert ("Couldn't download the update…"), which isn't quite what happened, and the "Log:" line is in English for every language. We'll add a translated message for that case when we rebase.
  • CI runs the tests only on Windows, so LinuxInstallerTests and the helper-script tests are skipped there. We'll run them on Linux before merging.
  • #408 and #469 also change UpdateAsync and FindFiles and will go in first. We'll rebase this one (a few small conflicts in UpdateService.cs and its tests), keep your per-platform FindFiles, and run #469's signature check only in the Windows branch.

Thanks again for sticking with this!

@manish0467

Copy link
Copy Markdown
Author

I've pushed 8fd4bc5 with the additional workspace-cleanup safeguards. The stale-workspace sweep now runs only on Linux, validates Filee's exact .update-<pid>-<32-hex-characters> naming pattern before checking age or PID, and includes regression tests confirming foreign and malformed directories are preserved.

The targeted tests and formatting checks passed locally. Please let me know if anything else needs attention.

KnifeLemon and others added 4 commits October 23, 2026 18:18
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a failed install the helper rolls back and starts the old Filee, which
showed "Couldn't download the update" with an English "Log:" line. It now
says the update couldn't be installed and the previous version was kept
(update.rolled_back_alert), with the log path in a translated line
(update.rolled_back_log), in all 14 languages. A failure in the current run
keeps update.failed_alert without a log line from an earlier run.

DeleteStaleWorkspaces does nothing on other platforms when no root is given:
there the default root is the shared temp folder.

The Linux update test no longer starts a real `true`, which is missing on
Windows without Git's tools on PATH.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	src/Filee.App/Services/UpdateService.cs
#	tests/Filee.App.Tests/UpdateServiceTests.cs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@KnifeLemon
KnifeLemon merged commit 4c50992 into KnifeLemon:main Oct 23, 2026
19 of 20 checks passed
KnifeLemon added a commit to yanscholze/Filee that referenced this pull request Oct 23, 2026
install.sh: the GNOME extension step moves into KnifeLemon#360's first-install branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants