North Rivet
Technical Library · a Validus Group Inc brand
Embedded Systems · Validated Procedure

Radxa ROCK 5B Plus: Debian GNOME on NVMe

Start with Radxa's official Debian 12 Bookworm image, retain its RK3588 board support, replace KDE Plasma with Debian's GNOME desktop, update the onboard SPI bootloader, and migrate the complete configured system from microSD to NVMe.

ROCK 5B+RK3588Debian 12GNOMESPI NORNVMe

This field procedure records the complete path used on physical hardware: official Radxa Bookworm on microSD, Radxa SPI boot support, Debian GNOME conversion, block-level migration to NVMe, and final filesystem expansion.

North Rivet Technical LibraryIntermediateValidated on physical hardware
Validated Procedure

Overview

The goal is not to replace Radxa's Debian base. The goal is to keep the vendor-supported boot, kernel, firmware, repositories and RK3588 integration while replacing only the KDE desktop layer and moving the finished installation to NVMe.

Official Radxa Bookworm microSD
Radxa SPI Bootloader
Debian GNOME + GDM
SD → NVMe Clone
NVMe Boot
Expanded ext4 Root
Final resultThe tested ROCK 5B Plus boots with no microSD installed. /config, /boot/efi and / are all on the NVMe SSD, and the root filesystem uses the full drive.

At the time this procedure was validated, Radxa's download page identified the ROCK 5B Plus Debian kernel-6.1 image as rock-5b-plus_bookworm_kde_r7. Always use the current official ROCK 5B Plus image listed by Radxa.

Requirements

ItemPurposeValidated / recommended state
Radxa ROCK 5B PlusTarget RK3588 systemProcedure validated on physical ROCK 5B Plus hardware
Official Radxa Debian Bookworm imageKnown-good board support and temporary microSD systemKernel 6.1 Bookworm KDE image
microSD cardInitial boot and configuration mediaTested with a nominal 32 GB card
M.2 NVMe SSDPermanent system storageTested with Samsung SSD 970, 500 GB class
Network connectionAPT updates and SSH administrationWired Ethernet is convenient during conversion
Destructive steps are includedThe dd command used later overwrites the entire destination drive. Identify the microSD and NVMe devices from the live machine instead of copying device names blindly from this article.

1. Boot the Official Radxa Bookworm Image from microSD

Download the current ROCK 5B Plus Debian Bookworm image from Radxa's official download page. Write the extracted image to microSD with a trusted image-writing utility, insert the card, connect the display/network, and boot the board.

Once the system is running, update the image before changing the desktop:

sudo apt-get update
sudo apt-get full-upgrade -y
sudo reboot
Why start with the Radxa image?The official image supplies the board-specific boot path, kernel, firmware and repositories required by the ROCK 5B Plus. This procedure deliberately leaves those components intact.

2. Enable SSH

Remote administration makes the desktop conversion and disk migration substantially easier. Install and enable OpenSSH:

sudo apt update
sudo apt install -y openssh-server
sudo systemctl enable --now ssh
sudo systemctl status ssh --no-pager

Find the board's addresses:

hostname -I

Connect from another Linux workstation:

ssh <username>@<rock-5b-plus-ip>

3. Install and Verify the NVMe SSD

Power the board down before installing the NVMe SSD. Seat the drive in the ROCK 5B Plus M.2 M-key connector, secure it mechanically, then boot again from the microSD card.

Inspect the storage topology:

lsblk -e7 -o NAME,PATH,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL,TRAN

The validated machine showed the running root filesystem on /dev/mmcblk1p3 and the Samsung NVMe as /dev/nvme0n1:

NAME        PATH             SIZE TYPE FSTYPE MOUNTPOINTS MODEL           TRAN
mmcblk1     /dev/mmcblk1    28.9G disk
├─mmcblk1p1 /dev/mmcblk1p1    16M part vfat   /config
├─mmcblk1p2 /dev/mmcblk1p2   300M part vfat   /boot/efi
└─mmcblk1p3 /dev/mmcblk1p3  28.5G part ext4   /
nvme0n1     /dev/nvme0n1   465.8G disk                    Samsung SSD 970 nvme

