patches and low-level development discussion
 help / color / mirror / code / Atom feed
From: Demi Marie Obenour <demiobenour@gmail.com>
To: Spectrum OS Development <devel@spectrum-os.org>
Cc: Demi Marie Obenour <demiobenour@gmail.com>, Alyssa Ross <hi@alyssa.is>
Subject: [PATCH v3 12/14] Support updates via systemd-sysupdate
Date: Wed, 19 Nov 2025 03:18:35 -0500	[thread overview]
Message-ID: <20251119-updates-v3-12-b88a99915509@gmail.com> (raw)
In-Reply-To: <20251119-updates-v3-0-b88a99915509@gmail.com>

Include a new `spectrum-update` command to update the system.  This
tells the new sys.appvm-systemd-sysupdate VM to download the updates
into a staging directory using systemd-sysupdate.  The host then runs
systemd-sysupdate to apply the updates itself.

sys.appvm-systemd-sysupdate uses host-provided information to fetch the
update.  This allows editing files on the host to change the update URL
and signing key.

Updates require /boot to be mounted so that systemd-sysupdate can update
the unified kernel image.  They also require that /tmp is writable so
that they can store temporary files, so put a tmpfs there.  Furthermore,
there needs to be a directory for storing downloaded updates.  Create
/home so that users can mount their persistent data there.

The directory the VM downloads updates into is *not* reset (wiped)
before or after the update.  This allows the VM to know if the system is
already up to date.  Otherwise, it would redownload the entire
multi-gigabyte update image.

Updates are currently not compressed.  This should be changed in the
future, but it would add a small amount of additional complexity.  In
particular, the script generating the update directory would need to
generate a SHA256SUMS containing the hash of both the compressed and
uncompressed versions.  More importantly, the VM must not be able to
make the host use the compressed version.  This would be a potential
security risk because decompression happens before signature
verification.  GnuPG currently decompresses signatures, but in the
future it will be replaced by Sequoia which does not.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
---
Changes since v2:

- Generate the transfer files in the guest, not the host.
- Do not use an environment file.
- Reject URLs that cannot work.
- Escape sed metacharacters.
- Escape backslashes for systemd-sysupdate.
- Do not validate update URLs at build time, only at runtime.
- Reject only update URLs that cannot possibly work.
- Set the partition UUIDs according to systemd's recommendation.
- Do not rely on finding partitions by label.
- Strip leading and trailing whitespace from the update URL.
- Rename the update command from `update` to `spectrum-update`.
- Delete the list of steps.  Replace it with comments in the script.

The awk script in the VM rejects URLs that contain whitespace.  This is
because they can't work, and passing them to systemd-sysupdate would
require figuring out how to escape them.  Rejecting such bogus URLs is
simpler than preventing them from being mangled.
---
 host/rootfs/Makefile                               | 19 ++++-
 host/rootfs/default.nix                            | 14 ++--
 host/rootfs/file-list.mk                           |  7 ++
 host/rootfs/image/etc/fstab                        |  1 +
 .../image/etc/sysupdate.d/50-verity.transfer       | 20 ++++++
 host/rootfs/image/etc/sysupdate.d/60-root.transfer | 20 ++++++
 .../image/etc/sysupdate.d/70-kernel.transfer       | 20 ++++++
 .../image/etc/vm-sysupdate.d/50-verity.transfer    | 18 +++++
 .../image/etc/vm-sysupdate.d/60-root.transfer      | 18 +++++
 .../image/etc/vm-sysupdate.d/70-kernel.transfer    | 18 +++++
 host/rootfs/image/usr/bin/spectrum-update          | 83 ++++++++++++++++++++++
 host/rootfs/os-release.in                          | 15 ++++
 lib/config.default.nix                             |  2 +
 lib/config.nix                                     | 11 ++-
 lib/fake-update-signing-key.gpg                    |  3 +
 release/live/shell.nix                             |  3 +-
 vm/app/systemd-sysupdate/default.nix               | 57 +++++++++++++++
 vm/app/systemd-sysupdate/escape-url.awk            | 31 ++++++++
 .../systemd-sysupdate/populate-transfer-directory  | 26 +++++++
 19 files changed, 376 insertions(+), 10 deletions(-)

diff --git a/host/rootfs/Makefile b/host/rootfs/Makefile
index 0d71dd1441077c9f30945e5063aacbfc240a9006..fe728b6b385862487dab06d832e79b29d57697a8 100644
--- a/host/rootfs/Makefile
+++ b/host/rootfs/Makefile
@@ -9,6 +9,7 @@ include file-list.mk
 ROOT_FS_DIR = build
 
 DIRS = \
+	boot \
 	dev \
 	etc/s6-linux-init/env \
 	etc/s6-linux-init/run-image/configs \
@@ -38,13 +39,15 @@ DIRS = \
 	etc/s6-linux-init/run-image/vm/by-id \
 	etc/s6-linux-init/run-image/vm/by-name \
 	ext \
+	home \
 	proc \
 	run \
-	sys
+	sys \
+	tmp
 
 FIFOS = etc/s6-linux-init/run-image/service/s6-svscan-log/fifo
 
-BUILD_FILES = build/etc/s6-rc
+BUILD_FILES = build/etc/s6-rc build/etc/os-release build/etc/update-url
 
 # This rule produces three files but Make only (portably)
 # supports one output per rule.  Instead of resorting to temporary
