Debian on The PinePhone

Installing and setting up Debian Testing on the PinePhone.

Introduction

Goals

This is a proof of concept guide on using Debian with the PinePhone. Our goals are:

  • Data Encryption
  • Full Calling and Messaging Functionality
  • Use of Android Applications
  • Stay Close to Trusted Upstreams

Gotchas

There are nonetheless some gotchas with this guide:

  • Rather than using the Debian Kernel, we use megi1’s patched kernel
  • Speakers do not work outside of calling though bluetooth or the headphone jack should still work
  • No cellular data (yet?) though calling/messaging/ethernet/WiFi is unaffected

Requirements

To follow this guide as written you need the following items:

  • PinePhone
  • Linux Computer
  • USB-C to USB-A Cable
  • SD Card

High Level Design Description

To do this we use a largely typical boot process. Tow-Boot will load the unencrypted kernel and custom initramfs with embedded wayland session, which then subsequently accepts the password from the user and opens the encrypted Btrfs root filesystem. Finally we switch-root and load Phosh, (though Plasma Mobile is available from external repos) and waydroid for android applications.

We choose Debian for this becuase:

  • It is stable (though we are using Testing) and well supported
  • It packages phosh in it’s repositories
  • It has a convient bootstrapping tool called debootstrap

Our filesystem will look like the following on 16/32GB devices:

  • eMMC

    • 512M/1G Boot with Kernel, Initramfs and U-Boot: /boot/efi
    • Luks
      • Btrfs: /
      • Swap 2G/4G
  • SD Card

    • Luks
      • Btrfs: /home

Note that we could also have kept the rootfs unencrypted and encrypted only /home. This would not require including a GUI in the Initramfs. As the design is described above, we will need to insert an on-screen keyboard application into the initramfs to receive the password from the touchscreen.

Booting

Tow Boot

First we install Tow-Boot. We use Tow-Boot becase it has a simple extlinux.conf or UEFI interface and it can present the PinePhone’s storage as a USB mass storage device for manipulator from the Computer.

This is easily done as described here. Download the correct archive from their github releases. I use pine64-pinephoneA64-2021.10-004.tar.xz. Insert your SD Card (I assume this is /dev/mmcblk0)into your reader and do something similar to:

1
2
3
tar xvf pine64-pinephoneA64-2021.10-004.tar.xz
cd pine64-pinephoneA64-2021.10-004
dd if=mmcboot.installer.img of=/dev/mmcblk0 bs=1M oflag=direct,sync status=progress

Next insert the SD Card into your pinephone and switch it on. As given in the instruction:

When starting up with Tow-Boot, which the installer image use, the phone will vibrate slightly and the LED will turn red. After a short moment, the LED should turn yellow. A few moments later the display will turn on with a blue colour, or will directly boot to the installer GUI. In the installer GUI, select “Install Tow-Boot to eMMC Boot”. It is not necessary to erase the storage before installing. Erasing the storage can be used to uninstall Tow-Boot (or any other platform firmware installed to the eMMC Boot partition). Once installed, remove the installation media, and verify Tow-Boot starts from power-on.

Once this is done, after removing the SD Card, switch it off and follow the instructions to start the phone in USB Mass Storage mode:

The phone can be started in USB Mass Storage mode by holding the volume up button at startup before and during the second vibration. The LED will turn blue if done successfully. In this mode, the phone will work like a USB drive when connected to a host computer.

Finally, attach it to your computer via USB.

Formatting

Identify the disk path of your PinePhone that is now connected via USB as a Mass Storage Device. We will assume that this is /dev/sdz. Additionally, insert the SD Card into your reader. We will assume that this is /dev/mmcblk0. We then format the drives with commands similar to the following. This will wipe your drives: First we wipe the SD Card to remove the tow boot installer:

1
dd if=/dev/urandom of=/dev/mmcblk0 bs=4M status=progress #We only need to overwrite the first few MB. The rest is optional.

Next we create the partition table for the eMMC and wipe the new partition:

1
2
fdisk /dev/sdz
dd if=/dev/urandom of=/dev/sdz2 bs=4M status=progress # Optional

Then we encrypt the data partitons with Luks.

1
2
cryptsetup luksFormat /dev/sdz2 --cipher aes-xts-essiv:sha256 --iter-time 3000
cryptsetup luksFormat /dev/mmcblk0 --cipher aes-xts-essiv:sha256 --iter-time 3000

