How to change the net qube of multiple qubes?

Danger: The original guide contains a huge script to achieve the same thing as the following:

  • With Qube Manager:
    1. select all the qubes for which you want to change the net qube
    2. open the context menu (i.e.: right click)
    3. In Network, select the new net qube
  • Or, in a terminal in dom0: replace OLD_NET_QUBE_NAME and NEW_NET_QUBE_NAME with the desired value in something like this:
        for qube in $( qvm-ls --raw-list --netvm-is OLD_NET_QUBE_NAME );
             do qvm-prefs "$qube" netvm NEW_NET_QUBE_NAME;
        done
    

That’s all! Presented here, for reference, the original guide is a very good example of bad practices leading to potential security issues: it relies on the user copying and running a huge script in dom0, just to achieve something very easy.

It could be very tempting for an attacker to change a few lines (in more than 300 lines) and create a malicious script, as this guide is a wiki post on the forum. Also, experienced users probably would not use this script nor review it either, so only beginners might be tempted to use it, and miss a malicious change.


Original guide (LLM generated?)

PURPOSE

This script automates the replacement of netvm configurations across multiple appvms and templates. This is essential when a network VM becomes corrupted and needs to be replaced, since you cannot delete a netvm that is still assigned to other VMs.

Netvms can break, and for repairs this can be extremely work-intensive if dozens or hundreds of appvms, dvms, standalonem disposals, or named disposals use the same netvm.
For example, it’s common for sys-whonix and sys-firewall to grant programs, and these are configured to be used by multiple machines in Qubes.
Maybe you want to create an improved sys-firewall or sys-whonix, or a custom one, and then replace it in bulk—doing the work manually is absurdly time-consuming and slow!

I solved this time problem for a broken sys-whonix across multiple VMs using this script and method.
In 3 minutes it was fixed, but if I had to maintain and remove sys-whonix as a netvm for dozens of appvms, it would take more than 1 hour.

USE CASE EXAMPLE

  1. sys-whonix becomes problematic/broken.
  2. 40+ appvms have sys-whonix configured as their netvm.
  3. You cannot delete sys-whonix until all references are removed manually.
  4. The manual process would take hours.
  5. THIS SCRIPT solves it by automating the replacement in seconds.

RECOMMENDED WORKFLOW

  1. Create a temporary netvm (e.g., sys-whonix-temporary).
  2. Run the script to replace the old netvm → temporary netvm.
  3. Delete the broken original netvm (now no longer referenced).
  4. Create/rebuild the repaired netvm.
  5. Run the script again to replace temporary → repaired netvm.
  6. Done! All VMs now use the working netvm.

OPTIONS

  1. APPVMS — Scan and modify network VM settings in all appvms.
  2. TEMPLATES — Scan and modify network VM settings in all templates. For some reasons, for tests, the user can set netvms for some templates.
  3. HELP — Display this help information.

How to Use

Copy netvm-mass-replacement.sh script to dom0.
For example, place it in your appvm vault at /home/your-user/. Inside the appvm vault, it will be located at this path:
/home/your-user/netvm-mass-replacement.sh

On dom0, run:

qvm-run --pass-io vault 'cat "/home/your-user/netvm-mass-replacement.sh"' > /home/your-user/netvm-mass-replacement.sh;

On dom0, run:

chmod +x netvm-mass-replacement.sh

Execute:

./netvm-mass-replacement.sh

netvm-mass-replacement.sh

#!/bin/bash

###############################################################################
# Qubes OS Mass NetVM Replacement Script
# Purpose: Replace netvm configuration across multiple appvms/templates
###############################################################################

#set -euo pipefail

# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }

show_help() {
    cat << 'EOF'

Qubes OS Mass NetVM Replacement Script
======================================

PURPOSE:
This script automates the replacement of netvm configurations across multiple
appvms and templates. This is essential when a network VM becomes corrupted
and needs to be replaced, as you cannot delete a netvm that is still assigned
to other VMs.

USE CASE EXAMPLE:
1. sys-whonix becomes problematic/broken
2. 40+ appvms have sys-whonix configured as their netvm
3. Cannot delete sys-whonix until all references are removed manually
4. Manual process would take hours
5. THIS SCRIPT solves this by automating the replacement in seconds

RECOMMENDED WORKFLOW:
1. Create a temporary netvm (e.g., sys-whonix-temporary)
2. Run script to replace old netvm → temporary netvm
3. Delete the broken original netvm (now no longer referenced)
4. Create/rebuild the repaired netvm
5. Run script again to replace temporary → repaired netvm
6. Done! All VMs now use the working netvm

OPTIONS:
1. APPVMS      - Scan and modify network VM settings in all appvms
2. TEMPLATES   - Scan and modify network VM settings in all templates
3. HELP        - Display this help information

EOF
}

check_permissions() {
    if ! command -v qvm-prefs &> /dev/null; then
        log_error "Qubes OS tools (qvm-prefs) not found. Are you running inside Qubes?"
        exit 1
    fi
}