Confirm the active root device separately:

findmnt -no SOURCE /

Before migration, this should point to the microSD root partition.

4. Update the ROCK 5B Plus SPI Bootloader

Radxa's documented NVMe boot workflow uses its rsetup utility to update the bootloader stored in onboard SPI NOR. From the official Bookworm system, run:

sudo rsetup

1Open System Maintenance

In rsetup, select System Maintenance.

2Choose Update SPI Bootloader

Select Update SPI Bootloader.

3Select the ROCK 5B Plus target

Choose the bootloader option corresponding to the ROCK 5B Plus, follow the prompts, and allow the write to finish completely.

Reboot after the SPI update:

sudo reboot
Do not interrupt an SPI bootloader writeLoss of power during firmware programming can leave the board without a usable SPI boot path. Use stable power and wait for rsetup to report completion.

5. Convert Radxa KDE to the Standard Debian GNOME Desktop

Debian's task-gnome-desktop package installs the Debian desktop featuring GNOME and the applications Debian expects on that desktop. The North Rivet conversion script installs GNOME and GDM first, leaves KDE available for the first reboot, and separates KDE removal into a later verification phase.

North Rivet conversion script

Download the exact script reproduced below. It preserves the Radxa package sources and does not replace the vendor kernel or firmware.

Download conversion script

On the ROCK 5B Plus:

chmod +x convert-radxa-bookworm-kde-to-gnome.sh
./convert-radxa-bookworm-kde-to-gnome.sh

When the script finishes:

sudo reboot

Verify the GNOME login and desktop before removing KDE. From a terminal or SSH session:

cat /etc/X11/default-display-manager
systemctl status gdm3 --no-pager

The default display manager should be:

/usr/sbin/gdm3
Complete conversion script
#!/usr/bin/env bash
set -Eeuo pipefail

# North Rivet Technical Library
# Radxa Debian 12 Bookworm KDE -> Debian GNOME conversion
#
# Tested workflow target:
#   Radxa ROCK 5B Plus
#   Official Radxa Debian 12 Bookworm image
#
# Normal conversion:
#   chmod +x convert-radxa-bookworm-kde-to-gnome.sh
#   ./convert-radxa-bookworm-kde-to-gnome.sh
#
# After GNOME has been verified after a reboot:
#   ./convert-radxa-bookworm-kde-to-gnome.sh --remove-kde
#
# The cleanup phase deliberately does NOT run apt autoremove. A dry-run
# autoremove report is created for review instead.

MODE="install"

usage() {
    cat <<'USAGE'
North Rivet - Radxa Bookworm KDE to Debian GNOME Converter

Usage:
  ./convert-radxa-bookworm-kde-to-gnome.sh
  ./convert-radxa-bookworm-kde-to-gnome.sh --remove-kde
  ./convert-radxa-bookworm-kde-to-gnome.sh --help

Modes:
  default       Install Debian GNOME and make GDM the default display manager.
                KDE is intentionally left installed until GNOME is verified.

  --remove-kde  Remove the high-level KDE/Plasma desktop packages after GNOME
                has been verified. Does NOT run apt autoremove.
USAGE
}

case "${1:-}" in
    "")
        MODE="install"
        ;;
    --remove-kde|remove-kde)
        MODE="remove-kde"
        ;;
    -h|--help)
        usage
        exit 0
        ;;
    *)
        printf 'ERROR: Unknown option: %s\n\n' "$1" >&2
        usage >&2
        exit 2
        ;;
esac

if [[ ${EUID} -eq 0 ]]; then
    SUDO=()
else
    command -v sudo >/dev/null 2>&1 || {
        echo "ERROR: sudo is required when this script is not run as root." >&2
        exit 1
    }
    SUDO=(sudo)
fi

need_cmd() {
    command -v "$1" >/dev/null 2>&1 || {
        printf 'ERROR: Required command not found: %s\n' "$1" >&2
        exit 1
    }
}

