From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from atuin.qyliss.net (localhost [IPv6:::1]) by atuin.qyliss.net (Postfix) with ESMTP id 65C8620175; Thu, 13 Nov 2025 16:44:37 +0000 (UTC) Received: by atuin.qyliss.net (Postfix, from userid 993) id BA229201BB; Thu, 13 Nov 2025 16:44:34 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on atuin.qyliss.net X-Spam-Level: X-Spam-Status: No, score=-0.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DMARC_MISSING,RCVD_IN_DNSWL_LOW,SPF_HELO_PASS autolearn=unavailable autolearn_force=no version=4.0.1 Received: from fout-a2-smtp.messagingengine.com (fout-a2-smtp.messagingengine.com [103.168.172.145]) by atuin.qyliss.net (Postfix) with ESMTPS id 7081C201B8 for ; Thu, 13 Nov 2025 16:44:32 +0000 (UTC) Received: from phl-compute-04.internal (phl-compute-04.internal [10.202.2.44]) by mailfout.phl.internal (Postfix) with ESMTP id 6D4CBEC010A; Thu, 13 Nov 2025 11:44:30 -0500 (EST) Received: from phl-mailfrontend-01 ([10.202.2.162]) by phl-compute-04.internal (MEProxy); Thu, 13 Nov 2025 11:44:30 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=alyssa.is; h=cc :cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm2; t=1763052270; x=1763138670; bh=6JO/7olROj +YwJ1QhfT7AYfrD9QK7zzpb+e4X3Xq8Yg=; b=MNDaG7yFLcV3rft1wI79Oc83fV 2cnysSXaWxLBqkVGswfvAkIhKC+u6ujqjhKXIPORGTnSd5qsxvDPq36AYo19Xdbm YZe3+PTSPytmq+xWurCOlVbMivJmnzlmoUbApR/r/5jqjAAkW/pi0Tw3bkYwsXh9 IseQHYaDggnnDkAakfOOtmmmACeep6QKxnjgryvHtlFdPeUyVUYbu/Io+MGFUp25 CsZLv3Z6GxKBvpo1l/yG3SDT63DqErsXg+C/IHYGV5emAUBkxsIukEE2MjbDBpVB PXNJZcmSgIhDC1O2hJyVcXkSh6MC8mvS1Ih2Uk4jJGyScJYeyb17oIsRhUkw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t= 1763052270; x=1763138670; bh=6JO/7olROj+YwJ1QhfT7AYfrD9QK7zzpb+e 4X3Xq8Yg=; b=iaS+R/kCsGFdnsHnQSutgNmJGCkYAeqP8pOLzcJtSc2UqQ4tGR0 bYuWaJcLbmvAMF0X5kaqxn9fow5j37fDMsYBckgMdyxUR3nC4zHGkeecS6ZQljEI GcBMMFj7nY07RTylse1Fgpzti2spXf+5Y1fOrEhWK8vwx8AnCjP49nKd+lJqiaS4 Si2EfGbb+HO/ziJ0mGfjILWqwDKLc7JnR7SMFLmOQ2Tu44JrCnsBeRnPLg4HrD+4 LWqaxr25njFG8yvXP3v2eohsfFfZcbtMSrgecPQZDelkRVVwROUjtdY76OW3J2TY PrvHKrH1Zh4LMCH1N7GGWImqeK5WHASNuWA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddvtdejgeehucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujf gurhephffvvefujghffffkgggtsehgtderredttddtnecuhfhrohhmpeetlhihshhsrgcu tfhoshhsuceohhhisegrlhihshhsrgdrihhsqeenucggtffrrghtthgvrhhnpeeuuddtje evleefgedtuefhfefgtdeiudetudduvefghffgvddviefgieeutdehueenucffohhmrghi nhepphhrohhvihguvghrshdrnhgvthenucevlhhushhtvghrufhiiigvpedtnecurfgrrh grmhepmhgrihhlfhhrohhmpehhihesrghlhihsshgrrdhishdpnhgspghrtghpthhtohep vddpmhhouggvpehsmhhtphhouhhtpdhrtghpthhtohepuggvmhhiohgsvghnohhurhesgh hmrghilhdrtghomhdprhgtphhtthhopeguvghvvghlsehsphgvtghtrhhumhdqohhsrdho rhhg X-ME-Proxy: Feedback-ID: i12284293:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Thu, 13 Nov 2025 11:44:29 -0500 (EST) Received: by fw12.qyliss.net (Postfix, from userid 1000) id 4D47A139D57F; Thu, 13 Nov 2025 17:44:28 +0100 (CET) From: Alyssa Ross To: Demi Marie Obenour Subject: Re: [PATCH v2 6/8] Support updates via systemd-sysupdate In-Reply-To: <20251112-updates-v2-6-88d96bf81b79@gmail.com> References: <20251112-updates-v2-0-88d96bf81b79@gmail.com> <20251112-updates-v2-6-88d96bf81b79@gmail.com> Date: Thu, 13 Nov 2025 17:44:26 +0100 Message-ID: <87tsyxc26t.fsf@alyssa.is> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha512; protocol="application/pgp-signature" Message-ID-Hash: DD5B6MVT3HC6AT2WESK6ZT2CGWD44FUP X-Message-ID-Hash: DD5B6MVT3HC6AT2WESK6ZT2CGWD44FUP X-MailFrom: hi@alyssa.is X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-devel.spectrum-os.org-0; header-match-devel.spectrum-os.org-1; header-match-devel.spectrum-os.org-2; header-match-devel.spectrum-os.org-3; header-match-devel.spectrum-os.org-4; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Spectrum OS Development X-Mailman-Version: 3.3.9 Precedence: list List-Id: Patches and low-level development discussion Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: --=-=-= Content-Type: text/plain Content-Transfer-Encoding: quoted-printable Demi Marie Obenour writes: > Include a new 'update' command to update the system. This works as > follows: > > 1. Take a global, system-wide lock. > 2. Create a BTRFS subvolume for the sys.updates VM to write the updates. > 3. Bind-mount this subvolume into the VM's shared directory. > 4. Start sys.appvm-updates to get the updates. > 5. Wait for the VM to shut down. > 6. Take a BTRFS snapshot of the subvolume. > 7. Call syncfs() to flush all of the data on the subvolume. > 8. Inspect the contents of the subvolume. > Check that everything is a regular file and that the names are reasona= ble. > Check that SHA256SUMS and SHA256SUMS.gpg are present. Not any more. > 9. Call systemd-sysupdate to run the actual update. > > sys.appvm-updates uses host-provided information to fetch the update. > This allows editing files on the host to change the update URL and > signing key. > > Signed-off-by: Demi Marie Obenour > --- > host/rootfs/Makefile | 2 + > host/rootfs/default.nix | 28 ++++++- > host/rootfs/file-list.mk | 4 + > 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 +++++ > host/rootfs/image/usr/bin/update | 89 ++++++++++++++++= ++++++ > host/rootfs/os-release.in | 13 ++++ > host/rootfs/os-release.in.license | 2 + > host/rootfs/updatevm-url-env | 3 + > host/rootfs/vm-sysupdate.d/50-verity.transfer | 18 +++++ > host/rootfs/vm-sysupdate.d/60-root.transfer | 18 +++++ > host/rootfs/vm-sysupdate.d/70-kernel.transfer | 18 +++++ > lib/config.default.nix | 2 + > lib/config.nix | 11 ++- > lib/fake-update-signing-key.gpg | 1 + > lib/fake-update-signing-key.gpg.license | 2 + > release/live/default.nix | 4 +- > release/live/shell.nix | 3 +- > vm/app/updates.nix | 37 +++++++++ > 21 files changed, 309 insertions(+), 7 deletions(-) > diff --git a/host/rootfs/default.nix b/host/rootfs/default.nix > index b574b8ddf5858867156507429a55b7f537e3c485..0a7638f8d78cf36592c2721d0= 59bc867b04f233c 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 ( >=20=20 > @@ -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 > }: >=20=20 > let > @@ -36,6 +38,7 @@ let > 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 > + btrfs-progs Let's keep this sorted. > @@ -79,11 +82,24 @@ let > appvm-firefox =3D callSpectrumPackage ../../vm/app/firefox.nix {}; > appvm-foot =3D callSpectrumPackage ../../vm/app/foot.nix {}; > appvm-gnome-text-editor =3D callSpectrumPackage ../../vm/app/gnome-t= ext-editor.nix {}; > + appvm-updates =3D callSpectrumPackage ../../vm/app/updates.nix {}; I think appvm-sysupdate or appvm-systemd-sysupdate would be clearer. > }; >=20=20 > packagesSysroot =3D runCommand "packages-sysroot" { > depsBuildBuild =3D [ inkscape ]; > nativeBuildInputs =3D [ xorg.lndir ]; > + env =3D { > + VERSION =3D config.version; > + UPDATE_URL =3D config.update-url; > + }; > + src =3D fileset.toSource { > + root =3D ./.; > + fileset =3D fileset.intersection src (fileset.unions [ > + ./vm-sysupdate.d > + ./os-release.in > + ./updatevm-url-env > + ]); > + }; > } '' > mkdir -p $out/usr/bin $out/usr/share/dbus-1/services \ > $out/usr/share/icons/hicolor/20x20/apps > @@ -95,8 +111,7 @@ let > done >=20=20 > # 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 > @@ -118,6 +133,14 @@ let > ln -st $out/usr/share/dbus-1/services \ > ${pkgsGui.xdg-desktop-portal-gtk}/share/dbus-1/services/org.free= desktop.impl.portal.desktop.gtk.service >=20=20 > + mkdir -p -- "$out/etc/updatevm/sysupdate.d" > + substitute "$src/os-release.in" "$out/etc/os-release" --subst-var VE= RSION > + for d in "$src/vm-sysupdate.d"/*.transfer; do > + result_file=3D''${d#"$src/vm-sysupdate.d/"} > + substitute "$d" "$out/etc/updatevm/sysupdate.d/$result_file" --sub= st-var UPDATE_URL > + done > + substitute "$src/updatevm-url-env" "$out/etc/updatevm/url-env" --sub= st-var UPDATE_URL > + I think it would make more sense to do these at the Make layer. It handles other generated files, so I don't see why it can't handle these too, and then if I add something to os-release I don't have to rebuild any Nix stuff. > diff --git a/host/rootfs/image/etc/fstab b/host/rootfs/image/etc/fstab > index 6a82ecc85090a37b13603b29f74ca6e554a28c33..78cec99f29dda993ad9704877= 1097121a0e42622 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=3D4,mode=3D620 0 0 > tmpfs /dev/shm tmpfs defaults 0 0 > sysfs /sys sysfs defaults 0 0 > +tmpfs /tmp tmpfs defaults,mode=3D0700 0 0 Is this used? > diff --git a/host/rootfs/image/usr/bin/update b/host/rootfs/image/usr/bin= /update > new file mode 100755 > index 0000000000000000000000000000000000000000..cbbf8ad8634a7771a0a5f7d65= 86ee88cdc0672a8 > --- /dev/null > +++ b/host/rootfs/image/usr/bin/update > @@ -0,0 +1,89 @@ > +#!/bin/execlineb -WS1 > +# SPDX-License-Identifier: EUPL-1.2+ > +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour > + > +# Steps: > +# > +# 1. Take a global, system-wide lock. > +# 2. Create a BTRFS subvolume for the sys.updates VM to write the update= s. > +# 3. Bind-mount this subvolume into the VM's shared directory. > +# 4. Start sys.updates to get the updates. > +# 5. Wait for the VM to shut down. > +# 6. Take a BTRFS snapshot of the subvolume. > +# 7. Call syncfs() to flush all of the data on the subvolume. > +# 8. Inspect the contents of the subvolume. > +# Check that everything is a regular file and that the names are reas= onable. > +# Check that SHA256SUMS and SHA256SUMS.gpg are present. Not any more. > +# 9. Call systemd-sysupdate to run the actual update. > + > +if { mkdir -p -m 0700 /run/updater } > +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 } > + Wouldn't it break if there's already stuff in it? I'd do foreground { redirfd -w 2 /dev/null btrfs subvolume delete -- shared } if { btrfs subvolume create -- shared } and then you know you've got an empty subvolume. > + # Snapshot directory may have files or directories with untrusted name= s. > + # Redirect its output to /dev/null to avoid printing them to the conso= le. > + ifelse -n { redirfd -w 2 /dev/null rm -rf -- snapshot } { > + foreground { redirfd -w 2 echo "Cannot remove snapshot directory" } > + exit 1 > + } Why not btrfs subvolume delete? It's faster and won't print names. > + > + backtick -E update_vm_id_ { > + backtick -E id_path { readlink /run/vm/by-name/sys.appvm-updates } > + basename -- $id_path > + } > + > + multisubstitute { > + define fsdir /run/vm/by-id/${update_vm_id_}/fs > + define update_vm_id ${update_vm_id_} Why? > + define svcdir /run/service/vmm/instance/${update_vm_id_} Can also use /run/vm/by-name/sys.appvm-updates/fs and /run/vm/by-name/sys.appvm-updates/service if you prefer, although you need to look up the ID for vm-start anyway currently. > + } > + > + # $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 { rm -rf -- ${fsdir}/etc } > + if { umask 022 mkdir -p -- ${fsdir}/updates ${fsdir}/etc/systemd } > + if { cp -R -- /etc/updatevm/sysupdate.d /etc/updatevm/url-env ${fsdir}= /etc } > + if { cp -- /etc/systemd/import-pubring.gpg ${fsdir}/etc/systemd } Why copy rather than bind mount? > + > + # If the directory is already mounted, unmount it. This prevents a > + # confusing error from mount. > + foreground { redirfd -w 2 /dev/null umount -- ${fsdir}/updates } > + > + # Share the update directory with the VM. > + if { mount --bind -- shared ${fsdir}/updates } > + > + # Start the update VM. > + if { vm-start $update_vm_id } > + > + # Wait for the VM to exit. > + if { s6-svwait -D ${svcdir} } > + It might be more robust to use a transient VM, like we use for AppImages, so that nothing can restart it. Transient VMs are still developing though, so it's also fine to say we'll do it this way for now and adapt it later. This would also save all the filesystem resetting you're needing to do here. > diff --git a/host/rootfs/os-release.in.license b/host/rootfs/os-release.i= n.license > new file mode 100644 > index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749= a7524ac3871dda4 > --- /dev/null > +++ b/host/rootfs/os-release.in.license > @@ -0,0 +1,2 @@ > +SPDX-License-Identifier: CC0-1.0 > +SPDX-FileCopyrightText: 2025 Demi Marie Obenour os-release files can have comments, so no need for a separate license file here. > diff --git a/lib/config.nix b/lib/config.nix > index 01bcfa2bb2d5c412e212f5a60d9032e89c8a7442..5b6b95013734202b7e2e01d5f= fce313080658006 100644 > --- a/lib/config.nix > +++ b/lib/config.nix > @@ -1,5 +1,6 @@ > -# SPDX-FileCopyrightText: 2023 Alyssa Ross > # SPDX-License-Identifier: MIT > +# SPDX-FileCopyrightText: 2024 Alyssa Ross > +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour Why have I changed from 2023 to 2024? >=20=20 > let > customConfigPath =3D builtins.tryEval ; > @@ -17,5 +18,11 @@ let > callConfig =3D config: if builtins.typeOf config =3D=3D "lambda" then = config { > inherit default; > } else config; > + finalConfig =3D default // callConfig config; > in > - default // callConfig config; > + finalConfig // { > + update-signing-key =3D builtins.path { > + name =3D "signing-key"; > + path =3D finalConfig.update-signing-key; > + }; > + } What does this do? > diff --git a/lib/fake-update-signing-key.gpg b/lib/fake-update-signing-ke= y.gpg > new file mode 100644 > index 0000000000000000000000000000000000000000..b4c15467614ee15deef02af05= f4c6554a1f7a013 > --- /dev/null > +++ b/lib/fake-update-signing-key.gpg > @@ -0,0 +1 @@ > +NOT A VALID KEY - UPDATES WILL NOT WORK > diff --git a/lib/fake-update-signing-key.gpg.license b/lib/fake-update-si= gning-key.gpg.license > new file mode 100644 > index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749= a7524ac3871dda4 > --- /dev/null > +++ b/lib/fake-update-signing-key.gpg.license > @@ -0,0 +1,2 @@ > +SPDX-License-Identifier: CC0-1.0 > +SPDX-FileCopyrightText: 2025 Demi Marie Obenour Given it's not a valid key anyway might as well just put this in the file. > diff --git a/release/live/default.nix b/release/live/default.nix > index dc649732ffa46a998a4a66360aa8ff7ef6bccae0..581420da9acf855d4b3d9ecec= c1ef406f742fd75 100644 > --- a/release/live/default.nix > +++ b/release/live/default.nix > @@ -7,7 +7,7 @@ import ../../lib/call-package.nix ( > { callSpectrumPackage, spectrum-build-tools, rootfs, src > , lib, pkgsStatic, stdenvNoCC > , cryptsetup, dosfstools, jq, mtools, util-linux > -, systemdUkify, version, efi > +, systemdUkify, config, efi > }: >=20=20 > let > @@ -49,7 +49,7 @@ stdenv.mkDerivation { > SYSTEMD_BOOT_EFI =3D "${efi.systemd}/lib/systemd/boot/efi/systemd-bo= ot${efiArch}.efi"; > EFI_IMAGE =3D efi; > EFINAME =3D "BOOT${toUpper efiArch}.EFI"; > - VERSION =3D version; > + VERSION =3D config.version; > }; >=20=20 > buildFlags =3D [ "dest=3D$(out)" ]; Maybe this should be squashed into an earlier patch? > diff --git a/vm/app/updates.nix b/vm/app/updates.nix > new file mode 100644 > index 0000000000000000000000000000000000000000..d2c1e5fcb35b37c7ed8a173f1= 9b97894a36a7f0c > --- /dev/null > +++ b/vm/app/updates.nix > @@ -0,0 +1,37 @@ > +# SPDX-License-Identifier: MIT > +# SPDX-FileCopyrightText: 2023 Alyssa Ross > +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour > + > +import ../../lib/call-package.nix ( > +{ callSpectrumPackage, config, curl, lib, src > +, runCommand, systemd, writeScript > +}: > + > +let > + update-url =3D config.update-url; > + mountpoint =3D "/run/virtiofs/virtiofs0"; > + sysupdate-path =3D "${systemd}/lib/systemd/systemd-sysupdate"; > + runner =3D writeScript "update-run-script" > + '' > + #!/usr/bin/execlineb -P > + if { mount -toverlay -olowerdir=3D${mountpoint}/etc:/etc -- overlay = /etc } > + envfile ${mountpoint}/etc/url-env Seems like overkill to use an envfile for a single URL? > + importas -i update_url UPDATE_URL > + if { ${sysupdate-path} update } > + if { ${curl}/bin/curl -L --proto =3Dhttp,https > + -o ${mountpoint}/updates/SHA256SUMS.gpg ''${update_url}/SHA256SUM= S.gpg } > + # systemd-sysupdate recently went from needing SHA256SUMS.gpg to SHA= 256SUMS.sha256.asc. > + # I (Demi) have no need if this is intentional or a bug. I also hav= e no idea if this > + # behavior will stay unchanged in the future. Therefore, create bot= h files and let > + # systemd-sysupdate ignore the one it isn't interested in. > + if { ln -f ${mountpoint}/updates/SHA256SUMS.gpg ${mountpoint}/update= s/SHA256SUMS.sha256.asc } Would be good to figure out why that happened. If we add a comment like this it's very unlikely to ever get cleaned up. > + ${curl}/bin/curl -L --proto =3Dhttp,https > + -o ${mountpoint}/updates/SHA256SUMS ''${update_url}/SHA256SUMS > + ''; > +in > + > +callSpectrumPackage ../make-vm.nix {} { > + providers.net =3D [ "sys.netvm" ]; > + type =3D "nix"; > + run =3D "${runner}"; Might as well inline this. > +}) (_: {}) > > --=20 > 2.51.2 --=-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iHUEARYKAB0WIQQGoGac7QfI+H5ZtFCZddwkt31pFQUCaRYK6gAKCRCZddwkt31p Fc/yAQDoedf8jRFvENaQMfuciuqvPgZPYaFUzcIa4mm9fxVuQgD/Y9c98HUnXR6V 4b5Q+skZ5uu46BkY7WjADkTGccGoggc= =EpH7 -----END PGP SIGNATURE----- --=-=-=--