Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save eaon/e48c58bec5dff3a6550f6390da7d4e6e to your computer and use it in GitHub Desktop.
Save eaon/e48c58bec5dff3a6550f6390da7d4e6e to your computer and use it in GitHub Desktop.

For context, see freedomofpress/securedrop-workstation#887 and freedomofpress/securedrop-yum-test#48

Unpack RPM and reassemble root image

$ rpm2cpio qubes-template-securedrop-workstation-bullseye-4.0.6-202206302135.noarch.rpm | cpio -idmv
$ cat var/lib/qubes/vm-templates/securedrop-workstation-bullseye/root.img.part.* | tar --sparse -xf - -C .

Mount everything that's necessary to use chroot and ensure DNS will work. losetup command will give you a /dev/loop device, the specific number of which depends on whether you already had loop devices established. The specific number of the loop device is replaced with a ? in these instructions.

$ sudo su
# losetup -P -f --show root.img
# mount /dev/loop?p3 /mnt
# mount -t proc /proc /mnt/proc/
# mount --rbind /sys /mnt/sys/
# mount --make-rslave /mnt/sys/
# mount --rbind /dev /mnt/dev/
# mount --make-rslave /mnt/dev/
# mv /mnt/etc/resolv.conf /mnt/etc/resolv.conf.bak
# cp /etc/resolv.conf /mnt/etc/
# chroot /mnt/ /bin/bash
# (chroot) mkdir /tmp
# (chroot) mount -t tmpfs tmp tmp

Do the update thing

# (chroot) apt update
# (chroot) DEBIAN_FRONTEND=noninteractive apt upgrade

The upgrade command may have GRUB complain because it wants to update the Master Boot Record, we're doing it manually in the next step, so it can be disregarded. Please note: it's important to install the MBR on the "root" loop device rather than partition 3.

# (chroot) grub-install --target=i386-pc --modules=part_gpt /dev/loop?

Clean up after ourselves, including ensuring that the data of the partition are contiguous blocks at the beginning of the partition (hoping that that will help with compression)

# (chroot) umount tmp
# (chroot) rmdir tmp
# (chroot) mv /etc/resolv.conf.bak /etc/resolv.conf
# (chroot) unset HISTFILE
# (chroot) exit
# umount -R /mnt
# losetup -d /dev/loop?

We're removing the loopback device to ensure that some file system operations succeed. This means we will probably get assigned a new loopback device ID:

# losetup -P -f --show root.img
# e2fsck -f /dev/loop?p3

The filesystem check will probably complain about inodes etc, it's good to fix all of the issues

# resize2fs -p /dev/loop?p3 420000K

resize2fs will complain that the size is too small, but give you the exact $minimum_block_count. Use that in the next step

# resize2fs -p /dev/loop?p3 $minimum_block_count

Regrowing after having ensured all the data is one continous blob, after which we zero out empty space

# resize2fs -p /dev/loop?p3
# mount /dev/loop?p3 /mnt
# cat /dev/zero > /mnt/fillitup
# rm /mnt/fillitup
# umount /mnt
# losetup -d /dev/loop?
# exit

This concludes the image operations, we can now move on to packaging. RPM doesn't support large files, so we split up the image into chunks. File names here are important as the SPEC file will expect them to be there

$ tar --sparse --dereference -cf - root.img | split -d -b 1G - var/lib/qubes/vm-templates/securedrop-workstation-bullseye/root.img.part.

