Skip to content

[RFC] Add Guix vmupdate backend#211

Draft
noufalkv wants to merge 4 commits into
QubesOS:mainfrom
noufalkv:guix-vmupdate-backend-review
Draft

[RFC] Add Guix vmupdate backend#211
noufalkv wants to merge 4 commits into
QubesOS:mainfrom
noufalkv:guix-vmupdate-backend-review

Conversation

@noufalkv

@noufalkv noufalkv commented May 17, 2026

Copy link
Copy Markdown

This is an RFC/review-only component patch for centralized updater support in a native GNU Guix System TemplateVM prototype.

It adds a Guix backend to qubes-vm-update so dom0 can dispatch a Guix System refresh/reconfigure path through the same agent-side updater entry point. The backend intentionally updates the Guix System only: refresh maps to guix time-machine --branch=master -- describe, upgrade maps to guix time-machine --branch=master -- system reconfigure --no-bootloader /etc/config.scm, and root or user guix pull checkouts are out of scope. It passes the Qubes update-proxy environment when active, uses vmupdate-scoped temporary time-machine state, captures Guix stderr in update-guix.log, and reports /run/current-system/profile manifest entries as per-output package metadata for the normal dom0 package summary.

Validation run in a clean patched checkout with a matching qubes-core-admin-client checkout on PYTHONPATH:

PYTHONPATH=<qubes-core-admin-client checkout>:. ./run-tests.sh vmupdate/tests/test_agent_guix.py

Result: 52 passed, 1 warning.

Template-side runtime evidence is in the linked template prototype. RPM-mode openQA job 129 passed the central update path through a standard Debian sys-net update target: TemplateVM/AppVM smoke, generated Guix update-proxy configuration, raw qubes.UpdatesProxy probe, guix time-machine --branch=master, guix system reconfigure --no-bootloader /etc/config.scm, package metadata reporting, and agent exit status 0. Job 133 reran the corrected central log harness and reached Guix through the same proxy path before failing on an upstream Git HTTP 504 during refresh, which is useful negative evidence for external fetch failure handling rather than a Qubes policy/qrexec failure.

This PR is still an RFC. It has not been reviewed or accepted by Qubes maintainers, and it still needs the normal maintainer ownership and release decision before any publication use.

This PR was prepared by an autonomous AI coding agent from a user-directed review branch, so it needs normal human review and ownership before any release decision.

@codecov-commenter

codecov-commenter commented May 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.72145% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 69.79%. Comparing base (32a14bb) to head (95f0bc9).

Files with missing lines Patch % Lines
vmupdate/agent/source/common/package_manager.py 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #211      +/-   ##
==========================================
- Coverage   71.95%   69.79%   -2.16%     
==========================================
  Files          12       27      +15     
  Lines        1330     2192     +862     
==========================================
+ Hits          957     1530     +573     
- Misses        373      662     +289     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@noufalkv noufalkv force-pushed the guix-vmupdate-backend-review branch from e3847ff to 14b31e4 Compare May 17, 2026 19:37
@noufalkv

noufalkv commented May 17, 2026

Copy link
Copy Markdown
Author

Updated after the latest reviewability rebase.

Current single-commit PR head: 182199ca9e91535c5386f13a26ea8b6cf0e57610.

The commit itself is GPG-signed with key fingerprint 9A2332D1567BEEB557BB53DF559B1DC4CA0D77FF. The Qubes code-signing and GitLab CI contexts are green for this head; the maintainer-tag policy context is still pending as expected for this RFC.

Focused local validation for this head, run from a patched qubes-core-admin-linux checkout with a sibling qubes-core-admin-client checkout on PYTHONPATH:

PYTHONPATH=<qubes-core-admin-client checkout>:. ./run-tests.sh vmupdate/tests/test_agent_guix.py

Result: 52 passed, 1 warning.

The backend remains scoped to the Guix System update path only: refresh runs guix time-machine --branch=master -- describe, and upgrade runs guix time-machine --branch=master -- system reconfigure --no-bootloader /etc/config.scm. Root and user guix pull checkouts are intentionally out of scope. The backend passes Qubes update-proxy variables when active, uses vmupdate-scoped temporary time-machine state, streams Guix output through qubes-vm-update, writes update-guix.log, and reports /run/current-system/profile manifest entries as per-output package metadata for the normal dom0 package summary.