for cmd in apt-get apt-mark dpkg-query systemctl tee date grep readlink; do
    need_cmd "$cmd"
done

[[ -r /etc/os-release ]] || {
    echo "ERROR: /etc/os-release is not readable." >&2
    exit 1
}

# shellcheck disable=SC1091
source /etc/os-release

if [[ "${ID:-}" != "debian" || "${VERSION_CODENAME:-}" != "bookworm" ]]; then
    echo "ERROR: This script is intended for Debian 12 Bookworm." >&2
    printf 'Detected: ID=%s VERSION_CODENAME=%s\n' \
        "${ID:-unknown}" "${VERSION_CODENAME:-unknown}" >&2
    exit 1
fi

MODEL="unknown"
if [[ -r /proc/device-tree/model ]]; then
    MODEL="$(tr -d '\000' < /proc/device-tree/model)"
fi

STAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP_DIR="/var/backups/northrivet-radxa-desktop-${STAMP}"

printf '\n============================================================\n'
printf ' North Rivet - Radxa Debian Desktop Conversion\n'
printf '============================================================\n'
printf 'OS:           %s\n' "${PRETTY_NAME:-Debian 12 Bookworm}"
printf 'Board:        %s\n' "${MODEL}"
printf 'Architecture: %s\n' "$(dpkg --print-architecture)"
printf 'Kernel:       %s\n' "$(uname -r)"
printf 'Mode:         %s\n\n' "${MODE}"

backup_state() {
    echo "Creating pre-change record: ${BACKUP_DIR}"
    "${SUDO[@]}" install -d -m 0755 "${BACKUP_DIR}"

    dpkg-query -W -f='${binary:Package}\t${Version}\t${db:Status-Abbrev}\n' \
        | "${SUDO[@]}" tee "${BACKUP_DIR}/packages-before.txt" >/dev/null

    if [[ -f /etc/apt/sources.list ]]; then
        "${SUDO[@]}" cp -a /etc/apt/sources.list "${BACKUP_DIR}/"
    fi

    if [[ -d /etc/apt/sources.list.d ]]; then
        "${SUDO[@]}" cp -a /etc/apt/sources.list.d "${BACKUP_DIR}/"
    fi

    if [[ -f /etc/X11/default-display-manager ]]; then
        "${SUDO[@]}" cp -a /etc/X11/default-display-manager "${BACKUP_DIR}/"
    fi
}

install_gnome() {
    backup_state

    echo
    echo "Updating Debian package metadata..."
    "${SUDO[@]}" apt-get update

    # Avoid the interactive display-manager selection dialog. GDM is the
    # intended display manager for the Debian GNOME desktop.
    if command -v debconf-set-selections >/dev/null 2>&1; then
        printf '%s\n' \
            'gdm3 shared/default-x-display-manager select gdm3' \
            'sddm shared/default-x-display-manager select gdm3' \
            | "${SUDO[@]}" debconf-set-selections
    fi

    echo
    echo "Installing Debian's standard GNOME desktop task and GDM..."
    "${SUDO[@]}" env DEBIAN_FRONTEND=noninteractive \
        apt-get install -y task-gnome-desktop gdm3

    echo
    echo "Making GDM the default graphical login manager..."

    if systemctl list-unit-files sddm.service >/dev/null 2>&1; then
        "${SUDO[@]}" systemctl disable sddm.service >/dev/null 2>&1 || true
    fi

    "${SUDO[@]}" systemctl enable --force gdm3.service >/dev/null 2>&1 || true
    printf '/usr/sbin/gdm3\n' \
        | "${SUDO[@]}" tee /etc/X11/default-display-manager >/dev/null
    "${SUDO[@]}" systemctl set-default graphical.target >/dev/null

    # Keep the packages that define the desired desktop explicitly manual.
    "${SUDO[@]}" apt-mark manual task-gnome-desktop gdm3 >/dev/null

    echo
    echo "GNOME installation is complete."
    echo "KDE has NOT been removed yet; this is intentional."
    echo
    echo "Configured display manager:"
    cat /etc/X11/default-display-manager 2>/dev/null || true
    echo
    echo "Next step:"
    echo "  sudo reboot"
    echo
    echo "After reboot, verify GNOME works. Only then run:"
    echo "  $0 --remove-kde"
}