@@ -62,12 +65,22 @@ $(ROOT_FS): ../../scripts/make-erofs.sh $(PACKAGES_FILE) $(FILES) $(BUILD_FILES)
 	mkdir -p $(ROOT_FS_DIR) && \
 	{ \
 	    cat $(PACKAGES_FILE) ;\
+	    printf '%s\n%s\n' "$$UPDATE_SIGNING_KEY" /etc/systemd/import-pubring.gpg; \
 	    for file in $(FILES) $(LINKS); do printf '%s\n%s\n' $$file "$${file#image/}"; done ;\
-	    for file in $(BUILD_FILES); do printf '%s\n%s\n' $$file $${file#build/}; done ;\
+	    for file in $(BUILD_FILES) $(BUILD_NON_TARGET_FILES); do printf '%s\n%s\n' $$file $${file#build/}; done ;\
 	    printf 'build/empty\n%s\n' $(DIRS) ;\
 	    printf 'build/fifo\n%s\n' $(FIFOS) ;\
 	} | ../../scripts/make-erofs.sh $(ROOT_FS)
 
+build/etc/update-url:
+	mkdir -p build/etc
+	# might have metacharacters, so avoid interpolation
+	printf %s\\n "$${UPDATE_URL:?'update URL empty or missing'}" > build/etc/update-url
+
+build/etc/os-release:
+	mkdir -p build/etc
+	sed 's/@VERSION@/$(VERSION)/g' < os-release.in > build/etc/os-release
+
 build/fifo:
 	mkdir -p build
 	mkfifo -m 0600 $@
diff --git a/host/rootfs/default.nix b/host/rootfs/default.nix
index 26d6dc3f9a63680bf1867c9769f807dc562c81c9..f0b7e061a1f39b3e70d337ef4fe14c98a8f022c8 100644
--- a/host/rootfs/default.nix
+++ b/host/rootfs/default.nix
@@ -5,6 +5,7 @@
 import ../../lib/call-package.nix (
 { callSpectrumPackage, spectrum-build-tools, src
 , pkgsMusl, pkgsStatic, linux_latest
+, config
 }:
 pkgsStatic.callPackage (
 
@@ -13,6 +14,7 @@ pkgsStatic.callPackage (
 , busybox, cloud-hypervisor, cryptsetup, dbus, execline, inkscape
 , iproute2, inotify-tools, jq, mdevd, s6, s6-linux-init, socat
 , util-linuxMinimal, virtiofsd, xorg, xdg-desktop-portal-spectrum-host
+, btrfs-progs
 }:
 
 let
@@ -33,8 +35,8 @@ let
   foot = pkgsGui.foot.override { allowPgo = false; };
 
   packages = [
-    cloud-hypervisor cryptsetup dbus execline inotify-tools iproute2
-    jq mdevd s6 s6-linux-init s6-rc socat spectrum-host-tools
+    btrfs-progs cloud-hypervisor cryptsetup dbus execline inotify-tools
+    iproute2 jq mdevd s6 s6-linux-init s6-rc socat spectrum-host-tools
     virtiofsd xdg-desktop-portal-spectrum-host
 
     (busybox.override {
@@ -80,11 +82,13 @@ let
     appvm-firefox = callSpectrumPackage ../../vm/app/firefox.nix {};
     appvm-foot = callSpectrumPackage ../../vm/app/foot.nix {};
     appvm-gnome-text-editor = callSpectrumPackage ../../vm/app/gnome-text-editor.nix {};
+    appvm-systemd-sysupdate = callSpectrumPackage ../../vm/app/systemd-sysupdate {};
   };
 
   packagesSysroot = runCommand "packages-sysroot" {
     depsBuildBuild = [ inkscape ];
     nativeBuildInputs = [ xorg.lndir ];
+    src = builtins.path { name = "os-release"; path = ./os-release.in; };
   } ''
     mkdir -p $out/usr/bin $out/usr/share/dbus-1/services \
       $out/usr/share/icons/hicolor/20x20/apps
@@ -96,8 +100,7 @@ let
     done
 
     # If systemd-pull is missing systemd-sysupdate will fail with a
-    # very confusing error message.  If systemd-sysupdate doesn't work,
-    # users will not be able to receive an update that fixes the problem.
+    # very confusing error message.
     for i in sysupdate pull; do
         if ! cat -- "$out/usr/lib/systemd/systemd-$i" > /dev/null; then
             echo "link to systemd-$i didn't get installed" >&2
@@ -147,6 +150,9 @@ stdenvNoCC.mkDerivation {
       printf "%s\n/\n" ${packagesSysroot} >$out
       sed p ${writeClosure [ packagesSysroot] } >>$out
     '';
+    UPDATE_SIGNING_KEY = config.update-signing-key;
+    UPDATE_URL = config.update-url;
+    VERSION = config.version;
   };
 
   # The Makefile uses $(ROOT_FS_DIR), not $(dest), so it can share code
diff --git a/host/rootfs/file-list.mk b/host/rootfs/file-list.mk
index 9acaa1d90bed674814775becf89c1c847d0ce3e3..dc2f3f353b62931cb046e13aa757528a4587025f 100644
--- a/host/rootfs/file-list.mk
+++ b/host/rootfs/file-list.mk
@@ -42,13 +42,20 @@ FILES = \
 	image/etc/s6-linux-init/run-image/service/xdg-desktop-portal-spectrum-host/template/notification-fd \
 	image/etc/s6-linux-init/run-image/service/xdg-desktop-portal-spectrum-host/template/run \
 	image/etc/s6-linux-init/scripts/rc.init \
+	image/etc/sysupdate.d/50-verity.transfer \
+	image/etc/sysupdate.d/60-root.transfer \
+	image/etc/sysupdate.d/70-kernel.transfer \
 	image/etc/udev/rules.d/99-spectrum.rules \
+	image/etc/vm-sysupdate.d/50-verity.transfer \
+	image/etc/vm-sysupdate.d/60-root.transfer \
+	image/etc/vm-sysupdate.d/70-kernel.transfer \
 	image/etc/xdg/weston/autolaunch \
 	image/etc/xdg/weston/weston.ini \
 	image/usr/bin/assign-devices \
 	image/usr/bin/create-vm-dependencies \
 	image/usr/bin/run-appimage \
 	image/usr/bin/run-vmm \
+	image/usr/bin/spectrum-update \
 	image/usr/bin/vm-console \
 	image/usr/bin/vm-import \
 	image/usr/bin/vm-start \
diff --git a/host/rootfs/image/etc/fstab b/host/rootfs/image/etc/fstab
index 6a82ecc85090a37b13603b29f74ca6e554a28c33..36e073c58b91e81d63ae40a6aa6f019a2f03e546 100644
--- a/host/rootfs/image/etc/fstab
+++ b/host/rootfs/image/etc/fstab
@@ -4,3 +4,4 @@ proc	/proc		proc	defaults		0	0
 devpts	/dev/pts	devpts	defaults,gid=4,mode=620	0	0
 tmpfs	/dev/shm	tmpfs	defaults		0	0
 sysfs	/sys		sysfs	defaults		0	0
+tmpfs	/tmp		tmpfs	defaults		0	0
diff --git a/host/rootfs/image/etc/sysupdate.d/50-verity.transfer b/host/rootfs/image/etc/sysupdate.d/50-verity.transfer
new file mode 100644
index 0000000000000000000000000000000000000000..9cd64b58ae55d55d378d99f5701f1ecef867e436
--- /dev/null
+++ b/host/rootfs/image/etc/sysupdate.d/50-verity.transfer
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+
+# Uses example code from systemd man pages which is under MIT-0
+# (no attribution required).
+[Transfer]
+ProtectVersion=%A
+
+[Source]
+Type=url-file
+Path=file:///run/updater
+MatchPattern=Spectrum_@v_@u.verity
+
+[Target]
+Type=partition
+Path=auto
+MatchPattern=Spectrum_@v.verity
+MatchPartitionType=root-verity
+PartitionFlags=0
+ReadOnly=1
diff --git a/host/rootfs/image/etc/sysupdate.d/60-root.transfer b/host/rootfs/image/etc/sysupdate.d/60-root.transfer
new file mode 100644
index 0000000000000000000000000000000000000000..cd12d2bd2b4ecd9bb5c7d26cc7c27a4bdb74cac8
--- /dev/null
+++ b/host/rootfs/image/etc/sysupdate.d/60-root.transfer
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+
+# Uses example code from systemd man pages which is under MIT-0
+# (no attribution required).
+[Transfer]
+ProtectVersion=%A
+
+[Source]
+Type=url-file
+Path=file:///run/updater
+MatchPattern=Spectrum_@v_@u.root
+
+[Target]
+Type=partition
+Path=auto
+MatchPattern=Spectrum_@v
+MatchPartitionType=root
+PartitionFlags=0
+ReadOnly=1
diff --git a/host/rootfs/image/etc/sysupdate.d/70-kernel.transfer b/host/rootfs/image/etc/sysupdate.d/70-kernel.transfer
new file mode 100644
index 0000000000000000000000000000000000000000..e4190587a6bb127cb7315f38d59e48cf279318a4
--- /dev/null
+++ b/host/rootfs/image/etc/sysupdate.d/70-kernel.transfer
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+
+# Uses example code from systemd man pages which is under MIT-0
+# (no attribution required).
+[Transfer]
+ProtectVersion=%A
+
+[Source]
+Type=url-file
+Path=file:///run/updater
+MatchPattern=Spectrum_@v.efi
+
+[Target]
+Type=regular-file
+Path=/EFI/Linux
+PathRelativeTo=boot
+MatchPattern=Spectrum_@v.efi
+Mode=0644
+InstancesMax=2
diff --git a/host/rootfs/image/etc/vm-sysupdate.d/50-verity.transfer b/host/rootfs/image/etc/vm-sysupdate.d/50-verity.transfer
new file mode 100644
index 0000000000000000000000000000000000000000..ab4997c83605e3820a22b0b2178dcd76dfcf787e
--- /dev/null
+++ b/host/rootfs/image/etc/vm-sysupdate.d/50-verity.transfer
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+
+# Uses example code from systemd man pages which is under MIT-0
+# (no attribution required).
+[Transfer]
+Verify=yes
+
+[Source]
+Type=url-file
+Path=@UPDATE_URL@
+MatchPattern=Spectrum_@v_@u.verity
+
+[Target]
+Type=regular-file
+Path=/run/virtiofs/virtiofs0/updates
+MatchPattern=Spectrum_@v_@u.verity
+Mode=0644
diff --git a/host/rootfs/image/etc/vm-sysupdate.d/60-root.transfer b/host/rootfs/image/etc/vm-sysupdate.d/60-root.transfer
new file mode 100644
index 0000000000000000000000000000000000000000..8a3175684f1697e0eca443eb0a1a97176e4f66d4
--- /dev/null
+++ b/host/rootfs/image/etc/vm-sysupdate.d/60-root.transfer
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+
+# Uses example code from systemd man pages which is under MIT-0
+# (no attribution required).
+[Transfer]
+Verify=yes
+
+[Source]
+Type=url-file
+Path=@UPDATE_URL@
+MatchPattern=Spectrum_@v_@u.root
+
+[Target]
+Type=regular-file
+Path=/run/virtiofs/virtiofs0/updates
+MatchPattern=Spectrum_@v_@u.root
+Mode=0644
diff --git a/host/rootfs/image/etc/vm-sysupdate.d/70-kernel.transfer b/host/rootfs/image/etc/vm-sysupdate.d/70-kernel.transfer
new file mode 100644
index 0000000000000000000000000000000000000000..cb181239d71c5a6d0a5b3652d5534a23eda64183
--- /dev/null
+++ b/host/rootfs/image/etc/vm-sysupdate.d/70-kernel.transfer
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+
+# Uses example code from systemd man pages which is under MIT-0
+# (no attribution required).
+[Transfer]
+Verify=yes
+
+[Source]
+Type=url-file
+Path=@UPDATE_URL@
+MatchPattern=Spectrum_@v.efi
+
+[Target]
+Type=regular-file
+Path=/run/virtiofs/virtiofs0/updates
+MatchPattern=Spectrum_@v.efi
+Mode=0644
diff --git a/host/rootfs/image/usr/bin/spectrum-update b/host/rootfs/image/usr/bin/spectrum-update
new file mode 100755
index 0000000000000000000000000000000000000000..ad598b557ac1cc4e9b95ff65a53a68f04d3759ee
--- /dev/null
+++ b/host/rootfs/image/usr/bin/spectrum-update
@@ -0,0 +1,83 @@
+#!/bin/execlineb -WS1
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+
+if { mkdir -p -m 0700 /run/updater }
+
+# Take a global lock to avoid races.
+s6-setlock /run/update-lock
+
+foreground { redirfd -w 2 /dev/null rmdir -- $1 }
+if { umask 0077 mkdir -p -- $1 }
+cd $1
+foreground {
+  # If this exists already that is okay.
+  foreground { redirfd -w 2 /dev/null btrfs subvolume create -- shared }
+
+  # Delete any stale temporary files.  Delete any existing signature
+  # files.  If the VM is still running (it should not be), the VM might
+  # have write access to the directory.  However, updates-dir-check is
+  # safe against that.
+  if { updates-dir-check cleanup shared }
+
+  if {
+    # rm -f ensures that "snapshot" does not exist afterwards.
+    ifte { exit 0 } { rm -f snapshot }
+    # TODO: suppress only "subvolume does not exist" errors.
+    redirfd -w 2 /dev/null btrfs subvolume delete snapshot
+  }
+
+  backtick -E update_vm_id {
+    backtick -E id_path { readlink /run/vm/by-name/sys.appvm-systemd-sysupdate }
+    basename -- $id_path
+  }
+
+  # $fsdir is read-only to the guest, but read-write to the host.
+  # Directories bind-mounted into it are read-write to the guest.
+  # See etc/s6-linux-init/run-image/service/vhost-user-fs/template/run
+  # for details.
+
+  # Set up /etc with what the VM needs.  The VM will overlay this
+  # on its own /etc.
+  if {
+    if { rm -rf -- /run/vm/by-id/${update_vm_id}/fs/etc }
+    umask 022
+    if { mkdir -p -- /run/vm/by-id/${update_vm_id}/fs/updates /run/vm/by-id/${update_vm_id}/fs/etc/systemd }
+    if { cp -R -- /etc/vm-sysupdate.d /etc/update-url /run/vm/by-id/${update_vm_id}/fs/etc }
+    cp -- /etc/systemd/import-pubring.gpg /run/vm/by-id/${update_vm_id}/fs/etc/systemd
+  }
+
+  # If the directory is already mounted, unmount it.  This prevents a
+  # confusing error from mount.
+  foreground { redirfd -w 2 /dev/null umount -- /run/vm/by-id/${update_vm_id}/fs/updates }
+
+  # Share the update directory with the VM.
+  if { mount --bind -- shared /run/vm/by-id/${update_vm_id}/fs/updates }
+
+  # Start the update VM.
+  if { vm-start $update_vm_id }
+
+  # Wait for the VM to exit.
+  if { s6-svwait -D /run/service/vmm/instance/${update_vm_id} }
+
+  # Remove the bind mount.
+  if { umount -- /run/vm/by-id/${update_vm_id}/fs/updates }
+
+  # Ensure that the VM cannot change the directory
+  # while systemd-sysupdate is using it.
+  if { btrfs subvolume snapshot -- shared snapshot }
+
+  # Validate the update directory.  Delete any stale temporary files.
+  # Check that a signature file was downloaded.
+  if { updates-dir-check check snapshot }
+
+  # Perform the update in a separate mount namespace.
+  unshare --mount
+  if { mount --bind -o ro -- snapshot /run/updater }
+
+  /usr/lib/systemd/systemd-sysupdate update
+}
+importas -i sysupdate_exit_status ?
+# Clean up.
+foreground { btrfs subvolume delete -- snapshot }
+exit $sysupdate_exit_status
diff --git a/host/rootfs/os-release.in b/host/rootfs/os-release.in
new file mode 100644
index 0000000000000000000000000000000000000000..d6e699e82f87dcb1c4656ac19d4e9986282f14a5
--- /dev/null
+++ b/host/rootfs/os-release.in
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: CC0-1.0
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+NAME="Spectrum OS"
+ID=spectrum
+PRETTY_NAME="Spectrum @VERSION@"
+VERSION=@VERSION@
+VERSION_ID=@VERSION@
+IMAGE_ID=spectrum-root
+IMAGE_VERSION=@VERSION@
+RELEASE_TYPE=development
+HOME_URL="https://spectrum-os.org"
+BUG_REPORT_URL="mailto:discuss@spectrum-os.org"
+ANSI_COLOR="1;34"
+VENDOR_NAME=Spectrum
+VENDOR_URL="https://spectrum-os.org"
diff --git a/lib/config.default.nix b/lib/config.default.nix
index 489c231490a8b66aa01f50053b25646060f7f963..e53b01f1259543b988458a14b3014eb8ca29e90d 100644
--- a/lib/config.default.nix
+++ b/lib/config.default.nix
@@ -5,4 +5,6 @@
   pkgsFun = import ./nixpkgs.default.nix;
   pkgsArgs = {};
   version = "0.0.0";
+  update-url = "https://your-spectrum-os-update-server.invalid/download-directory";
+  update-signing-key = ./fake-update-signing-key.gpg;
 }
diff --git a/lib/config.nix b/lib/config.nix
index e437cdbe9aa22dd0f9c8d7052ac331c8fccf6ce6..d7edb967f339f2d0af97adef5c0302eb58950d19 100644
--- a/lib/config.nix
+++ b/lib/config.nix
@@ -1,5 +1,6 @@
-# SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
 # SPDX-License-Identifier: MIT
+# SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
 
 let
   customConfigPath = builtins.tryEval <spectrum-config>;
@@ -17,6 +18,12 @@ let
   callConfig = config: if builtins.typeOf config == "lambda" then config {
     inherit default;
   } else config;
+  finalConfig = default // callConfig config;
 in
 
-default // callConfig config
+finalConfig // {
+  update-signing-key = builtins.path {
+    name = "signing-key";
+    path = finalConfig.update-signing-key;
+  };
+}
diff --git a/lib/fake-update-signing-key.gpg b/lib/fake-update-signing-key.gpg
new file mode 100644
index 0000000000000000000000000000000000000000..12e18f4c7c740e31692e1f1975282fa72ac1f2e3
--- /dev/null
+++ b/lib/fake-update-signing-key.gpg
@@ -0,0 +1,3 @@
+SPDX-License-Identifier: CC0-1.0
+SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+NOT A VALID KEY - UPDATES WILL NOT WORK
diff --git a/release/live/shell.nix b/release/live/shell.nix
index ffaa9a571c662810348822a5952d479d251a25e5..b263eacc4d0324191e3c7737dd90d304e477e79b 100644
--- a/release/live/shell.nix
+++ b/release/live/shell.nix
@@ -2,7 +2,7 @@
 # SPDX-FileCopyrightText: 2021-2024 Alyssa Ross <hi@alyssa.is>
 
 import ../../lib/call-package.nix (
-{ callSpectrumPackage, stdenv, qemu_kvm }:
+{ callSpectrumPackage, config, stdenv, qemu_kvm }:
 
 let
   efi = callSpectrumPackage ../../host/efi.nix {};
@@ -17,6 +17,7 @@ in
       OVMF_CODE = "${qemu_kvm}/share/qemu/edk2-${stdenv.hostPlatform.qemuArch}-code.fd";
       ROOT_FS_DIR = efi.rootfs;
       EFI_IMAGE = efi;
+      VERSION = config.version;
     };
   }
 )) (_: {})
diff --git a/vm/app/systemd-sysupdate/default.nix b/vm/app/systemd-sysupdate/default.nix
new file mode 100644
index 0000000000000000000000000000000000000000..04df283f09a1f1ece9197e275d562193af170982
--- /dev/null
+++ b/vm/app/systemd-sysupdate/default.nix
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: MIT
+# SPDX-FileCopyrightText: 2023 Alyssa Ross <hi@alyssa.is>
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+
+import ../../../lib/call-package.nix (
+{ callSpectrumPackage, curl, lib, src
+, runCommand, systemd, writeScript
+}:
+
+let
+  escape-url = builtins.path {
+    name = "escape-url";
+    path = ./escape-url.awk;
+  };
+  populate-transfer-directory = builtins.path {
+    name = "populate-transfer-directory";
+    path = ./populate-transfer-directory;
+  };
+in
+
+callSpectrumPackage ../../make-vm.nix {} {
+  providers.net = [ "sys.netvm" ];
+  type = "nix";
+  run = writeScript "run-script" ''
+#!/usr/bin/execlineb -P
+export LC_ALL C
+export LANGUAGE C
+if { mount -toverlay -olowerdir=/run/virtiofs/virtiofs0/etc:/etc -- overlay /etc }
+backtick tmpdir { mktemp -d /run/sysupdate-XXXXXX }
+# Not a useless use of cat: if there are NUL bytes in the URL
+# busybox's awk might misbehave.
+backtick update_url { cat /etc/update-url }
+# Leading and trailing whitespace is almost certainly user error,
+# but be friendly to the user (by stripping it) rather than failing.
+backtick update_url {
+  awk "BEGIN {
+    url = ENVIRON[\"update_url\"]
+    gsub(/^[[:space:]]+/, \"\", url)
+    gsub(/[[:space:]]+$/, \"\", url)
+    print url
+  }"
+}
+multisubstitute {
+  importas -iSu tmpdir
+  importas -iSu update_url
+}
+if { ${populate-transfer-directory} ${escape-url} /etc/vm-sysupdate.d ''${tmpdir} ''${update_url} }
+if { ${systemd}/lib/systemd/systemd-sysupdate --definitions=''${tmpdir} update }
+# [ and ] are allowed in update URLs so that IPv6 addresses work, but
+# they cause globbing in the curl command-line tool by default.  Use --globoff
+# to disable this feature.  Only allow HTTP and HTTPS protocols on redirection.
+if { ${curl}/bin/curl -L --proto-redir =http,https --globoff
+     -o /run/virtiofs/virtiofs0/updates/SHA256SUMS -- ''${update_url}/SHA256SUMS }
+${curl}/bin/curl -L --proto-redir =http,https --globoff
+     -o /run/virtiofs/virtiofs0/updates/SHA256SUMS.sha256.asc -- ''${update_url}/SHA256SUMS.sha256.asc
+'';
+}) (_: {})
diff --git a/vm/app/systemd-sysupdate/escape-url.awk b/vm/app/systemd-sysupdate/escape-url.awk
new file mode 100644
index 0000000000000000000000000000000000000000..8edd816a20ceefa08ecc7f1bc2d1cfbe33fa8a89
--- /dev/null
+++ b/vm/app/systemd-sysupdate/escape-url.awk
@@ -0,0 +1,31 @@
+#!/usr/bin/awk -f
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+BEGIN {
+    update_url = ARGV[1];
+    # Check for a GNU awk misfeature
+    newline = "\n";
+    # Reject URLs with control characters, query parameters, or fragments.
+    # They *cannot* work and so are rejected to produce better error messages.
+    # curl rejects control characters with "Malformed input to a URL function".
+    # Fragment specifiers ("#") and query parameters ("?") break concatenating
+    # /SHA256SUMS and /SHA256SUMS.sha256.asc onto the update URL.  Also, it is
+    # simpler to reject update URLs that contain whitespace than to try to
+    # escape them.
+    if (update_url ~ /^[^\001-\040?#\x7F]+$/) {
+        # Backslashes are special to systemd-sysupdate.
+        # Use \\\\& because without the & the result is
+        # not portable between GNU awk and non-GNU awk.
+        gsub(/\\/, "\\\\&", update_url);
+        # "&" and "\\" are special on the RHS of a sed substitution
+        # and must be escaped with another backslash.  The delimiter
+        # ("#" in this case) and "\n" must also be escaped, but they
+        # were rejected above so don't bother.
+        gsub(/[&\\]/, "\\\\&", update_url);
+        printf "%s", update_url;
+        exit 0;
+    } else {
+        print "Bad update URL from host: control characters, whitespace, query parameters, and fragment specifiers not allowed" > "/dev/stderr";
+        exit 100;
+    }
+}
diff --git a/vm/app/systemd-sysupdate/populate-transfer-directory b/vm/app/systemd-sysupdate/populate-transfer-directory
new file mode 100755
index 0000000000000000000000000000000000000000..f8e515c1a69b5a6a292cc3a4d387d501f1c6a3fe
--- /dev/null
+++ b/vm/app/systemd-sysupdate/populate-transfer-directory
@@ -0,0 +1,26 @@
+#!/usr/bin/env -S execlineb -WS4
+# SPDX-License-Identifier: EUPL-1.2+
+# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com>
+# $1: awk script name
+# $2: transfer directory
+# $3: target directory
+# $4: update URL
+export LC_ALL C
+export LANGUAGE C
+backtick -N sed_rhs {
+  # Use awk to both validate the URL and to escape sed metacharacters.
+  awk -f $1 -- $4
+}
+export tmpdir $3
+elglob -w -0 transfer_file_ ${2}/*.transfer
+forx -E transfer_file { $transfer_file_ }
+backtick target_basename {
+  basename -- $transfer_file
+}
+multisubstitute {
+  importas -iuS sed_rhs
+  importas -iuS target_basename
+  importas -iuS tmpdir
+  define source $transfer_file
+}
+redirfd -w 1 ${tmpdir}/${target_basename} sed -E -- "s#@UPDATE_URL@#${sed_rhs}#g" $source

-- 
2.52.0


  parent reply	other threads:[~2025-11-19  8:21 UTC|newest]

Thread overview: 177+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-10-29 10:12 [PATCH 0/7] System updates based on systemd-sysupdate Demi Marie Obenour
2025-10-29 10:12 ` [PATCH 1/7] host/rootfs: Use full util-linux and systemd Demi Marie Obenour
2025-10-29 11:36   ` Alyssa Ross
2025-11-01  3:25     ` Demi Marie Obenour
2025-11-01 12:13       ` Alyssa Ross
2025-11-06  9:15         ` Demi Marie Obenour
2025-10-29 10:12 ` [PATCH 2/7] release/combined: Compress installation image Demi Marie Obenour
2025-10-29 11:50   ` Alyssa Ross
2025-10-29 16:51     ` Alyssa Ross
2025-11-01 22:15       ` Demi Marie Obenour
2025-11-02  0:18         ` Demi Marie Obenour
2025-11-02 12:05           ` Alyssa Ross
2025-11-02 14:42             ` Alyssa Ross
2025-11-02 19:38             ` Demi Marie Obenour
2025-10-29 10:12 ` [PATCH 3/7] tools: Add directory checker for updates Demi Marie Obenour
2025-10-29 12:01   ` Alyssa Ross
2025-10-31 20:31     ` Demi Marie Obenour
2025-11-01 12:17       ` Alyssa Ross
2025-11-01 14:09         ` Alyssa Ross
2025-11-01 18:36         ` Demi Marie Obenour
2025-11-02 12:18           ` Alyssa Ross
2025-11-02 12:43             ` Alyssa Ross
2025-11-02 19:34               ` Demi Marie Obenour
2025-11-04 15:26                 ` Alyssa Ross
2025-11-02 19:21             ` Demi Marie Obenour
2025-11-04 15:27               ` Alyssa Ross
2025-11-04 22:56                 ` Demi Marie Obenour
2025-11-06 10:15                   ` Alyssa Ross
2025-10-29 10:12 ` [PATCH 4/7] Adjust partition layout to support updates Demi Marie Obenour
2025-10-29 15:49   ` Alyssa Ross
2025-10-29 10:12 ` [PATCH 5/7] release: add install step Demi Marie Obenour
2025-10-29 12:20   ` Alyssa Ross
2025-10-29 10:12 ` [PATCH 6/7] Factor out dm-verity build rules Demi Marie Obenour
2025-10-29 12:22   ` Alyssa Ross
2025-10-31  6:39     ` Demi Marie Obenour
2025-10-29 10:12 ` [PATCH 7/7] Support updates via systemd-sysupdate Demi Marie Obenour
2025-10-29 15:48   ` Alyssa Ross
2025-11-12 22:14 ` [PATCH v2 0/8] System updates based on systemd-sysupdate Demi Marie Obenour
2025-11-12 22:14   ` [PATCH v2 1/8] host/rootfs: Install all programs from util-linuxMinimal Demi Marie Obenour
2025-11-13 12:35     ` Alyssa Ross
2025-11-12 22:14   ` [PATCH v2 2/8] host/rootfs: Install systemd-pull Demi Marie Obenour
2025-11-13 15:22     ` Alyssa Ross
2025-11-13 23:46       ` Demi Marie Obenour
2025-11-14 11:59         ` Alyssa Ross
2025-11-12 22:14   ` [PATCH v2 3/8] tools: Add directory checker for updates Demi Marie Obenour
2025-11-13 13:21     ` Alyssa Ross
2025-11-13 17:53       ` Demi Marie Obenour
2025-11-13 18:01         ` Alyssa Ross
2025-11-13 18:03           ` Demi Marie Obenour
2025-11-14 13:08             ` Alyssa Ross
2025-11-14 18:37               ` Demi Marie Obenour
2025-11-15 15:20                 ` Alyssa Ross
2025-11-12 22:14   ` [PATCH v2 4/8] Adjust partition layout to support updates Demi Marie Obenour
2025-11-13 16:00     ` Alyssa Ross
2025-11-12 22:14   ` [PATCH v2 5/8] release: Create directory with system update Demi Marie Obenour
2025-11-13 16:04     ` Alyssa Ross
2025-11-13 18:23       ` Demi Marie Obenour
2025-11-13 19:09         ` Alyssa Ross
2025-11-12 22:15   ` [PATCH v2 6/8] Support updates via systemd-sysupdate Demi Marie Obenour
2025-11-13 16:44     ` Alyssa Ross
2025-11-13 20:25       ` Demi Marie Obenour
2025-11-14 12:14         ` Alyssa Ross
2025-11-14 23:16           ` Demi Marie Obenour
2025-11-20 14:56             ` Alyssa Ross
2025-11-20 19:42               ` Demi Marie Obenour
2025-11-12 22:15   ` [PATCH v2 7/8] Documentation: Update support Demi Marie Obenour
2025-11-13 16:49     ` Alyssa Ross
2025-11-13 22:24       ` Demi Marie Obenour
2025-11-14 12:16         ` Alyssa Ross
2025-11-12 22:15   ` [PATCH v2 8/8] lib/config.nix: Validate configuration parameters Demi Marie Obenour
2025-11-13 17:16     ` Alyssa Ross
2025-11-19  8:18   ` [PATCH v3 00/14] System updates based on systemd-sysupdate Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 01/14] host/rootfs: Install all programs from util-linuxMinimal Demi Marie Obenour
2025-11-19 14:14       ` Alyssa Ross
2025-11-20  0:12         ` Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 02/14] host/rootfs: Install systemd-pull Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 03/14] tools: Add directory checker for updates Demi Marie Obenour
2025-11-19 14:45       ` Alyssa Ross
2025-11-19 23:58         ` Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 04/14] scripts: port make-gpt.sh to bash Demi Marie Obenour
2025-11-20 10:28       ` Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 05/14] scripts/make-gpt.sh: Allow specifying partition size Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 06/14] Support generating multiple partition UUIDs Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 07/14] scripts: Use shell expansion to get partition path Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 08/14] Use OS version to set partition labels and UKI name Demi Marie Obenour
2025-11-20 12:11       ` Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 09/14] release: Compress installation images and remove live image Demi Marie Obenour
2025-11-20 12:14       ` Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 10/14] Add B partitions to installation images Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 11/14] release: Create directory with system update Demi Marie Obenour
2025-11-19  8:18     ` Demi Marie Obenour [this message]
2025-11-19  8:18     ` [PATCH v3 13/14] Documentation: Update support Demi Marie Obenour
2025-11-19  8:18     ` [PATCH v3 14/14] Validate configuration parameters Demi Marie Obenour
2025-11-22  1:23     ` [PATCH v4 00/14] System updates based on systemd-sysupdate Demi Marie Obenour
2025-11-22  1:23       ` [PATCH v4 01/14] host/rootfs: Install all programs from util-linuxMinimal Demi Marie Obenour
2025-11-25 11:56         ` Alyssa Ross
2025-11-22  1:23       ` [PATCH v4 02/14] host/rootfs: Install systemd-pull Demi Marie Obenour
2025-11-25  7:36         ` Alyssa Ross
2025-11-22  1:23       ` [PATCH v4 03/14] tools: Add directory checker for updates Demi Marie Obenour
2025-11-22  1:23       ` [PATCH v4 04/14] scripts: port make-gpt.sh to bash Demi Marie Obenour
2025-11-22  1:23       ` [PATCH v4 05/14] scripts/make-gpt.sh: Allow specifying partition size Demi Marie Obenour
2025-11-22  1:23       ` [PATCH v4 06/14] Support generating multiple partition UUIDs Demi Marie Obenour
2025-11-25 13:02         ` Alyssa Ross
2025-11-26 18:26           ` Demi Marie Obenour
2025-11-22  1:23       ` [PATCH v4 07/14] scripts: Use shell expansion to get partition path Demi Marie Obenour
2025-11-22  1:23       ` [PATCH v4 08/14] release: Compress installation images and remove live image Demi Marie Obenour
2025-11-25 13:19         ` Alyssa Ross
2025-11-25 22:38           ` Demi Marie Obenour
2025-11-28 11:09             ` Alyssa Ross
2025-11-28 19:45               ` Demi Marie Obenour
2025-11-22  1:23       ` [PATCH v4 09/14] Use OS version to set partition labels and UKI name Demi Marie Obenour
2025-11-25 14:11         ` Alyssa Ross
2025-11-22  1:23       ` [PATCH v4 10/14] Add B partitions to installation images Demi Marie Obenour
2025-11-25 16:31         ` Alyssa Ross
2025-11-22  1:23       ` [PATCH v4 11/14] release: Create directory with system update Demi Marie Obenour
2025-11-25 16:50         ` Alyssa Ross
2025-11-22  1:23       ` [PATCH v4 12/14] Support updates via systemd-sysupdate Demi Marie Obenour
2025-11-25 17:54         ` Alyssa Ross
2025-11-22  1:23       ` [PATCH v4 13/14] Documentation: Update support Demi Marie Obenour
2025-11-25 18:00         ` Alyssa Ross
2025-11-22  1:23       ` [PATCH v4 14/14] Validate configuration parameters Demi Marie Obenour
2025-11-25 18:06         ` Alyssa Ross
2025-11-25 12:22       ` [PATCH v4 00/14] System updates based on systemd-sysupdate Alyssa Ross
2025-11-26 19:40       ` [PATCH v5 00/13] " Demi Marie Obenour
2025-11-26 19:40         ` [PATCH v5 01/13] tools: Add directory checker for updates Demi Marie Obenour
2025-11-26 19:40         ` [PATCH v5 02/13] scripts: port make-gpt.sh to bash Demi Marie Obenour
2025-11-26 19:40         ` [PATCH v5 03/13] scripts/make-gpt.sh: Allow specifying partition size Demi Marie Obenour
2025-11-26 19:40         ` [PATCH v5 04/13] Port scripts/format-uuid.sh to awk Demi Marie Obenour
2025-11-26 19:40         ` [PATCH v5 05/13] Use set and a command substitution to set UUID variables Demi Marie Obenour
2025-11-26 19:40         ` [PATCH v5 06/13] scripts: Use shell expansion to get partition path Demi Marie Obenour
2025-11-28 11:20           ` Alyssa Ross
2025-11-26 19:40         ` [PATCH v5 07/13] release: Compress installation images and remove live image Demi Marie Obenour
2025-11-28 11:21           ` Alyssa Ross
2025-11-26 19:40         ` [PATCH v5 08/13] Use OS version to set partition labels and UKI name Demi Marie Obenour
2025-11-26 19:40         ` [PATCH v5 09/13] Add B partitions to installation images Demi Marie Obenour
2025-11-28 11:23           ` Alyssa Ross
2025-11-26 19:40         ` [PATCH v5 10/13] release: Create directory with system update Demi Marie Obenour
2025-11-26 19:40         ` [PATCH v5 11/13] Support updates via systemd-sysupdate Demi Marie Obenour
2025-11-28 13:47           ` Alyssa Ross
2025-11-28 20:27             ` Demi Marie Obenour
2025-11-28 20:41               ` Alyssa Ross
2025-11-28 20:44                 ` Demi Marie Obenour
2025-11-28 21:08                   ` Alyssa Ross
2025-11-28 21:28                     ` Demi Marie Obenour
2025-11-28 21:30                       ` Alyssa Ross
2025-11-26 19:40         ` [PATCH v5 12/13] Documentation: Update support Demi Marie Obenour
2025-11-26 19:40         ` [PATCH v5 13/13] Validate configuration parameters Demi Marie Obenour
2025-11-29  9:49         ` [PATCH v6 0/8] System updates based on systemd-sysupdate Demi Marie Obenour
2025-11-29  9:49           ` [PATCH v6 1/8] tools: Add directory checker for updates Demi Marie Obenour
2025-11-29 11:16             ` Alyssa Ross
2025-11-29  9:49           ` [PATCH v6 2/8] release: Compress installation images and remove live image Demi Marie Obenour
2025-11-29 11:16             ` Alyssa Ross
2025-11-29  9:50           ` [PATCH v6 3/8] Use OS version to set partition labels and UKI name Demi Marie Obenour
2025-11-29 11:16             ` Alyssa Ross
2025-11-29  9:50           ` [PATCH v6 4/8] Add B partitions to installation images Demi Marie Obenour
2025-11-29 11:16             ` Alyssa Ross
2025-11-29  9:50           ` [PATCH v6 5/8] release: Create directory with system update Demi Marie Obenour
2025-11-29 11:16             ` Alyssa Ross
2025-11-29  9:50           ` [PATCH v6 6/8] Support updates via systemd-sysupdate Demi Marie Obenour
2025-11-29 11:16             ` Alyssa Ross
2025-11-29  9:50           ` [PATCH v6 7/8] Documentation: Update support Demi Marie Obenour
2025-11-30 21:46             ` Alyssa Ross
2025-11-29  9:50           ` [PATCH v6 8/8] Validate configuration parameters Demi Marie Obenour
2025-11-26 19:33     ` [PATCH v4 00/13] System updates based on systemd-sysupdate Demi Marie Obenour
2025-11-26 19:33       ` [PATCH v4 01/13] tools: Add directory checker for updates Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 02/13] scripts: port make-gpt.sh to bash Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 03/13] scripts/make-gpt.sh: Allow specifying partition size Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 04/13] Port scripts/format-uuid.sh to awk Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 05/13] Use set and a command substitution to set UUID variables Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 06/13] scripts: Use shell expansion to get partition path Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 07/13] release: Compress installation images and remove live image Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 08/13] Use OS version to set partition labels and UKI name Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 09/13] Add B partitions to installation images Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 10/13] release: Create directory with system update Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 11/13] Support updates via systemd-sysupdate Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 12/13] Documentation: Update support Demi Marie Obenour
2025-11-26 19:34       ` [PATCH v4 13/13] Validate configuration parameters Demi Marie Obenour

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20251119-updates-v3-12-b88a99915509@gmail.com \
    --to=demiobenour@gmail.com \
    --cc=devel@spectrum-os.org \
    --cc=hi@alyssa.is \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://spectrum-os.org/git/crosvm
	https://spectrum-os.org/git/doc
	https://spectrum-os.org/git/mktuntap
	https://spectrum-os.org/git/nixpkgs
	https://spectrum-os.org/git/spectrum
	https://spectrum-os.org/git/ucspi-vsock
	https://spectrum-os.org/git/www

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).