Create a debian bootable live USB

Updated 2019-06-25: use Debian Stretch explicitly (already implied by kernel version); suggest testing with qemu.

We can install a full Linux distribution in a USB drive, and use it to boot a system. That is called a "live USB," and it can be used for recovery, as a portable environment, etc.

In this article we explain how to install a Debian GNU/Linux OS in a USB drive as if it was a hard disk. We will use Debian's own debootstrap to populate the root partition and syslinux as a bootloader (it is simpler than the standard grub).

Obs: to ease copy-and-pasting, we show the commands without prompt, and prepend > to the output of commands on most examples.

1 Partitioning

After inserting the USB drive, it will appear as a block device under /dev, usually sd[a-z]. Take a note on the device name. We will use /dev/sdc through the examples.

Create two partitions on our USB drive: one with 256MB for the /boot that will hold the syslinux bootloader and the Linux kernel; and another with all the rest of the space, that will hold the root filesystem.

There are many utilities you can use to partition the USB drive: parted, fdisk, etc.

For example, using fdisk, run it on /dev/sdc:

fdisk /dev/sdc
> Welcome to fdisk (util-linux 2.25.2).
> Changes will remain in memory only, until you decide to write them.
> Be careful before using the write command.

You are left at fdisk's command prompt. Create the boot partition:

Command (m for help): n
> Partition type
>    p   primary (0 primary, 0 extended, 4 free)
>    e   extended (container for logical partitions)
> Select (default p): p
> Partition number (1-4, default 1): 1
> First sector (2048-31350782, default 2048):
> Last sector, +sectors or +size{K,M,G,T,P} (2048-31350782, default 31350782): +256M
>
> Created a new partition 1 of type 'Linux' and of size 256 MiB.

Set its type to FAT16:

Command (m for help): t
> Selected partition 1
> Hex code (type L to list all codes): 6
> If you have created or modified any DOS 6.x partitions, please see the fdisk documentation for additional information.
> Changed type of partition 'Linux' to 'FAT16'.

Mark it as active:

Command (m for help): a
> The bootable flag on partition 1 is enabled now.

Create the root partition:

Command (m for help): n
> Partition type
>    p   primary (1 primary, 0 extended, 3 free)
>    e   extended (container for logical partitions)
> Select (default p): p
> Partition number (2-4, default 2): 2
> First sector (526336-31350782, default 526336):
> Last sector, +sectors or +size{K,M,G,T,P} (526336-31350782, default 31350782):
>
> Created a new partition 2 of type 'Linux' and of size 14.7 GiB.

Check that they were created:

Command (m for help): p
> Disk /dev/sdc: 15 GiB, 16051600896 bytes, 31350783 sectors
> Units: sectors of 1 * 512 = 512 bytes
> Sector size (logical/physical): 512 bytes / 512 bytes
> I/O size (minimum/optimal): 512 bytes / 512 bytes
> Disklabel type: dos
> Disk identifier: 0x13090bb3
>
> Device     Boot  Start      End  Sectors  Size Id Type
> /dev/sdc1  *      2048   526335   524288  256M  6 FAT16
> /dev/sdc2       526336 31350782 30824447 14.7G 83 Linux

Save and exit:

Command (m for help): w
> The partition table has been altered.
> Calling ioctl() to re-read partition table.
> Syncing disks.

We now have a /dev/sdc1 that will be our /boot, and /dev/sdc2 that will be our root file system. Observe that the boot partition has a MS-DOS type - that is required syslinux.

(the instructions above are heavily based on using-syslinux-to-boot-debootstraped)

2 Installing the bootloader

Create a FAT16 filesystem on the boot device:

mkdosfs -n LINUXBOOT /dev/sdc1
> mkfs.fat 3.0.27 (2014-11-12)

Install syslinux on it:

syslinux /dev/sdc1

3 Installing the base system in the root partition

Create the filesystem:

mkfs.ext4 /dev/sdc2
> mke2fs 1.42.12 (29-Aug-2014)
> Creating filesystem with 3853055 4k blocks and 964768 inodes
> Filesystem UUID: 68d66fd5-97f2-46ed-aee6-dad6f228a172
> Superblock backups stored on blocks:
>         32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208
>
> Allocating group tables: done
> Writing inode tables: done
> Creating journal (32768 blocks): done
> Writing superblocks and filesystem accounting information: done

You can use any filesystem here, as long as it is supported by your future kernel.

Mount both partitions and use debootstrap to install the base files on it:

mkdir -p usbroot
mount -t auto /dev/sdc2 usbroot
mkdir -p usbroot/boot
mount -t auto /dev/sdc1 usbroot/boot
debootstrap stretch usbroot http://ftp.debian.org/debian
> I: Retrieving Release.gpg
> I: Checking Release signature
> I: Valid Release signature (key id 75DDC3C4A499F1A18CB5F3C8CBF8D6FD518E17E1)
> I: Retrieving Packages
> I: Validating Packages
> I: Resolving dependencies of required packages...
> I: Resolving dependencies of base packages...
> I: Found additional required dependencies: acl adduser dmsetup insserv libaudit1 libaudit-common libbz2-1.0 libcap2 libcap2-bin libcryptsetup4 libdb5.3 libdebconfclient0 libdevmapper1.02.1 libgcrypt20 libgpg-error0 libkmod2 libncursesw5 libprocps3 libsemanage1 libsemanage-common libslang2 libsystemd0 libudev1 libustr-1.0-1 procps systemd systemd-sysv udev
> I: Found additional base dependencies: libdns-export100 libffi6 libgmp10 libgnutls-deb0-28 libgnutls-openssl27 libhogweed2 libicu52 libidn11 libirs-export91 libisccfg-export90 libisc-export95 libmnl0 libnetfilter-acct1 libnettle4 libnfnetlink0 libp11-kit0 libpsl0 libtasn1-6
> I: Checking component main on http://ftp.debian.org/debian...
> I: Retrieving acl 2.2.52-2
> (...)
> I: Configuring libc-bin...
> I: Configuring systemd...
> I: Base system installed successfully.

4 On-root configuration

We will have to chroot into our root filesystem to configure it further.

Mount the boot device and the default ones inside the root mount point:

mount -t devtmpfs dev       usbroot/dev
mount -t devpts   devpts    usbroot/dev/pts
mount -t proc     proc      usbroot/proc
mount -t sysfs    sysfs     usbroot/sys

chroot into root:

chroot usbroot /bin/bash

Set the root user password:

passwd
> Enter new UNIX password:
> Retype new UNIX password:
> passwd: password updated successfully

Install the Linux kernel and other important packages:

apt-get install --no-install-recommends -y linux-image-amd64 syslinux busybox-static
> Reading package lists... Done
> Building dependency tree... Done
> The following extra packages will be installed:
>   initramfs-tools klibc-utils libklibc libuuid-perl linux-base linux-image-4.9.0-9-amd64
> Suggested packages:
>   bash-completion linux-doc-3.16 debian-kernel-handbook grub-pc grub-efi extlinux
> Recommended packages:
>   busybox busybox-initramfs busybox-static firmware-linux-free irqbalance
> The following NEW packages will be installed:
>   initramfs-tools klibc-utils libklibc libuuid-perl linux-base linux-image-4.9.0-9-amd64 linux-image-amd64
> 0 upgraded, 7 newly installed, 0 to remove and 0 not upgraded.
> Need to get 34.1 MB of archives.
> After this operation, 164 MB of additional disk space will be used.
> (...)
> Setting up linux-image-amd64 (3.16+63) ...
> Processing triggers for initramfs-tools (0.120) ...
> ln: failed to create hard link '/boot/initrd.img-4.9.0-9-amd64.dpkg-bak' => '/boot/initrd.img-4.9.0-9-amd64': Operation not permitted
> update-initramfs: Generating /boot/initrd.img-4.9.0-9-amd64

We now have to set up our mount points in the /etc/fstab of the USB drive, but if we simply use /dev/sdc* as the devices, we will have trouble mounting it on other systems with a different number of hard drives. To have stable mount points, we use the UUID - universal unique identifiers - of the filesystems. Use blkid to find out the values of your identifiers:

blkid
> (...)
> /dev/sdc1: SEC_TYPE="msdos" UUID="2420-26B1" TYPE="vfat" PARTUUID="13090bb3-01"
> /dev/sdc2: UUID="68d66fd5-97f2-46ed-aee6-dad6f228a172" TYPE="ext4" PARTUUID="13090bb3-02"

In this example, the UUID of the boot filesystem is 2420-26B1, and the UUID of the root filesystem is 68d66fd5-97f2-46ed-aee6-dad6f228a172. Use them to populate /etc/fstab:

echo 'UUID=68d66fd5-97f2-46ed-aee6-dad6f228a172 /     ext4 defaults,noatime 0 0' >  etc/fstab
echo 'UUID=2420-26B1                            /boot vfat defaults         0 0' >> etc/fstab

Figure out the name of the kernel and initrd installed on the boot partition:

ls boot/vmlinuz* boot/initrd*
> boot/initrd.img-4.9.0-9-amd64  boot/vmlinuz-4.9.0-9-amd64

And use them with the UUIDs to create the boot/syslinux.cfg file, with the following contents:

default linux
timeout 1
prompt 1

label linux
    kernel vmlinuz-4.9.0-9-amd64
    append initrd=initrd.img-4.9.0-9-amd64 root=UUID=68d66fd5-97f2-46ed-aee6-dad6f228a172 ro

Finally, write syslinux's master boot record on the USB drive:

cat /usr/lib/SYSLINUX/mbr.bin > /dev/sdc

5 Closing up

We are now ready to leave the chroot and umount all devices:

exit
umount usbroot/dev/pts
umount usbroot/dev
umount usbroot/proc
umount usbroot/sys
umount usbroot/boot
umount usbroot

We can test our system using qemu:

qemu-system-x86_64 -m 512 -hda /dev/sdc

We should be able to login as root, with the password we set above.

If everything is working as expected, we can now remove the USB drive and use it to boot any computer.

References

This article is, in fact, basically a rehash of the following references with the UUID part added.

I've also created a gist that's easy to change, with the commands in this article.