vmupdate: add qvm-template-upgrade - #213
nihalxkumar wants to merge 11 commits into
Conversation
ben-grande
left a comment
There was a problem hiding this comment.
Yay, started. As I've done a review now, I will receive a notification every time you commit. Let me know when you need another look or have doubts by mentioning me.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #213 +/- ##
==========================================
+ Coverage 71.36% 75.60% +4.23%
==========================================
Files 12 34 +22
Lines 1383 4648 +3265
==========================================
+ Hits 987 3514 +2527
- Misses 396 1134 +738 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
PipelineRetryFailed |
|
Some tests are not enabled on this repo, such as mypy, black and pylint. See this as an example: https://github.com/QubesOS/qubes-core-admin/blob/main/.gitlab-ci.yml. Can you create a separate PR to enable those checks? |
|
Sure, will do |
|
We can squash here if it's looks fine. Will also have to rebase as this is 24 commits behind. |
|
Looks fine to squash. |
71dee9e to
6c3d431
Compare
|
Oh no, commit message in the other PR closed it ... |
|
I see a conflict here - CI will not run until it's resolved. |
16b99f5 to
f1f8fc1
Compare
ben-grande
left a comment
There was a problem hiding this comment.
I have taken a look through the new code. Thanks for the progress, I like to see it evolving. Just minor review though
ad97dca to
a0289a7
Compare
|
Can you share more details of what fails on cleanup? |
|
When I tried upgrading fedora 41 -> 42 there was a cleanup failure which happened after the successful version upgrade. logs as shared privately on tuesday: https://gist.github.com/nihalxkumar/01fb990deab3960cb28d680773bb1089#file-upgrade42-log-L48-L55 we can see https://gist.github.com/nihalxkumar/0e4edccb4f4409b1a4daf5c44b6f576f After
|
|
But we can't ignore all cleanup failures. We don't know what is causing it, so this needs to be investigated. |
|
I tried to test manually what openQA complained: I got fedora-43-xfce template installed, and then ran I don't have any more details yet. Maybe setting |
|
The output (also seen in the openqa report) is: So, it clearly did set releasever, as seen in the fedora repository names. |
|
Running So, there are two issues:
Solving the second one might be tricky. Having rpmfusion-*-release packages installed is intentional, so users can easily enable it when needed, but it should still be up to the user to do that - that's why those repositories are disabled. The dependency there is strict, so just excluding rpmfusion packages from the upgrade won't help. What does work is temporarily enable the rpmfusion repositories ( So, what I'd propose is:
Do the same for |
|
@marmarek I tried on minimal fedora-43 template |
|
Well, yes, it worked on minimal because it doesn't have rpmfusion included... |
|
right. I currently have a running upgrade for fedora-43-xfce |
|
successful https://asciinema.org/a/9xZ74qLWNNnRDdc2 |
|
Ok, that's another approach, yes. Not perfect, but probably good enough for now. In any case, it would be good to fix reporting error on transaction resolve step. I found another way you can simulate the failure - edit |
|
Ok, now the transaction failure info is included in the log, which is good. But on the tool output it still have last status as Other than that, it seems to work now, and indeed removes rpmfusion package. openQA test says it works now :) |
|
I see a conflict now, and that prevents CI from running :( |
|
Yes, I am fixing |
Add a --version-upgrade mode to the in-VM agent so a qube can be moved to the next major distribution release. The dnf backend distro-syncs to the target releasever; the apt backend updates, rewrites source codenames, and dist-upgrades. Both confirm os-release before reporting success.
The agent emits a final explicit 100% even when callback progress already reached it, causing a bogus "err: 100.00" line. Drop the duplicate 100 milestone after progress has finished.
Upgrade a clone of a TemplateVM or StandaloneVM to the next distribution release, leaving the original qube untouched.
Document the options, the clone name derivation, and which template-* features the tool rewrites, including why the inherited epoch, version and release make qvm-template list the upgraded clone as upgradeable.
Progress hit 100% before post-transaction scriptlets finished, making the bar appear frozen for the remaining work. Cap package progress at 97% and advance asymptotically through post-transaction scriptlets. Report 100% after qubes.PostInstall so the bar stays responsive until everything is done.
During release upgrades the root volume briefly holds both releases, which can trigger a spurious disk-space warning toast. Suppress disk-space-not-notify on the clone during the upgrade and restore it afterwards.
Each Debian release-upgrade step drove the progress bar to 100%, silencing later steps. Give each step its own weighted slice of the bar so progress stays proportional throughout the upgrade.
9cfcc22 to
66580a6
Compare
|
@ben-grande are you happy with the current shape of this PR? |
| ] | ||
| except OSError as exc: | ||
| return self._refuse(f"cannot read apt sources: {exc}.") | ||
| if not any( |
There was a problem hiding this comment.
I think the any here is fine. It shouldn't be all because some sources might not even have a proper suite, such as
There was a problem hiding this comment.
This gives me an idea, a test that includes external repos, can even be a fake/localhost one, that doesn't have a codename in the sources file, just to guarantee that this any is not changed to all in the future.
| # Update before switching sources and running dist-upgrade: a stale | ||
| # system risks unresolvable transactions across the release boundary. | ||
| # Each step gets its own slice of the bar, weighted by its share of | ||
| # a measured debian-12 to 13 template upgrade; without that, every |
There was a problem hiding this comment.
Any tips on how to choose the step number in case something else needs to be added here? Please include a comment about it. As in, how it was measured to set this step configuration.
| encoding="utf-8", | ||
| newline="", | ||
| ) as releases_file: | ||
| for release in csv.DictReader(releases_file): |
There was a problem hiding this comment.
Please use csv.reader: https://docs.python.org/3/library/csv.html#csv.reader.
| if postinstall != 0: | ||
| self.log.warning( | ||
| "qubes.PostInstall exited with %d; dom0 metadata " | ||
| "(qvm-features) will refresh on next qube start.", |
There was a problem hiding this comment.
please change "dom0 metadata (qvm-features)" to "qube features".
| self, target: str, os_data: dict | ||
| ) -> ProcessResult: | ||
| """Verify the target is a valid single-step upgrade from the current release.""" | ||
| if not (target.isascii() and target.isdigit()): |
There was a problem hiding this comment.
Is there a digit that is not ASCII?
| self.log.debug( | ||
| "dnf package download for release %s took %.3fs", | ||
| target, | ||
| time.monotonic() - download_started, |
There was a problem hiding this comment.
I counted 13 occurrences of the pattern:
- record time
- run action
- log time difference
Can be made with contextlib.contextmanager.
| """Temporarily suppress qui-disk-space during upgrade. | ||
|
|
||
| The upgrade briefly holds both releases' packages, which can | ||
| trip the 90% threshold. This may bring unnecesarry panic for users. |
There was a problem hiding this comment.
Hm.. 90% is above what I expected. When we discussed about having enough space in the root file system for upgrades, I thought the threshold was bigger. You reached 90% while having how much total space in the root partition?
|
|
||
| fedora-41-minimal, 41 -> 42 => fedora-42-minimal | ||
|
|
||
| custom, 41 -> 42 => custom-42 |
There was a problem hiding this comment.
Not for this PR, but in the future, having an option to keep the same name when it doesn't have the version in the name, would be interesting for standalones and custom named templates.
| description="Upgrade a TemplateVM or StandaloneVM to the next distro " | ||
| "version.", | ||
| # Avoid qubesadmin package metadata lookup when run from PYTHONPATH. | ||
| version="", |
There was a problem hiding this comment.
Then show the version of the vmupdate?
| raise ValidationError( | ||
| f"{self.source_vm.name} is a {distro} derivative; its own " | ||
| f"version numbering does not match {family}'s releases, so " | ||
| f"it cannot be upgraded with this tool." |
There was a problem hiding this comment.
Examples:
- Ubuntu: has its own repos
- Kicksecure: has debian repos
- Whonix: has debian repos
I think Kicksecure and Whonix will break on this validation.
There was a problem hiding this comment.
I still think it is important to do a test run on Kicksecure and Whonix and let us know what happens. Whonix, different than other derivatives, doesn't mess with /etc/os-release for example.
Imagine a whonix-gateway-18, it has the following:
% cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
VERSION="13 (trixie)"
VERSION_CODENAME=trixie
DEBIAN_VERSION_FULL=13.4
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
If upgrading from whonix-gateway-12, which was bookworm, will the template be renamed to whonix-gateway-13?
There was a problem hiding this comment.
As upgrading Whonix and Kicksecure was out of the scope, if it fails, could you please open an issue for it to be tracked?
There was a problem hiding this comment.
No need to do it, just sharing the Whonix release upgrade instructions: https://www.whonix.org/wiki/Release_Upgrade_17_to_18, not as simple as just upgrading base distributions.
| raise ValidationError( | ||
| f"{self.source_vm.name} is a {distro} derivative; its own " | ||
| f"version numbering does not match {family}'s releases, so " | ||
| f"it cannot be upgraded with this tool." |
There was a problem hiding this comment.
As upgrading Whonix and Kicksecure was out of the scope, if it fails, could you please open an issue for it to be tracked?
| # clone has already halted. | ||
| try: | ||
| self.cloned_qube.kill() | ||
| except qubesadmin.exc.QubesVMNotStartedError: |
There was a problem hiding this comment.
When does this happen? Please document/comment.


This PR introduces the
qvm-template-upgradedom0 command-line utility, that performs an in-place N -> N+1 distribution upgrade of Debian and Fedora TemplateVM or StandaloneVMfixes: QubesOS/qubes-issues#8605
GSoC 2026 project: Automate Template Version Upgrade