[personal profile] muwlgr

Most of "household" portable USB drives have a single whole-drive partition formatted as FAT32.
In the past years I remember having seen some recipes of installing bootable live Ubuntu system on such drives which did not require shrinking FAT32 partition, adding more partitions, removing anything existing, etc.
But as I also remember, none of such recipes had become widely recommended and usable.
So I devised my own way of doing this which I document here.
Only 1 folder and 1 image file will be added to your USB drive; all other files you had there will be kept intact.
Also you could remove these folder & file to free up your storage space in any time of the future.
An already-running Ubuntu system on some PC with USB slot will be required to create this portable system.
Most commands below will require root permissions, so open root's shell with sudo -s in advance.

  1. Plug your USB drive into your PC.
    Looking into dmesg, lsblk or blkid printout, notice what name it received.
    In simple cases, device name would be 'sdb' and partition name would be 'sdb1'.
    Let's assign PART=sdb1 and refer to it as $PART below.
  2. Mount this partition and go into its top-level folder :
    mkdir /tmp/$PART
    mount /dev/$PART /tmp/$PART
    cd /tmp/$PART
    df -h .

    Ensure that you have at least 5 GB free on this drive, else your root filesystem will be too constrained
  3. mkdir ubuntu-boot
    time fallocate -l 4095M ubuntu.loop

    ubuntu-boot folder will be mounted as /boot on live system and will hold grub2, vmlinuz and initrd bootable files;
    ubuntu.loop will hold the root file system.
    4096M/4G is file size limitation on FAT32, most probably you could not allocate more.
    give fallocate enough time to complete filling the file with zeros (10..20 minutes on slow flash drive).
  4. losetup -f ubuntu.loop
    losetup -a

    Notice what device name it received.
    In simple cases it would be loop0, so let's assign LOOP=loop0 and refer to it as $LOOP below
  5. apt install f2fs-tools
    mkfs.f2fs /dev/$LOOP
    mkdir /tmp/$LOOP
    mount /dev/$LOOP /tmp/$LOOP
    cd /tmp/$LOOP
    df -h .

    this will create root FS and prepare it for population.
    you may choose to create ext4 FS for rotating HDD; f2fs is recommended for solid-state (flash) storage
    df should show you about 3.6..3.8 free GBs in your FS
  6. apt install debootstrap
    time debootstrap bionic . http://ua.archive.ubuntu.com/ubuntu
    echo $?
    df -h .
    du -sh .

    give debootstrap enough time to complete on slow flash drive (10 minutes or more) .
    check debootstrap exit code and review debootstrap.log to ensure everything has completed well (300 MB or more could be used after successful debootstrap).
    debootstrap should work equally well for other Ubuntu or Debian releases,
    my only doubt is with its ability to create 64-bit roots from under 32-bit system
  7. mkdir host
    that's needed to access mounted FAT32 partition on booted-up system, see /usr/share/initramfs-tools/scripts/local for details
  8. now create a script file in /tmp/$LOOP/root/runme.sh with the following content :
    set -x -e
    [ $( id -u ) = 0 ] # fail for non-root
    root=$( dirname $0 | sed 's/root.*//' )
    cd $root 
    for i in dev dev/pts proc run sys tmp var/cache/apt/archives
    do mount --bind /$i ./$i 
    done 
    mount --bind / ./mnt 
    chroot . || :
    while fgrep $root /proc/mounts
    do fgrep $root /proc/mounts | while read h i j
       do umount $i || :
       done 
    done
  9. Create a script in /tmp/$LOOP/root/complete.sh with the following content :
    set -x -e
    [ "$COMPLETE" = "y" ]
    echo /host/ubuntu-boot /boot none bind 0 0 >> /etc/fstab
    echo tmpfs /tmp tmpfs defaults 0 0 >> /etc/fstab
    mount /dev/$PART /host
    mount /boot
    echo do_symlinks = no >> /etc/kernel-img.conf # else focal won't install linux-image on FAT
    set -- $(cat /etc/apt/sources.list)
    { for i in $3 $3-security $3-updates
      do echo $1 $2 $i main restricted universe multiverse
      done
      echo $1 http://archive.canonical.com/ubuntu $3 partner
    } > /etc/apt/sources.list.d/$3.list
    sed -i '/^ *deb/s/^/#/' /etc/apt/sources.list
    apt update
    apt install linux-image-generic # this will pull in grub-pc 
    ird=$(find /boot/ -type f -iname 'initrd.img*' | xargs ls -t | head -n1)
    t=$(mktemp)
    lsinitramfs $ird > $t
    for i in f2fs libcrc32c crc32_generic crc32-pclmul
    do fgrep '/'$i'.ko' $t || echo $i >> /etc/initramfs-tools/modules
    done # fix for f2fs support in bionic
    rm $t
    apt install f2fs-tools dosfstools # this will update initrd
    eval $(blkid | grep /$PART | egrep -o ' UUID=[^ ]+') # echo $UUID
    echo GRUB_CMDLINE_LINUX_DEFAULT=\"root=UUID=$UUID loop=ubuntu.loop rw\" >> /etc/default/grub
    grub-install /dev/$(echo $PART | tr -d '0-9') # install into MBR
    mount /dev/$PART /tmp/$PART # for grub-probe to succeed in focal
    update-grub
    fgrep vmlinuz /boot/grub/grub.cfg || { 
     echo GRUB_DEVICE=/dev/$PART >> /etc/default/grub # help bionic discover boot files
     update-grub
    }
    for i in en wl # initialize simplest config for systemd-networkd
    do cat << EOF1 > /etc/systemd/network/$i.network
    [Match]
    Name=$i*
    [Network]
    DHCP=ipv4
    EOF1
    done
    systemctl enable systemd-networkd
    
  10. Use runme.sh to enter into your newly-created root :
    export PART LOOP
    sh /tmp/$LOOP/root/runme.sh
  11. Use complete.sh to do some setup within there :
    COMPLETE=y sh /root/complete.sh
    adduser user # choose your favorite name for login, and give it some password
    adduser user sudo # don't forget to make it a sudoer
    exit # from your chroot session
  12. Wait for umounts to complete. Then do
    cd ..
    umount /tmp/$LOOP
    losetup -d /dev/$LOOP
    umount /tmp/$PART
    exit # from your sudo -s root session

    now you are free to remove your USB drive from your PC and test its bootability on some real or virtual hardware.