Now we create the file build_timestamp_securedrop-workstation-bullseye, the content of which is a datetime in a "flat" format like 202306141554. (I'm too lazy to look up the right format args for date)

And another file called version with just 4.1 in it.

Now you can take templates.spec from this gist and run:

$ rpmbuild --target noarch --define "template_name securedrop-workstation-bullseye" --define "DIST f32" -bb templates.spec
# Original file: https://github.com/QubesOS/qubes-linux-template-builder/blob/main/templates.spec
# Modified to work with manual template update procedure.
#
# This SPEC is for bulding RPM packages that contain complete Qubes Template files
# This includes the VM's root image, patched with all qubes rpms, etc
#
%{!?template_name: %global template_name %{getenv:TEMPLATE_NAME}}
%{!?version: %global version %(cat version)}
%{!?rel: %global rel %(cat build_timestamp_%{template_name} || echo unavailable)}
Name: qubes-template-%{template_name}
Version: %{version}
Release: %{rel}
Summary: Qubes template for %{template_name}
License: GPL
URL: http://www.qubes-os.org
Source: .
Requires: xdg-utils
Requires(post): tar
BuildArch: noarch
Provides: qubes-template
Obsoletes: %{name} > %{version}-%{release}
%define _builddir %(pwd)
%define _rpmdir %(pwd)/rpm
%define template_dir var/lib/qubes/vm-templates/%{template_name}
%define _binaries_in_noarch_packages_terminate_build 0
%description
Qubes template for %{template_name}
%build
# No build step
%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/%{template_dir}
cp -r %{template_dir}/* $RPM_BUILD_ROOT/%{template_dir}
%pre
export XDG_DATA_DIRS=/usr/share/
if [ "$1" -gt 1 ] ; then
# upgrading already installed template...
# avoid removing innocent files if *.desktop doesn't mach anything
# https://bugs.freedesktop.org/105635
if ls /%{template_dir}/apps/*.directory /%{template_dir}/apps/*.desktop >/dev/null 2>&1; then
echo "--> Removing previous menu shortcuts..."
xdg-desktop-menu uninstall --mode system \
/%{template_dir}/apps/*.directory /%{template_dir}/apps/*.desktop
fi
fi
%post
if command -v qvm-template-postprocess >/dev/null 2>&1; then
qvm-template-postprocess --really post-install %{template_name} /%{template_dir}
exit $?
fi
echo "--> Processing the root.img... (this might take a while)"
rm -f /%{template_dir}/root.img
cat /%{template_dir}/root.img.part.* | tar --sparse -xf - -C /%{template_dir}
rm -f /%{template_dir}/root.img.part.*
chown root.qubes /%{template_dir}/root.img
chmod 0660 /%{template_dir}/root.img
echo "--> Processing the volatile.img..."
/usr/lib/qubes/prepare-volatile-img.sh /%{template_dir}/volatile.img $[ `stat -c '%s' /%{template_dir}/root.img` / 1024 / 1024 ] || exit 1
chown root.qubes /%{template_dir}/volatile.img
chmod 0660 /%{template_dir}/volatile.img
tar --sparse -cf /%{template_dir}/clean-volatile.img.tar -C /%{template_dir} volatile.img
chown root.qubes /%{template_dir}/clean-volatile.img.tar
chmod 0660 /%{template_dir}/clean-volatile.img.tar
if [ "$1" = 1 ] ; then
# installing for the first time
echo "--> Creating private.img..."
truncate -s 2G /%{template_dir}/private.img
mkfs.ext4 -m 0 -q -F /%{template_dir}/private.img
chown root.qubes /%{template_dir}/private.img
chmod 0660 /%{template_dir}/private.img
fi
export XDG_DATA_DIRS=/usr/share/
echo "--> Instaling menu shortcuts..."
local_user=`getent group qubes | cut -d : -f 4 | cut -d , -f 1`
if [ -n "$local_user" ]; then
call_as_user() {
su -c "$*" - $local_user
}
else
# This will be the case during installation - user will be created in
# firstboot. There is also a code to fix file permissions, so not a big problem
call_as_user() {
$*
}
fi
if [ "$1" = 1 ] ; then
# installing for the first time
call_as_user qvm-add-template --rpm %{template_name}
fi
# If running inside of chroot (means - from anaconda), force offline mode
if [ "`stat -c %d:%i /`" != "`stat -c %d:%i /proc/1/root/.`" ]; then
qvm-template-commit --offline-mode %{template_name}
call_as_user /usr/libexec/qubes-appmenus/create-apps-for-appvm.sh \
/%{template_dir}/apps.templates %{template_name} vm-templates appvm-black
else
qvm-template-commit %{template_name}
qvm-prefs --force-root -s %{template_name} netvm none
qvm-start --no-guid %{template_name}
call_as_user qvm-sync-appmenus --force-root %{template_name}
qvm-shutdown --wait %{template_name}
qvm-prefs --force-root -s %{template_name} netvm default
# restore default firewall settings, which was reset by setting netvm=none
rm -f /%{template_dir}/firewall.xml
chgrp -R qubes /%{template_dir}
chmod g+rwX -R /%{template_dir}
fi
exit 0
%preun
if [ "$1" = 0 ] ; then
# no more packages left
if command -v qvm-template-postprocess >/dev/null 2>&1; then
qvm-template-postprocess --really pre-remove %{template_name} /%{template_dir}
exit $?
fi
# First remove DispVM template (even if not exists...)
qvm-remove --force-root -q %{template_name}-dvm
if ! qvm-remove --force-root -q --just-db %{template_name}; then
exit 1
fi
rm -f /%{template_dir}/root-cow.img
rm -f /%{template_dir}/root-cow.img.old
rm -f /%{template_dir}/firewall.xml
rm -f /%{template_dir}/%{template_name}.conf
rm -f /%{template_dir}/updates.stat
# we need to have it here, because rpm -U <template>
# apparently executes %preun of the old package *after* %post of the new packages...
echo "--> Removing menu shortcuts..."
export XDG_DATA_DIRS=/usr/share/
xdg-desktop-menu uninstall --mode system /%{template_dir}/apps/*.directory /%{template_dir}/apps/*.desktop
rm -rf %{template_dir}/apps %{template_dir}/apps.templates
rm -rf %{template_dir}/apps.icons %{template_dir}/apps.tempicons
fi
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(660,root,qubes,770)
%attr(2770,root,qubes) %dir /%{template_dir}
%ghost /%{template_dir}/root.img
/%{template_dir}/root.img.part.*
/%{template_dir}/clean-volatile.img.tar
%ghost /%{template_dir}/volatile.img
%ghost /%{template_dir}/private.img
%attr (775,root,qubes) %dir /%{template_dir}/apps
%attr (775,root,qubes) %dir /%{template_dir}/apps.templates
%attr (775,root,qubes) %dir /%{template_dir}/apps.tempicons
%attr (664,root,qubes) /%{template_dir}/whitelisted-appmenus.list
%attr (664,root,qubes) /%{template_dir}/vm-whitelisted-appmenus.list
%attr (664,root,qubes) /%{template_dir}/netvm-whitelisted-appmenus.list
%attr (664,root,qubes) /%{template_dir}/template.conf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment