Context
My previous post describes my experience setting my laptop as a postmarketOS (pmOS) host, including initializing pmbootstrap. This post documents my experience using that host to flash pmOS on a Pixel 3a.
At a high level, the steps are:
- Install
android-toolson the host - Use
pmbootstrapto build a system image - Boot Pixel 3a into Fastboot mode
- Unlock the Bootloader
- Use
pmbootstrapto flash pmOS - Reboot into pmOS
Install android-tools
I need android-tools to get the fastboot CLI. The installation is straightforward:
$ sudo apk add android-tools
[sudo] password for erik:
The following NEW packages will be installed:
android-tools android-tools-adb android-tools-append2simg
android-tools-avbtool android-tools-e2fsdroid android-tools-ext2simg
android-tools-fastboot android-tools-img2simg android-tools-lpadd
android-tools-lpdump android-tools-lpflash android-tools-lpmake
android-tools-lpunpack android-tools-make_f2fs android-tools-mkbootimg
android-tools-mkdtboimg android-tools-mke2fs.android
android-tools-repack_bootimg android-tools-simg2img android-tools-sload_f2fs
android-tools-unpack_bootimg fmt
...
OK: 2732.6 MiB in 1045 packages
Build The System Image
I previously initialized pmbootstrap. Now I need to build the rootfs and kernel package for the Pixel 3a. pmbootstrap downloads necessary chroots and package the OS. I’ll include all the details since this is the first time I’m going through this:
$ pmbootstrap install
Choose a password for the user 'erik':
Confirm password for 'erik':
[15:47:33] *** (1/4) PREPARE NATIVE CHROOT ***
[15:47:34] Update package index for x86_64 (4 file(s))
[15:47:37] Download http://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/apk-tools-static-3.0.6-r0.apk
[15:47:38] (native) Creating chroot
[15:47:44] (native) install cryptsetup util-linux parted
[15:47:47] *** (2/4) CREATE DEVICE ROOTFS ("google-sargo") ***
[15:47:55] (native) install qemu-aarch64
[15:47:57] Register qemu binfmt (aarch64)
[15:47:58] (rootfs_google-sargo) Creating chroot
[15:47:58] Update package index for aarch64 (4 file(s))
[15:48:31] (rootfs_google-sargo) install postmarketos-base-systemd
[15:55:14] (rootfs_google-sargo) install postmarketos-base device-google-sargo postmarketos-ui-phosh postmarketos-base-ui-audio-backend-pulseaudio postmarketos-base-ui-wifi-wpa_supplicant postmarketos-base-nofde sudo-rs phosh-mobile-settings phosh-tour decibels firefox-esr flatpak fprintd g4music gnome-calculator gnome-calendar gnome-clocks gnome-console gnome-contacts gnome-maps gnome-software gnome-software-plugin-apk gnome-text-editor gnome-user-share gnome-weather gst-libav gst-plugins-bad gst-plugins-good gst-plugins-rs-dav1d gvfs-full loupe nautilus papers rygel showtime snapshot tuned-ppd font-droid font-droid-nonlatin font-twemoji lang font-droid font-droid-nonlatin font-twemoji lang calls chatty mobile-config-firefox postmarketos-tweaks-setting-definitions ttyescape
[sudo] password for erik:
[16:04:52] (rootfs_google-sargo) install postmarketos-mkinitfs
[sudo] password for erik:
[16:05:02] (rootfs_google-sargo) mkinitfs
[16:06:44] *** SET LOGIN PASSWORD FOR: 'erik' ***
[16:06:46] NOTE: No valid keymap specified for device
[16:07:40] *** (3/4) PREPARE INSTALL BLOCKDEVICE ***
[16:07:42] (native) create google-sargo.img (3236M)
[16:07:42] (native) mount /dev/install (google-sargo.img)
[16:07:42] (native) partition /dev/install (boot: 512M)
[16:07:43] Mounting partitions of /dev/loop0 inside the chroot
[16:07:43] (native) install e2fsprogs
[16:07:45] (native) format /dev/installp2 (pmOS_root, ext4)
[16:07:45] (native) mount /dev/installp2 to /mnt/install
[16:07:46] (native) install dosfstools
[16:07:47] (native) format /dev/installp1 (pmOS_boot, fat32)
[16:07:48] (native) create /etc/fstab
[16:07:48] (rootfs_google-sargo) mkinitfs
[16:09:22] *** (4/4) FILL INSTALL BLOCKDEVICE ***
[16:09:22] (native) copy rootfs_google-sargo to /mnt/install/
[16:14:17] (native) make sparse rootfs
[16:14:18] (native) install android-tools
...
[16:19:57] *** FLASHING INFORMATION ***
[16:19:57] Run the following to flash your installation to the target device:
[16:19:57] * pmbootstrap flasher flash_rootfs
[16:19:57] Flashes the generated rootfs image to your device:
[16:19:57] /mnt/pmos_usb/pmbootstrap/chroot_native/home/pmos/rootfs/google-sargo.img
[16:19:57] (NOTE: This file has a partition table, which contains /boot and / subpartitions. That way we don't need to change the partition layout on your device.)
[16:19:57] * pmbootstrap flasher flash_vbmeta
[16:19:57] Flashes vbmeta image with verification disabled flag.
[16:19:57] * pmbootstrap flasher flash_kernel
[16:19:57] Flashes the kernel + initramfs to your device:
[16:19:57] /mnt/pmos_usb/pmbootstrap/chroot_rootfs_google-sargo/boot
[16:19:57] (NOTE: fastboot also supports booting the kernel/initramfs directly without flashing. Use 'pmbootstrap flasher boot' to do that.)
[16:19:57] * If the above steps do not work, you can also create symlinks to the generated files with 'pmbootstrap export' and flash outside of pmbootstrap.
[16:19:57]
[16:19:57] *** SSH DAEMON INFORMATION ***
[16:19:57] SSH daemon is enabled (disable with --no-sshd).
[16:19:57] Login as 'erik' with the password given during installation.
[16:19:57]
[16:19:57] *** FIREWALL INFORMATION ***
[16:19:57] Firewall is enabled, but may not work (couldn't determine if kernel supports nftables).
[16:19:57] For more information: https://postmarketos.org/firewall
[16:19:57]
[16:19:57] NOTE: chroot is still active (use 'pmbootstrap shutdown' as necessary)
[16:19:57] DONE!
Boot Into Fastboot
- Power off the Pixel 3a completely
- Press and hold Volume Down + Power simultaneously until the Fastboot screen (Android bootloader menu) appears.
- Press the Volume Down button at least once to cancel the default “start” option, otherwise the device will continue booting as usual
- Plug the Pixel 3a into the host laptop via USB
- Verify the phone is detected:
$ fastboot devices 93RAY0EQWZ fastboot
Unlock the Bootloader
- Run the unlock command:This will cause the phone to display a confirmation.
$ fastboot flashing unlock OKAY [ 0.100s] Finished. Total time: 0.100s - Confirm using the Volume and Power buttons. The phone will restart into Fastboot. As above, press the Volume Down button to cycle past the default “start” option
Flash postmarketOS
- Erase the Android device tree overlay (DTBO) to prevent kernel conflicting issues:
$ fastboot erase dtbo Erasing 'dtbo_b' OKAY [ 0.398s] Finished. Total time: 0.564s - Flash the system images:
$ pmbootstrap flasher flash_rootfs ... Finished. Total time: 104.767s [16:51:00] NOTE: chroot is still active (use 'pmbootstrap shutdown' as necessary) [16:51:00] DONE! - Flash the kernel:
$ pmbootstrap flasher flash_kernel [16:52:13] (rootfs_google-sargo) install device-google-sargo [16:52:16] (rootfs_google-sargo) install postmarketos-mkinitfs [16:52:19] (rootfs_google-sargo) mkinitfs [16:54:05] INFO: config-postmarketos-qcom-sdm670.aarch64 has suboptimal configuration (category:default), run 'pmbootstrap kconfig check' for details! [16:54:08] (native) flash kernel postmarketos-qcom-sdm670 [16:54:11] (native) install android-tools Sending 'boot_b' (25164 KB) OKAY [ 0.645s] Writing 'boot_b' OKAY [ 0.250s] Finished. Total time: 1.310s [16:54:13] You will get an IP automatically assigned to your USB interface shortly. [16:54:13] Then you can connect to your device using ssh after pmOS has booted: [16:54:13] ssh erik@172.16.42.1 [16:54:13] NOTE: If you enabled full disk encryption, you should make sure that Unl0kr has been properly configured for your device [16:54:13] NOTE: chroot is still active (use 'pmbootstrap shutdown' as necessary) [16:54:13] DONE!
Reboot To pmOS
$ fastboot reboot
Rebooting OKAY [ 0.016s]
Finished. Total time: 0.118s
First, the Fastboot screen will display a reboot is pending, then the device will reboot into the Google boot screen, and then the pmOS boot screen(!)
Debug SSH
The output from flash_rootfs mentions I can ssh into the phone:
...
[16:54:13] Then you can connect to your device using ssh after pmOS has booted:
[16:54:13] ssh erik@172.16.42.1
...
But when I try, ssh hangs. pmOS’ USB Network doc, explains this is the default IP address for a pmOS installation:
Out of the box, a postmarketOS device uses a static address of 172.16.42.1/24 on any USB network interface … If you have multiple postmarketOS devices connected, you may want to manually set IP via
setup-interfacesboth on host and on second phone to 172.16.42.x (or any other) to have internet access on all devices.
Since I’m also using pmOS as the host, trying to ssh from the host to that address actually results in an attempt to ssh into the host, which fails.
To fix this, I need to change the address of the host machine. Here are the steps:
- Identify the USB interface ID of the host
- Manually set the host’s USB IP address
- Verify the fix
My host’s USB interface ID is usb0:
$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
link/ether 1e:cd:0f:e0:06:4f brd ff:ff:ff:ff:ff:ff
4: usb0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP qlen 1000
link/ether 9a:78:42:21:77:07 brd ff:ff:ff:ff:ff:ff
Now I’ll give that interface a new address:
$ sudo ip address add 172.16.42.2/24 dev usb0
I can ping to verify:
$ ping 172.16.42.1
PING 172.16.42.1 (172.16.42.1): 56 data bytes
64 bytes from 172.16.42.1: seq=0 ttl=42 time=6.965 ms
...
But the true test is ssh:
$ ssh erik@172.16.42.1
...
Welcome to postmarketOS! o/
...
pixel3a:~$
Clean Up
Since I’m using a USB disk for my pmbootstrap storage location, I need to clean things up when I’m done before I can eject the disk. pmbootstrap mounts several things, so a simple umount on the partition is insufficient for unmounting it. A symptom of this is running umount successfully and then observing the partition is still mounted.
Check the starting state:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 1 116.1G 0 disk
└─sda1 8:1 1 116.1G 0 part /mnt/pmos_usb
mmcblk0 179:0 0 29.1G 0 disk
...
Tell pmbootstrap to clean itself up:
$ pmbootstrap shutdown
This command is relatively lightweight, leaving all the artifacts created by init in place. I can just resume work later with pmbootstrap install.
pmbootstrap creates bind mounts manually using root/kernel privileges and these mounts bypass UDisks entirely, so I can’t use udisksctl to unmount. Instead, I use umount to directly tell the Linux kernel to unmount the disk, regardless of who or what mounted it:
$ sudo umount /mnt/pmos_usb
Verify the unmount:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 1 116.1G 0 disk
└─sda1 8:1 1 116.1G 0 part
mmcblk0 179:0 0 29.1G 0 disk
...
Flush the cache:
$ sync
Eject the disk:
udisksctl power-off -b /dev/sda
Verify the eject:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
mmcblk0 179:0 0 29.1G 0 disk
├─mmcblk0p1 179:1 0 243M 0 part /boot
└─mmcblk0p2 179:2 0 28.9G 0 part /
Future Work
Ideally, the installed image would have a couple conveniences:
- Syncthing installed and registered with systemd
- Flathub configured, so non-technical users can install apps, such as Obsidian, from the Gnome Software app
- Valent pre-installed via Flatpak, so GSConnect works out of the box