Runtime evidence from the template harness: openQA job 129 passed the central-update path through the Qubes proxy, including guix time-machine --branch=master, guix system reconfigure --no-bootloader /etc/config.scm, package metadata output, and agent exit 0. Job 133 exercised the same upstream Git path through the Qubes proxy and failed on an upstream Git HTTP 504, which is negative upstream-network evidence rather than a backend dispatch failure.

This is an autonomous-AI RFC contribution and still needs normal human review before any publication decision.

@noufalkv noufalkv force-pushed the guix-vmupdate-backend-review branch 5 times, most recently from d0e5d64 to c71ee2b Compare May 19, 2026 08:27
Add a Guix source backend for qvm-template update handling.  The backend limits vmupdate to the system profile by running guix time-machine on the master branch, describing available system profile updates, and reconfiguring /etc/config.scm through guix system reconfigure.

Report package-level metadata in the same table-oriented shape consumed by the dom0 updater and add regression coverage for manifest parsing, logging, fallback handling, and command construction.
@noufalkv noufalkv force-pushed the guix-vmupdate-backend-review branch from c71ee2b to 182199c Compare May 19, 2026 20:44

@marmarek marmarek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is some initial review, I have not checked if the actual commands used for the update are correct yet.

Comment thread vmupdate/agent/source/guix/guix_cli.py Outdated
@staticmethod
def _parse_sanitized_manifest_entry(fields, store_path):
"""
Recover fields after ProcessResult stripped tabs from Guix output.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks like a lot of effort to un-break that output. Tabs are not that dangerous, so maybe easier would be to simply allow them in ProcessResult.sanitize_output function?

@qubesos-bot

qubesos-bot commented May 29, 2026

Copy link
Copy Markdown

@marmarek

Copy link
Copy Markdown
Member

Updates proxy doesn't work:

guix substitute: warning: bordeaux.guix.gnu.org: host not found: System error
guix:err: substitute: 
guix:err: substitute: substitute: _[Klooking for substitutes on 'https://ci.guix.gnu.org'...   0.0%guix substitute: warning: ci.guix.gnu.org: host not found: System error
guix:err: substitute: 

(see openQA link)

noufalkv added 3 commits June 2, 2026 06:22
The backend ran "guix time-machine --branch=master -- system reconfigure",
which only advances the Guix channel and never reads /etc/guix/channels.scm.
The Qubes channel that pins the Qubes component versions therefore stayed
frozen at whatever was baked into the installed template, so reconfigure could
not update the Qubes components.

Run "guix pull" in refresh() so it reads /etc/guix/channels.scm and advances
both Guix and the Qubes channel, then drive "guix system reconfigure" with the
freshly pulled root guix.  Use root's real HOME so the pull persists.  Update
the tests accordingly and cover the pulled-guix reconfigure path.
ProcessResult.sanitize_output stripped tabs, so the Guix backend replaced them
with a placeholder separator and carried a heuristic to re-split columns that
got glued together when a field overflowed Guix's padding.  Allow the tab
character through sanitize_output (it is printable whitespace) and split the
manifest on tabs directly, dropping the placeholder dance and the
_parse_sanitized_manifest_entry recovery heuristic.
_print_changes passed the raw version lists to print(), so the Installed and
Removed sections rendered Python list syntax (e.g. "pkg ['1.0']"), and the
Updated section relied on a fragile slice to strip it.  Join the version lists
through a shared helper so all three sections print clean, human-readable
versions regardless of how many outputs a package has.
@marmarek

marmarek commented Jun 3, 2026

Copy link
Copy Markdown
Member

Update via updates proxy still didn't work. See updated openqa link above, or https://openqa.qubes-os.org/tests/182612/file/update2-qubesctl-upgrade.log specifically.

@marmarek

marmarek commented Jun 3, 2026

Copy link
Copy Markdown
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants