[SUGGESTION] Adding LVM support for extending the root filesystem

So I’d like to preface that I am not an expert in OS development and have limited working knowledge when it comes to disk and filesystem maintenance. I am first and foremost a tinkerer and a full stack developer. I’ve been running Umbrel for close to 3 years now, I started with a Raspberry Pi and eventually upgraded to a Dell Poweredge T40, up until release of 1.0.0 I have been running Umbrel on Ubuntu.

I’ve upgraded to 1.1.2 recently but one of the biggest drawbacks has been how it has completely ruined my storage configuration.

I have multiple drives, but as it currently stands Umbrel only has access to one.

One solution would be to utilize LVM to create a volume group and add the physical disk that was selected during installation. This way additional physical disks can be added to the volume group and extended and the file system can be resized.

I did some digging through the source code, it seems this could be done in the build_x86_artifacts() function.

I’ll post the suggested changes, but I should note I have not tested this. I dont know how I would go about doing so, I assume I would need to fork the repository, make the changes, build the ISO image, flash it, then attempt to install it with the changes to confirm it works as expected. But what I’m not sure about how to do would be building the ISO image. If anyone could explain how to do so, I would gladly test it and submit a PR.

But before going that far, I wanted to post here and start a discussion. Do these changes make sense? Is there a better approach? Security concerns? Please feel free to contribute any thoughts.

Here is the suggested changes to build_x86_artifacts()

# Build the x86 artifacts.
function build_x86_artifacts() {
    echo "Creating disk image..."
    rootfs_tar_size="$(du --block-size 1M /data/build/umbrelos-amd64.tar | awk '{print $1}')"
    rootfs_buffer="1024"
    disk_size_mb="$((rootfs_tar_size + rootfs_buffer))"
    disk_size_sector=$(expr $disk_size_mb \* 1024 \* 1024 / 512)
    disk_image="/tmp/disk.img"
    dd if=/dev/zero of="${disk_image}" bs=512 count="${disk_size_sector}"

    echo "Creating disk partitions..."
    gpt_efi="ef00"
    gpt_root_amd64="8e00"  # LVM partition type code
    sgdisk \
        --new 1:2048:+200M \
        --typecode 1:"${gpt_efi}" \
        --change-name 1:ESP \
        --new 2:0:0 \
        --typecode 2:"${gpt_root_amd64}" \
        --change-name 2:ROOTFS \
        "${disk_image}"

    disk_layout=$(fdisk -l "${disk_image}")
    echo "${disk_layout}"

    echo "Attaching partitions to loopback devices..."
    efi_start=$(echo "${disk_layout}" | grep EFI | awk '{print $2}')
    efi_size=$(echo "${disk_layout}" | grep EFI | awk '{print $4}')
    root_start=$(echo "${disk_layout}" | grep ROOTFS | awk '{print $2}')
    root_size=$(echo "${disk_layout}" | grep ROOTFS | awk '{print $4}')
    efi_device=$(losetup --offset $((512*efi_start)) --sizelimit $((512*efi_size)) --show --find "${disk_image}")
    root_device=$(losetup --offset $((512*root_start)) --sizelimit $((512*root_size)) --show --find "${disk_image}")

    echo "Formatting partitions..."
    mkfs.vfat -n "ESP" "${efi_device}"
    # Do not format root_device yet, it will be used for LVM

    echo "Creating LVM volume group and logical volume..."
    pvcreate "${root_device}"
    vgcreate vg0 "${root_device}"
    lvcreate -l 100%FREE -n lv_root vg0

    echo "Formatting logical volume..."
    mkfs.ext4 -L "ROOTFS" /dev/vg0/lv_root

    echo "Mounting partitions..."
    efi_mount_point="/mnt/efi"
    root_mount_point="/mnt/root"
    mkdir -p "${efi_mount_point}"
    mkdir -p "${root_mount_point}"
    mount "${efi_device}" "${efi_mount_point}"
    mount /dev/vg0/lv_root "${root_mount_point}"

    echo "Extracting rootfs..."
    tar -xf /data/build/umbrelos-amd64.tar --directory "${root_mount_point}"

    echo "Setup hostname..."
    # We need to do this here because if we do it in the Dockerfile it gets
    # clobbered when Docker sets a random hostname during `docker run`. If
    # you copy any additional files here, please also do so in Rugpi.
    overlay_dir="/data/overlay-common"
    cp "${overlay_dir}/etc/hostname" "${root_mount_point}/etc/hostname"
    cp "${overlay_dir}/etc/hosts" "${root_mount_point}/etc/hosts"

    echo "Remove .dockerenv..."
    # We also need to remove this to prevent the system from being detected as a container
    rm "${root_mount_point}/.dockerenv"

    echo "Copying boot directory over to ESP partition..."
    cp -r "${root_mount_point}/boot/." "${efi_mount_point}"
    tree "${efi_mount_point}"
    echo

    echo "Unmounting partitions..."
    umount "${root_mount_point}"
    umount "${efi_mount_point}"

    echo "Detaching loopback devices..."
    losetup --detach "${efi_device}"
    losetup --detach "${root_device}"

    echo "Disk image created!"

    echo "Running disk image through mender-convert..."
    cd /mender
    /mender/mender-convert --disk-image "${disk_image}" --config /data/mender.cfg

    echo "Copying to ./build/..."
    mv /mender/deploy/umbrelos.mender /data/build/umbrelos-amd64.update
    mv /mender/deploy/umbrelos.img /data/build/umbrelos-amd64.img
}

I abandoned the Umbrel crap precisely because of that and more. They give no incentive to use disk redundancy or even encryption.
The cryptographic keys are in cleartext mode.
I don’t know anything about Linux but I had the courage to learn enough to free myself from this disgusting piece of software.

I’m running Umbrel on a Unraid server as a VM.
I have 2 4TB nvme drives that are mirrored so the data is redundant.
Works like a charm.

1 Like

That’s cool. Using a VM as a work around is not a bad idea, but I dislike the fact that’s really the only solution.
I’m not sure why Umbrel dropped the ball here by limiting the flagship OS software to only one drive, seems like a main priority for an OS.