Finally it should be noted that this scenario will work only for MBR partition scheme and legacy BIOS or UEFI+CSM boot.
For pure UEFI boot, different actions should be needed, but I did not have enough time and hardware to test it.
Some workarounds for grub-pc and initramfs-tools are related to Ubuntu 18.04 Bionic and might not be needed in Ubuntu 20.04 Focal or later (added some conditions to apply these fixes only when necessary).
The most unpleasant thing in bionic is that update-grub refuses to enumerate your vmlinuz/initrd files in /boot until you add GRUB_DEVICE= into /etc/default/grub , and when booting on different hardware, this device name may be different. So watch closely and if you see that update-grub ignores your /boot contents, you probably need to adjust GRUB_DEVICE= in /etc/default/grub and restart update-grub . I looked into the logic of bionic's /etc/grub.d/10_linux and I was unable to invent a better way to make it work as I needed.
I tested these scripts on 32-bit 18.04/Bionic and 64-bit 20.04/Focal. On Focal I successfully created both 32-bit bootable Bionic and 64-bit bootable Focal.
Did not replay this scenario on Debian but I feel it is going to be mostly similar
After completion of this scenario you could log in locally from text VGA console and have at least your wired Ethernet interface configured by DHCP.
GUI configuration will be reviewed in some later article.

Profile

Volodymyr Mutel

January 2026

S M T W T F S
    123
45678910
11121314151617
18192021222324
2526272829 3031

Style Credit

Expand Cut Tags

No cut tags
Page generated Feb. 10th, 2026 03:51 am
Powered by Dreamwidth Studios