Note that we should be conservative with the above iter time. A high iter-time will result in a drastically higher unlock time on the PinePhone. Then open the encrypted filesystems.

1
2
cryptsetup luksOpen /dev/sdzp2 emmc
cryptsetup luksOpen /dev/mmcblk0 home

Format the encrypted root:

1
2
fdisk /dev/mapper/emmc
partprobe /dev/mapper/emmc

Finally format the partitions:

1
2
3
4
mkfs.vfat /dev/sdz1
mkfs.btrfs /dev/mapper/emmc1
mkfs.btrfs /dev/mapper/home
mkswap /dev/mapper/emmc2

Now close the encrypted filesystems:

1
2
3
kpartx -d /dev/mapper/emmc
cryptsetup close emmc
cryptsetup close home

Kernel

The mainline Linux Kernel does not currently have the drivers necessary to reliably manage the USB-C port on the PinePhone or to manage the Realtek RTL8723CS WiFi antenna on the PinePhone. The PinePhone Pro may fair better however. We will use megi1’s kernel here. More info is available on https://xnux.eu/.

Bootstrap

This section is a little tedious; we will use Qemu’s Arm64 emulation to complete the remainder of the install. If you have an Arm64 device, it is a good idea to do this natively or with KVM.

First acquire qemu-system-aarch64 using your package manager and fetch an OpenSuse Tumbleweed ISO like wget https://download.opensuse.org/ports/aarch64/tumbleweed/iso/openSUSE-Tumbleweed-KDE-Live-aarch64-Current.iso. We use a Tumbleweed iso because it comes with a serial console enabled and OpenSuSE packages debootstrap. Then start up qemu, passing through the pinephones drives like so:

1
2
3
4
5
6
qemu-system-aarch64 \
-M virt -m 4G -cpu max -smp 4 --display none --serial mon:stdio \
-drive file=openSUSE-Tumbleweed-KDE-Live-aarch64-Current.iso,media=cdrom,if=virtio \
-drive file=/dev/mmcblk0,format=raw,if=virtio \
-drive file=/dev/sdz,format=raw,if=virtio \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/qemu/edk2-aarch64-code.fd

Note that /usr/share/qemu/edk2-aarch64-code.fd is the path to arm64 UEFI firmware and may be located differently on your system. Alternatively you can use libvirt and the virt-manager GUI.

Now, wait for the VM to start up. Eventually you will see a login prompt. Login with the username “linux” and an empty password. Note that I like to use -o compress-force=zstd. You may use lzo, zlib instead of zstd or compress instead of compress-force or remove the argument altogether. We then mount the drives as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cryptsetup open /dev/vdb home
cryptsetup open /dev/vdc2 emmc
partprobe /dev/mapper/emmc
mkdir -p /mnt/{os/,}btrfs
mount -o compress-force=zstd /dev/mapper/emmc-part1 /mnt/btrfs
btrfs subvol create /mnt/btrfs/root
mount -o compress-force=zstd,subvol=root /dev/mapper/emmc-part1 /mnt/os
mkdir -p /mnt/os/{boot/efi,home,btrfs}
mount -o compress-force=zstd /dev/mapper/emmc-part1 /mnt/os/btrfs
mount /dev/vdc1 /mnt/os/boot/efi
mount -o compress-force=zstd /dev/mapper/home /mnt/os/home

We are now ready to begin bootstrapping. While we could have partially done this on the host with debootstrap --foreign. There seemed to be some bugs with a circular dependancy so I opted to do the it completely in the VM. We the call debootstrap and wait:

1
2
zypper in debootstrap binutils
debootstrap --arch=arm64 testing /mnt/os https://deb.debian.org/debian/

We then note the UUID of /dev/vdc1:

1
2
lslbk
blkid

Then create an fstab at /mnt/os/etc/fstab like:

1
2
3
4
/dev/mapper/emmc / btrfs subvol=root,compress-force=zstd,noatime 0 0
/dev/mapper/emmc /btrfs btrfs compress-force=zstd,noatime 0 0
/dev/mapper/home /home btrfs compress-force=zstd,noatime 0 0
UUID=<UUID of /dev/vdc1> /boot/efi vfat defaults,noatime 0 2

We the chroot into the new install:

