[Contribution] qvm-upgrade-template (easy in-place upgrades for Debian and Fedora templates)

Per official documentation, it is recommended that users download latest templates through qvm-template before “switching” everything from a pre-existing template to the newer template. qvm-prefs allows for a somewhat seamless transition of preferences. But inheritance and persistence is a bit more complicated for the non-technical user.

This makes upgrading-in-place a very attractive option for new and experienced users alike. Fedrora documentation and Debian documentation suggest this process is for the “advanced” user. This should not be the case.

The solution you’d like

Include as a qubes command the option to “upgrade-in-place” a template, perhaps qvm-upgrade-debian and qvm-upgrade-fedora. I’ve provided basic Bash scripts for doing so, with an option for upgrading multiple templates of the same distro at once:

For Fedora-based templates.
For Debian-based templates.

At present, both scripts were tested successfully for upgrading Fedora-37 to Fedora-38 and Debian-11 to Debian-12. These scripts might also benefit from qvm-clone commands to create .bak templates and a help/usage function.

Deb Upgrader

#!/bin/env bash
# A simple script for updating Debian templates in QubesOS
# Debian 12 "Bookworm"
# Debian 13 "Trixie"

PREFIX="$(tput setaf 7)$(tput bold)"
YELLOW="$(tput setaf 3)$(tput bold)"
POSTFIX="$(tput sgr0)"
TAB="$(echo -e '\t')"

message() {
    echo "${PREFIX}${1}${POSTFIX}"
}

upgrade_template() {
    local template=$1
    local proceed=$2
    local clone=$3
    local new_template_name=$4
    local old_name=$5
    local new_name=$6
    
    vm_exists=$(qvm-ls | grep -w "$template")
    if [[ -z $vm_exists ]]; then
        message "Template $template does not exist."
        exit 1
    fi
    
    if [[ $proceed != "y" ]]; then
        message "Skipping $template without changes."
        return 0
    fi
    
    if [[ $clone == "y" ]]; then
        qvm-clone $template $new_template_name
    else
        new_template_name=$template
    fi
    
    message "Upgrading $new_template_name"
    qvm-run -a $new_template_name gnome-terminal
    
    message "Updating APT repositories..."
    qvm-run -p $new_template_name "sudo sed -i 's/$old_name/$new_name/g' /etc/apt/sources.list"
    qvm-run -p $new_template_name "sudo sed -i 's/$old_name/$new_name/g' /etc/apt/sources.list.d/qubes-r4.list"
    
    message "Performing upgrade..."
    qvm-run -p $new_template_name "sudo apt update && sudo apt upgrade && sudo apt dist-upgrade -y"
    qvm-run -p $new_template_name "sudo apt-get autoremove && sudo apt-get clean"
    
    message "Trimming the new template..."
    qvm-run -p $new_template_name "sudo fstrim -av"
    qvm-shutdown $new_template_name
    qvm-start $new_template_name
    qvm-run -p $new_template_name "sudo fstrim -av"
    
    message "Shutting down $new_template_name"
    qvm-shutdown $new_template_name
}

prompt_user() {
    message "Upgrade Debian template in QubesOS"
    read -p "Which template do you want to upgrade? " template
    read -p "Proceed with the upgrade? (y/n): " proceed
    if [[ $proceed != "y" ]]; then
        message "Exiting without changes."
        exit 0
    fi
    read -p "Do you want to clone the template before upgrading? (y/n): " clone
    read -p "What should be the new template name? " new_template_name
    read -p "Enter the old release name (e.g., buster): " old_name
    read -p "Enter the new release name (e.g., bullseye): " new_name
}