remove_kde() {
    if ! dpkg-query -W -f='${db:Status-Abbrev}' gdm3 2>/dev/null \
        | grep -q '^ii'; then
        echo "ERROR: gdm3 is not installed. Run the normal conversion first." >&2
        exit 1
    fi

    if [[ "$(cat /etc/X11/default-display-manager 2>/dev/null || true)" \
        != "/usr/sbin/gdm3" ]]; then
        echo "ERROR: GDM is not the configured default display manager." >&2
        echo "Verify GNOME first, then rerun the cleanup." >&2
        exit 1
    fi

    backup_state

    KDE_CANDIDATES=(
        task-kde-desktop
        kde-standard
        kde-plasma-desktop
        plasma-desktop
        sddm
    )

    INSTALLED=()
    for pkg in "${KDE_CANDIDATES[@]}"; do
        if dpkg-query -W -f='${db:Status-Abbrev}' "${pkg}" 2>/dev/null \
            | grep -q '^ii'; then
            INSTALLED+=("${pkg}")
        fi
    done

    echo
    if [[ ${#INSTALLED[@]} -eq 0 ]]; then
        echo "No high-level KDE packages from the conservative cleanup list are installed."
    else
        echo "Removing these high-level KDE desktop packages:"
        printf '  %s\n' "${INSTALLED[@]}"
        echo
        "${SUDO[@]}" apt-get purge -y "${INSTALLED[@]}"
    fi

    "${SUDO[@]}" systemctl enable --force gdm3.service >/dev/null 2>&1 || true
    printf '/usr/sbin/gdm3\n' \
        | "${SUDO[@]}" tee /etc/X11/default-display-manager >/dev/null
    "${SUDO[@]}" apt-mark manual task-gnome-desktop gdm3 >/dev/null

    REVIEW_FILE="${HOME}/debian-gnome-autoremove-review.txt"

    echo
    echo "Generating an autoremove DRY RUN for manual review..."
    apt-get --dry-run autoremove | tee "${REVIEW_FILE}"

    echo
    echo "KDE high-level cleanup is complete."
    echo "No automatic dependency purge was performed."
    echo "Review this file before ever running apt autoremove:"
    echo "  ${REVIEW_FILE}"
}

case "${MODE}" in
    install)
        install_gnome
        ;;
    remove-kde)
        remove_kde
        ;;
esac

6. Clone the Finished microSD System to NVMe

This method copies the complete configured Bookworm system—including the GNOME conversion—to the NVMe. First identify the source and target again immediately before using dd:

lsblk -e7 -o NAME,PATH,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL,TRAN
findmnt -no SOURCE /
Verify the direction before pressing EnterOn the validated system, /dev/mmcblk1 was the 28.9 GB microSD source and /dev/nvme0n1 was the 465.8 GB NVMe destination. Reversing if= and of= destroys the source installation.

Make sure no NVMe partitions are mounted:

mount | grep nvme || true
sudo umount /dev/nvme0n1p1 2>/dev/null || true
sudo umount /dev/nvme0n1p2 2>/dev/null || true
sudo umount /dev/nvme0n1p3 2>/dev/null || true

Reduce desktop activity and flush pending writes:

sudo systemctl stop gdm3 2>/dev/null || true
sudo systemctl stop sddm 2>/dev/null || true
sync
Live-root cloning considerationThis validated procedure cloned the running microSD root filesystem after stopping the graphical login manager and minimizing activity. Do not install packages, copy files, or otherwise modify the source while dd is running. For environments requiring snapshot-grade consistency, perform the clone from separate rescue media.

For the validated device mapping, the copy command was:

sudo dd if=/dev/mmcblk1 of=/dev/nvme0n1     bs=16M status=progress conv=fsync
sync

When dd completes, power the board completely off:

sudo shutdown -h now

Remove the microSD card before the first NVMe boot.

Why remove the microSD?A raw clone duplicates partition and filesystem identifiers. Leaving both the original and clone installed can create ambiguous UUID-based device resolution during boot.

7. Boot Directly from NVMe

With the microSD removed, power the ROCK 5B Plus back on. The onboard SPI bootloader should locate the boot files and operating system on NVMe.

After login, prove that root is on NVMe:

findmnt -no SOURCE /
findmnt /boot/efi
findmnt /config
lsblk

The root check should report an NVMe partition, such as:

/dev/nvme0n1p3
CheckpointDo not expand or clean anything until the board has booted successfully with the microSD physically removed.

8. Expand the Cloned NVMe Partition and ext4 Filesystem

A raw clone initially carries the microSD's smaller partition geometry. Install growpart and GPT utilities:

sudo apt update
sudo apt install -y cloud-guest-utils gdisk

Grow partition 3 to the end of the NVMe:

sudo growpart /dev/nvme0n1 3

Then expand the ext4 filesystem:

sudo resize2fs /dev/nvme0n1p3

Verify:

lsblk
df -h /

If growpart reports a GPT backup-table problem

A disk cloned from smaller media can retain the backup GPT at the old end-of-disk location. Repair the GPT location, then retry:

sudo sgdisk -e /dev/nvme0n1
sudo growpart /dev/nvme0n1 3
sudo resize2fs /dev/nvme0n1p3

9. Optional: Remove the Remaining KDE Desktop Packages

After GNOME and NVMe boot have both been proven, run the conversion script's conservative cleanup mode:

./convert-radxa-bookworm-kde-to-gnome.sh --remove-kde

The cleanup removes the high-level KDE/Plasma desktop packages and SDDM, then generates an apt autoremove dry-run report:

~/debian-gnome-autoremove-review.txt
Do not blindly run apt autoremove on a vendor SBC imageReview the proposed dependency removals first. The conversion script deliberately does not automate that final dependency purge so Radxa-specific packages can be protected.

Validated Final State

The tested ROCK 5B Plus finished with all three system partitions mounted from a 500 GB-class Samsung NVMe and the ext4 root expanded across the drive.

NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
mtdblock0    31:0    0    16M  0 disk
zram0       253:0    0   3.9G  0 disk [SWAP]
nvme0n1     259:0    0 465.8G  0 disk
├─nvme0n1p1 259:1    0    16M  0 part /config
├─nvme0n1p2 259:2    0   300M  0 part /boot/efi
└─nvme0n1p3 259:3    0 465.4G  0 part /

Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p3  459G  7.6G  432G   2% /

Troubleshooting

The board boots only when the microSD is installed

Boot the Radxa microSD system again, run sudo rsetup, and confirm that the SPI bootloader update for the ROCK 5B Plus completed successfully. Then shut down and retry with the SD removed.

The NVMe does not appear in lsblk

Power down, reseat the M.2 drive, and check again. For additional visibility:

sudo apt install -y nvme-cli
sudo nvme list
lspci

dd says the target is busy

Check mounts with findmnt or mount | grep nvme and unmount every NVMe partition before cloning. Do not unmount the microSD root filesystem that is currently running.

GDM does not become the login manager

sudo systemctl disable sddm 2>/dev/null || true
sudo systemctl enable --force gdm3
printf '/usr/sbin/gdm3
' | sudo tee /etc/X11/default-display-manager
sudo systemctl set-default graphical.target
sudo reboot

The cloned NVMe still shows roughly the microSD capacity

The clone worked; the partition and filesystem have simply not been expanded yet. Complete the growpart and resize2fs steps above.

References

Procedure validation date: August 30, 2026. Vendor download names and package revisions can change; verify the current ROCK 5B Plus image before beginning a new installation.

Practical engineering, documented on real hardware.

North Rivet Technical Library procedures are written around reproducible configurations, command-level verification and the failure points that matter when bringing embedded hardware into service.