1
2
3
4
5
6
7
8
mount --types proc /proc /mnt/os/proc
mount --rbind /sys /mnt/os/sys
mount --make-rslave /mnt/os/sys
mount --rbind /dev /mnt/os/dev
mount --make-rslave /mnt/os/dev
mount --bind /run /mnt/os/run
mount --make-slave /mnt/os/run
chroot /mnt/os /bin/bash

We the configure the PinePhone’s system:

1
2
3
apt install locales
dpkg-reconfigure locales
apt install aptitude

If you get a statoverride error, run the command below and repeat the steps above:

1
rm /var/lib/dpkg/statoverride

Then edit /etc/apt/sources.list and edit it so that it looks like:

1
2
3
4
5
6
7
8
deb https://deb.debian.org/debian testing main contrib non-free
deb-src https://deb.debian.org/debian testing main contrib non-free

deb https://deb.debian.org/debian-security testing-security main contrib non-free
deb-src https://deb.debian.org/debian-security testing-security main contrib non-free

deb https://deb.debian.org/debian testing-updates main contrib non-free
deb-src https://deb.debian.org/debian testing-updates main contrib non-free

Then install packages on the new system:

1
2
aptitude update # You do not need to upgrade now
aptitude install phosh phosh-phone firmware-realtek cryptsetup btrfs-progs wpasupplicant wget curl sudo git lz4