if [ $# -eq 0 ]; then
    prompt_user
    upgrade_template $template $proceed $clone $new_template_name $old_name $new_name
else
    message "Usage: $0"
    exit 1
fi


if [ $# -eq 0 ]; then
	cat >&2 <<-EOF
	Usage: ${0##*/} [options] -t 
	...
	EOF
fi

Fed Upgrader

#!/bin/env bash
# A simple script for updating Fedora templates in QubesOS

PREFIX="$(tput setaf 7)$(tput bold)"
YELLOW="$(tput setaf 3)$(tput bold)"
POSTFIX="$(tput sgr0)"
TAB="$(echo -e '\t')"
ENTER=""

message() {
    echo "${PREFIX}${1}${POSTFIX}"
}

upgrade_template() {
    local template=$1
    local proceed=$2
    local clone=$3
    local new_template_name=$4

    vm_exists=$(qvm-ls | grep -w "$template")
    if [[ -z $vm_exists ]]; then
        message "Template $template does not exist."
        exit 1
    fi

    current_version=$(qvm-run -p $template "cat /etc/fedora-release")
    current_num=$(echo $current_version | grep -oP '(\d+)')

    if [[ $proceed != "y" ]]; then
        message "Skipping $template without changes."
        return 0
    fi

    new_num=$((current_num + 1))
    new_release="fedora-$new_num"

    if [[ $clone == "y" ]]; then
        qvm-clone $template $new_template_name
    else
        new_template_name=$template
    fi
    
    message "Allocating additional space..."
    truncate -s 5GB /var/tmp/template-upgrade-cache.img
    dev=$(sudo losetup -f --show /var/tmp/template-upgrade-cache.img)

    message "Attaching block to $new_template_name"
    qvm-start $new_template_name
    qvm-block attach $new_template_name dom0:${dev##*/}
    qvm-run -p $new_template_name "sudo mkfs.ext4 /dev/xvdi"
    qvm-run -p $new_template_name "sudo mount /dev/xvdi /mnt/removable"

    message "Performing upgrade. Patience..."
    if qvm-run -p $new_template_name "sudo dnf clean all && sudo dnf --releasever=$new_num distro-sync --best --allowerasing -y";
    then
        qvm-run -p $new_template_name "sudo dnf update -y && sudo dnf upgrade -y"
        qvm-run -p $new_template_name "cat /etc/fedora-release"
        qvm-shutdown $new_template_name
        sleep 2
        message "Removing temporary cache..."
        sudo losetup -d $dev
        rm -f /var/tmp/template-upgrade-cache.img
        sleep 2
        message "Upgrade completed successfully!"
    else
        message "Upgrade failed. Check the template for issues."
        exit 1
    fi
}

prompt_user() {
    current_version=$(qvm-run -p $template "cat /etc/fedora-release")
    current_num=$(echo $current_version | grep -oP '(\d+)')
    message "Current version of $template is: Fedora release $current_num ${YELLOW} "
    read -p "Proceed with the upgrade? (y/n): " proceed
    if [[ $proceed != "y" ]]; then
        message "Skipping $template without changes."
        exit 0
    fi
    read -p "Do you want to clone the template before upgrading? (y/n): " clone
}

get_new_template_name() {
    if [[ $clone == "y" ]]; then
        read -p "What should be the new template name? " new_template_name
        echo $new_template_name
    else
        echo $1
    fi
}

if [ $# -gt 0 ]; then
    for template in "$@"; do
        prompt_user
        new_template_name=$(get_new_template_name $template)
        upgrade_template $template $proceed $clone $new_template_name
    done
else
    read -p "What template do you want to upgrade? " template
    prompt_user
    new_template_name=$(get_new_template_name $template)
    upgrade_template $template $proceed $clone $new_template_name
fi

if [ $# -eq 0 ]; then
	cat >&2 <<-EOF
	Usage: ${0##*/} [options] -t 
	...
	EOF
fi

The value to a user, and who that user might be

Such scripts would lower the barrier-to-entry for non-technical users interested in adopting Qubes. Furthermore, it would lower the amount of time spent on upgrading individual templates a user may have created based on the defaults, becoming a value-add. It would also limit bandwidth on current Qubes documentation.

Hopes

I’m hoping more tech-savy users can assist in following on the thread with @marmarek and others as I’ve no longer the bandwidth to pursue.

@mods if this is in the wrong sub-forum, please let me know and I can revert elsewhere. Thanks to all in advance.

2 Likes

This is the perfect place! Thanks for all the work on this highly needed uprade tool. I really hope this gets into Official Qubes some time in the future

1 Like