get_netvm() {
    local vm_name="$1"
    qvm-prefs "$vm_name" netvm 2>/dev/null || echo ""
}

set_netvm() {
    local vm_name="$1"
    local new_netvm="$2"
    
    if qvm-prefs --set "$vm_name" netvm "$new_netvm"; then
        log_info "✓ Updated '$vm_name': netvm changed to '$new_netvm'"
        return 0
    else
        log_error "✗ Failed to update '$vm_name'"
        return 1
    fi
}

validate_vm_name() {
    local name="$1"
    
    if [[ -z "$name" ]]; then
        return 1
    fi
    
    if [[ ! "$name" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]; then
        return 1
    fi
    
    return 0
}

scan_appvms() {
    local target_netvm="$1"
    local new_netvm="$2"
    local count=0
    local failures=0
    local checked=0
    
    log_info "Scanning appvms from /var/lib/qubes/appvms..."
    log_info "Target netvm to replace: '$target_netvm'"
    log_info "New netvm to assign: '$new_netvm'"
    echo ""
    
    local appvm_dir="/var/lib/qubes/appvms"
    
    if [[ ! -d "$appvm_dir" ]]; then
        log_error "Appvms directory not found: $appvm_dir"
        return 1
    fi
    
    log_info "Starting full scan of all appvms..."
    echo "----------------------------------------"
    
    for vm_dir in "$appvm_dir"/*; do
        [[ -d "$vm_dir" ]] || continue
        
        local vm_name
        vm_name=$(basename "$vm_dir")
        ((checked++))
        
        log_info "Checking VM #$checked: '$vm_name'..."
        
        local current_netvm
        current_netvm=$(get_netvm "$vm_name")
        
        if [[ -z "$current_netvm" ]]; then
            log_warn "  → No netvm configured (or inaccessible)"
            continue
        elif [[ "$current_netvm" == "None" ]]; then
            log_warn "  → No netvm set (None)"
            continue
        elif [[ "$current_netvm" == "$target_netvm" ]]; then
            log_info "  → MATCH FOUND! Current netvm='$current_netvm'"
            
            if set_netvm "$vm_name" "$new_netvm"; then
                ((count++))
            else
                ((failures++))
            fi
        else
            log_info "  → Skipping (current netvm='$current_netvm', not matching '$target_netvm')"
        fi
    done
    
    echo "----------------------------------------"
    log_info "=== SCAN COMPLETE ==="
    log_info "Total VMs checked: $checked"
    log_info "VMs updated: $count"
    
    if [[ $failures -gt 0 ]]; then
        log_warn "Failures: $failures"
    fi
    
    if [[ $count -eq 0 && $checked -gt 0 ]]; then
        log_warn "No VMs had the target netvm ('$target_netvm') configured."
    fi
    
    return 0
}

scan_templates() {
    local target_netvm="$1"
    local new_netvm="$2"
    local count=0
    local failures=0
    local checked=0
    
    log_info "Scanning templates from /var/lib/qubes/vm-templates..."
    log_info "Target netvm to replace: '$target_netvm'"
    log_info "New netvm to assign: '$new_netvm'"
    echo ""
    
    local templates_dir="/var/lib/qubes/vm-templates"
    
    if [[ ! -d "$templates_dir" ]]; then
        log_error "Templates directory not found: $templates_dir"
        return 1
    fi
    
    log_info "Starting full scan of all templates..."
    echo "----------------------------------------"
    
    for vm_dir in "$templates_dir"/*; do
        [[ -d "$vm_dir" ]] || continue
        
        local vm_name
        vm_name=$(basename "$vm_dir")
        ((checked++))
        
        log_info "Checking template #$checked: '$vm_name'..."
        
        local current_netvm
        current_netvm=$(get_netvm "$vm_name")
        
        if [[ -z "$current_netvm" ]]; then
            log_warn "  → No netvm configured (or inaccessible)"
            continue
        elif [[ "$current_netvm" == "None" ]]; then
            log_warn "  → No netvm set (None)"
            continue
        elif [[ "$current_netvm" == "$target_netvm" ]]; then
            log_info "  → MATCH FOUND! Current netvm='$current_netvm'"
            
            if set_netvm "$vm_name" "$new_netvm"; then
                ((count++))
            else
                ((failures++))
            fi
        else
            log_info "  → Skipping (current netvm='$current_netvm', not matching '$target_netvm')"
        fi
    done
    
    echo "----------------------------------------"
    log_info "=== SCAN COMPLETE ==="
    log_info "Total templates checked: $checked"
    log_info "Templates updated: $count"
    
    if [[ $failures -gt 0 ]]; then
        log_warn "Failures: $failures"
    fi
    
    if [[ $count -eq 0 && $checked -gt 0 ]]; then
        log_warn "No templates had the target netvm ('$target_netvm') configured."
    fi
    
    return 0
}

main_menu() {
    echo ""
    echo "=========================================="
    echo " Qubes NetVM Mass Replacement Tool"
    echo "=========================================="
    echo " Select operation:"
    echo " 1) Scan and modify APPVMS"
    echo " 2) Scan and modify TEMPLATES"
    echo " 3) Show HELP/USAGE information"
    echo " 4) Exit"
    echo "=========================================="
    echo -n " Enter choice [1-4]: "
}

run_script() {
    check_permissions
    
    local choice=""
    while true; do
        main_menu
        read -r choice
        
        case "$choice" in
            1)
                echo ""
                log_info "=== APPVMS MODE ==="
                echo -n " Enter the netvm NAME TO REPLACE (e.g., sys-whonix): "
                read -r netvm_to_change
                
                if ! validate_vm_name "$netvm_to_change"; then
                    log_error "Invalid VM name format. Exiting."
                    exit 1
                fi
                
                echo -n " Enter the NEW netvm NAME (e.g., sys-whonix-temporary): "
                read -r new_netvm
                
                if ! validate_vm_name "$new_netvm"; then
                    log_error "Invalid VM name format. Exiting."
                    exit 1
                fi
                
                echo ""
                log_warn "WARNING: You are about to change netvm for ALL appvms"
                log_warn "that currently use '$netvm_to_change' to '$new_netvm'"
                echo -n " Confirm? (yes/no): "
                read -r confirm
                
                if [[ "$confirm" == "yes" ]]; then
                    scan_appvms "$netvm_to_change" "$new_netvm"
                else
                    log_info "Operation cancelled."
                fi
                ;;
                
            2)
                echo ""
                log_info "=== TEMPLATES MODE ==="
                echo -n " Enter the netvm NAME TO REPLACE (e.g., sys-firewall): "
                read -r netvm_to_change
                
                if ! validate_vm_name "$netvm_to_change"; then
                    log_error "Invalid VM name format. Exiting."
                    exit 1
                fi
                
                echo -n " Enter the NEW netvm NAME (e.g., sys-firewall-new): "
                read -r new_netvm
                
                if ! validate_vm_name "$new_netvm"; then
                    log_error "Invalid VM name format. Exiting."
                    exit 1
                fi
                
                echo ""
                log_warn "WARNING: You are about to change netvm for ALL templates"
                log_warn "that currently use '$netvm_to_change' to '$new_netvm'"
                echo ""
                log_warn "Note: Templates are typically offline - changes apply next boot"
                echo -n " Confirm? (yes/no): "
                read -r confirm
                
                if [[ "$confirm" == "yes" ]]; then
                    scan_templates "$netvm_to_change" "$new_netvm"
                else
                    log_info "Operation cancelled."
                fi
                ;;
                
            3)
                show_help
                ;;
                
            4)
                log_info "Exiting. Stay safe!"
                exit 0
                ;;
                
            *)
                log_error "Invalid choice. Please enter 1-4."
                ;;
        esac
        
        echo ""
        echo "Press Enter to continue..."
        read -r
    done
}

run_script

QUESTION

Does Qubes OS have any factory program or script that does exactly this?
Since netvms break and this is very common, and it’s necessary to fix it quickly with good usability, having an official tool would be great.

You must have a REALLY powerful box :smiley:

Netvms can break | Since netvms break and this is very common

Love your work, this one made me curious though - break how? I don’t think this ever happened to me. You mean whonix (where I can’t join the conversation bcs I don’t use it / tor)? Or just netvms, like debian/fedora + vpn or sth like that?

You cannot delete sys-whonix until all references are removed manually.

You can with –force (for example qvm-shutdown –force a proxyvm which another VM uses as netvm).

factory program or script that does exactly this

Not to my knowledge, I’d use a small bash loop parsing qvm-ls or something.

Your script is certainly helpful though. I don’t have that many VMs (well not hundrets). I use one template per VM, as in each AppVM has its own individual template. (ok just counted, I have 65 VMs in total).
As for fixing buggy netvms - my approach is to just –force kill them and fix them. Till then there is no internet for the ones behind them :stuck_out_tongue: Your script certainly fixes that, though the question remains - if the netvm thats being buggy is being fixed, what netvm do I assign in the meantime? If its just a dummyvm I might as well –force kill the buggy VM, fix it and then spawn it again.

In older Qubes versions I remember this didnt work well, but since lately its very reliable to just –force shutdown VMs and start them again and “the internet” just works.

Wow. Who would download such a long script (AI slop?) and run it in dom0 instead of this oneliner?

for qube in $( qvm-ls --raw-list --netvm-is OLD_NET_QUBE_NAME ); do qvm-prefs "$qube" netvm NEW_NET_QUBE; done
3 Likes

Since the original poster have not addressed my concerns, I will take the liberty to edit this guide.

Note for the sake of clarity: this is a user action, as anyone can edit a guide. It has nothing to do with moderation.

2 Likes

Or you can just set, security wise, none as the default net qube in the Global config, and assign it manually when needed…