Note that at this point, it is good practice to create a new user and configue sudo. This is not detailed in this guide but you may wish to do this. Finally fetch and install the kernel:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
wget https://xff.cz/kernels/5.18/pp.tar.gz
tar xvf pp.tar.gz
cd pp-5.18
cp Image /boot/efi
cp board-1.2.dtb /boot/efi
mkdir -p /lib/modules
cp -r modules/lib/modules/* /lib/modules
cd ..
git clone https://megous.com/git/linux-firmware --depth=1
cp -r linux-firmware/* /lib/firmware/

Initramfs

We will use an Alpine Linux install as the initramfs which ends up at around 100M. We do this because we would like to embed an on-screen-keyboard in the initramfs for the passwords of the encrypted filesystems. Dracut/Plymouth does not currently have this feature. This is easier than creating a new Dracut module doing the same as the package manger ensures that everything required is included in initramfs while otherwise, we would need to identify many of these files ourselves.

First note the UUIDs of the SD Card (/dev/vdb) and encrypted emmc partition (/dev/vdc2):

1
2
lsblk
blkid

Fetch the Alpine package manager and use it to bootstrap the system:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
btrfs subvol create /btrfs/alpine

mkdir apk-tools
cd apk-tools
wget https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/aarch64/apk-tools-static-2.12.9-r3.apk
tar zxfv apk-tools-static-*.apk

./sbin/apk.static --arch aarch64 -X https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/ -U --allow-untrusted --root /btrfs/alpine --initdb add alpine-base
./sbin/apk.static --arch aarch64 -X https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/ -X https://dl-cdn.alpinelinux.org/alpine/latest-stable/community/ -U --allow-untrusted --root /btrfs/alpine add sway cryptsetup udev seatd multipath-tools dash mesa-dri-gallium ttf-dejavu wvkbd s6 foot
rm -rf /btrfs/alpine/var/cache/apk
cd ..

cp -r pp-5.18/modules/lib/modules/* /btrfs/alpine/lib/modules
cp -r linux-firmware/* /btrfs/alpine/lib/firmware

chroot /btrfs/alpine /bin/sh
adduser -h /home/user -s /usr/bin/dash -D user
adduser user input
adduser user video

Note that vi is available inside the chroot. Put the follwing script at /init (in the chroot):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/dash

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mkdir /dev/pts /dev/shm
mount -t devpts devpts /dev/pts
mount -t tmpfs none /dev/shm -o size=128M

grep rd.break /proc/cmdline && exec sh

modprobe -a goodix-ts sun8i-ce

s6-svscan /service > /tmp/svc.log 2>&1 &

for I in $(seq 1 3) ; do
        sleep 1
        s6-svc -o /service/foot1
        s6-svwait -d /service/foot1
        [ -e /dev/mapper/emmc ] && break
done

[ -e /dev/mapper/emmc ] || exec sh

for I in $(seq 1 3) ; do
        s6-svc -o /service/foot2
        s6-svwait -d /service/foot2
        [ -e /dev/mapper/home ] && break
done

[ -e /dev/mapper/home ] || exec sh

s6-svscanctl -t /service
kpartx -a /dev/mapper/emmc

swapon /dev/mapper/emmc2 -p 1024

mount -t btrfs -o ro,compress-force=zstd,subvol=root /dev/mapper/emmc1 /sysroot
mount -t btrfs -o compress-force=zstd /dev/mapper/emmc1 /sysroot/btrfs
mount -t btrfs -o compress-force=zstd /dev/mapper/home /sysroot/home

exec switch_root /sysroot /sbin/init

We use s6 to manage services. This makes it easy to control the lifsespan of our wayland components and especially, terminate the processes when init is done and ready to switch_root.

We then create an s6 scan directory:

1
2
3
mkdir -p /service/seatd /service/udev /service/sway /service/kbd /service/foot1 /service/foot2
ln -s /sbin/udevd /service/udev/run
ln -s /usr/bin/seatd /service/seatd/run

Put the following script at /service/sway/run:

1
2
3
4
5
6
#!/usr/bin/dash
s6-svwait /service/udev /service/seatd || exit 1
udevadm trigger
chown :video /dev/dri/card0 /run/seatd.sock
chmod g+rw /dev/dri/card0 /run/seatd.sock
XDG_RUNTIME_DIR=/home/user exec s6-setuidgid user sway

Put the following script at /service/kbd/run:

1
2
3
#!/usr/bin/dash
s6-svwait /service/sway || exit 1
XDG_RUNTIME_DIR=/home/user WAYLAND_DISPLAY=wayland-1 exec s6-setuidgid user wvkbd-mobintl

Put the following script at /service/foot1/run replacing <UUID of emmc> with the value you wrote down previously:

1
2
3
#!/usr/bin/dash
s6-svwait /service/sway || exit 1
XDG_RUNTIME_DIR=/home/user WAYLAND_DISPLAY=wayland-1 exec foot -T "Decrypt Rootfs" /sbin/cryptsetup open $(findfs UUID=<UUID of emmc>) emmc

Put the following script at /service/foot2/run replacing <UUID of SD Card> with the value you wrote down previously:

1
2
3
#!/usr/bin/dash
s6-svwait /service/sway || exit 1
XDG_RUNTIME_DIR=/home/user WAYLAND_DISPLAY=wayland-1 exec foot -T "Decrypt Home" /sbin/cryptsetup open $(findfs UUID=<UUID of SD Card>) home

Finally mark the scripts as executable and set foot1 and foot2 to initally down:

1
2
chmod +x /init /service/sway /service/kbd /service/foot1 /service/foot2
touch /service/foot1/down /service/foot2/down

Then we exit the chroot and build the initramfs:

1
2
3
exit
cd /btrfs/alpine
find . -print0 | cpio --null --create --format=newc | lz4 -l --best > /boot/efi/initramfs.lz4

Omit the --best to speed it up.

Finishing Up With Boot Procedure

Finally, the pinephone is (almost) bootable and we can put on the finishing touches:

1
2
3
adduser <username>
passwd root # Optional; sudo works too
systemctl enable phosh

The audio and modem on the PinePhone require interventaion to enable/setup. We use the scripts below to do this:

Put the following script at /usr/bin/setup_pp.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/sh

# Turn on the modem
echo 1 > /sys/class/modem-power/modem-power/device/powered

sleep 10

# Enable audio devices
/usr/bin/setup_pp_audio -m -s -h -l -e
/usr/bin/setup_pp_audio -m -s -h -l -e -2

We use audio-pp.c from https://xnux.eu/devices/feature/audio-pp.html to set up audio:

1
2
wget https://praveen.org.uk/assets/audio-pp.c
cc audio-pp.c -O2 -o /usr/bin/setup_pp_audio

Create a systemd service to run the above script. Put it at /etc/systemd/system/setup_pp.service:

1
2
3
4
5
6
7
8
9
[Unit]
Description=Setup pp

[Service]
ExecStart=/usr/bin/setup_pp.sh
Type=oneshot

[Install]
WantedBy=multi-user.target

Mark the script as executable and enable the service:

1
2
chmod +x /usr/bin/setup_pp.sh
systemctl enable setup_pp

Finally we use a extlinux.conf for the bootloader. This is simply the easiest way to do this. Put the following config at /boot/efi/extlinux/extlinux.conf

1
2
3
4
5
6
7
8
9
timeout 1

default pp

label pp
	kernel /Image
	append ro root=/dev/mapper/emmc1 rootflags=ro,compress-force=zstd,subvol=root
	FDT /board-1.2.dtb
	initrd /initramfs.lz4

Boot The Pinepone

Finally, the PinePhone should be bootable. It is reccomended to set up SSH or enable serial by appending console=ttyS0,115200 to the kernel command line above. Now poweroff the VM, sync on the VM host, unplug the PinePhone and boot it up. If everything works, great! If not, append rd.break to the above kernel command line. The initramfs will drop to a shell after which you can use some sort of USB-C keyboard/adapter or the PinePhone serial cable to check for errors and boot the pinephone manually (using essentially the commands in /init after cryptsetup from foot1 and foot2). The initramfs almost certainly contains all the tools needed for this.

Waydroid

Install

Now we install waydroid. Unfortunately, python3-gbinder is not yet packaged for the current Debian Testing; we must build this from source. We will use install_waydroid.sh from this gist to do this. Install libgl1-mesa-glx and run the script:

1
2
3
4
aptitude install libgl1-mesa-glx
wget https://praveen.org.uk/assets/install_waydroid.sh
cat install_waydroid.sh
bash install_waydroid.sh

Put the following at /etc/gbinder.conf:

1
2
[General]
ApiLevel = 29

There are a few bugs relating to WayDroid’s networking that we will need to work around:

1
sed -i 's/LXC_USE_NFT="false"/LXC_USE_NFT="true"/g' /lib/waydroid/data/scripts/waydroid-net.sh

Finally start waydroid:

1
2
3
waydroid init -s GAPPS
systemctl enable waydroid-container
systemctl restart waydroid-container

Omit the -s GAPPS above to install an Android Image without Google serices. Now, start up phosh and select waydroid in the app drawer. It should get you to an Android session without networking. To enable networking note down the ipv4 address of the interface below:

1
2
ip a show dev waydroid0
#Look for the inet entry

Then set it as the default route in the WayDroid session:

1
echo "ip route add default via <ipv4 address>" | waydroid shell

Now the container has networking though, unfortunately, this needs to be done each restart though only after manually starting the waydroid session. See this issue for info.

F-Droid (Optional)

Now we set up app repositories. Setting up F-Droid is simple:

1
2
wget https://f-droid.org/F-Droid.apk
waydroid app install F-Droid.apk

Google Play (Optional)

Google Play is more complicated. This requires the GAPPS image. Firstly we (at time of writing) need to update the Android System WebView in the image. Download the APK from here and transfer it to the PinePhone (scp?). On the PinePhone, in the Android session, go to settings (not Phosh settings), About Phone, then tap Build Number several times till developer settings get enabled. Note down the ipv4 adress of the PinePhone in the same section. Then search for, and enable ‘ADB over network’. Finally use adb to install the downloaded apk:

1
2
3
aptitude install adb
adb connect <ipv4 address>
adb install com.google.android.*.apk

Next retreive the android_id of the device and submit it to google:

1
2
waydroid shell
ANDROID_RUNTIME_ROOT=/apex/com.android.runtime sqlite3 /data/data/com.google.android.gsf/databases/gservices.db "select * from main where name = \"android_id\";"

Copy the android ID and submit it at https://www.google.com/android/uncertified/. Finally wait 20 minutes or so; after which you should be able to login to the Play Store an install apps like a typical android device.

Final Thoughts

In the end, we have a functional mobile device as we wanted. Linux alone provides basic calling and messaging functionality sufficient for use as a phone while WayDroid additionally provides a way to run Android apps seamlessy on top of a Debian Userspace and Linux Kernel allowing use of Android Apps (e.g. WhatsApp, FaceBook) on top of a trused Debian Kernel with auditable Encryption (Luks), Tunnelling (OpenVPN/Wireguard), Inspection (TOMOYO/Apparmor) and much more in a standard mobile form factor. My testing also showed very respectable performance on 2d Android Apps (e.g Messaging apps, eReaders) though video playback was poor.

The final device is indeed a proof of concept of a mobile device constructed from upstream Debian however, it lacks the likes of the polish that might be found on distributions dedicated to mobile use like Mobian or PostmarketOS.

Built with Hugo
Theme Stack designed by Jimmy