On-device installer

From postmarketOS
The on-device installer extracts the target OS to the same medium (SD card, eMMC), where the installer OS was started from.
On-device installer asking whether to install to SD or eMMC.

The on-device installer provides a graphical interface to configuring the operating system before it gets installed. The first version was available in postmarketOS up to v23.06. For the v23.12 release, due to various regressions we suggest using JumpDrive in the meantime to install from SD card to eMMC, and building your own image with pmbootstrap for full disk encryption. A rewrite called ondev2 is being developed and we plan to make it available in future releases.

Features

  • Install any mobile OS, not just postmarketOS
  • Full disk encryption (optional)
  • Set the user password (numeric PIN, as expected by Phosh and Plasma Mobile)
  • Set up a dedicated ssh user (optional) (related: calamares-extensions#6)
  • Choice between installing to eMMC or SD card (postmarketos-ondev#22).

It's far from perfect, but what's there works reliably and should give a good mobile installer base to iterate upon.

Overview

The on-device installer is dual-booting between the installer OS and target OS.

Installer OS is the operating system running Calamares with the mobile module (calamares-extensions#3). In theory, any OS could be used as installer OS. However, postmarketOS as installer OS should be the most straight forward way, so it's highly recommended to produce a working image with that first. This article assumes that postmarketOS is the installer OS.

postmarketos-ondev (repo, packaging) holds the postmarketOS specific installer OS configuration.

Target OS is the operating system that will be installed. It must be able to:

  • unlock encrypted partitions on boot, with osk-sdl or similar (source, pmOS, Debian)
  • extend rootfs space on first boot
    • delete the installer OS partition (pmOS)
    • extend the root partition over all unallocated space (pmOS)

Generating an installer image

pmbootstrap init

Install pmbootstrap. Run init and select the device you would like to install. The selected UI is only relevant if postmarketOS is the target OS. Everything else can be left at default values.

$ pmbootstrap init

Note that you can automate above step as follows:

$ yes "" | pmbootstrap init
$ pmbootstrap config device pine64-pinephone

If the target OS is not postmarketOS, set the boot partition to at least the size of the boot partition of the target OS (in MiB):

$ pmbootstrap config boot_size 300

If you run into "not enough free space" errors later on (#1904), you can add extra free space to the rootfs image too (in MiB).

$ pmbootstrap config extra_space 500

Debug user

Before the generation of the image is explained, a quick note on the debug user. The installer OS will have a debug user with the username user and password y. This user is created in the installer OS, but of course not in the target OS. The installer OS also has SSH enabled by default and listens on all interfaces. You can login as the debug user either via serial, or with SSH over USB Network.

Consider disabling the debug user in production images, if you expect the target device to be available on a network (e.g. Raspberry Pi). To disable the debug user, install a file to /no-debug-user:

$ touch empty
$ pmbootstrap install --ondev --cp empty:/no-debug-user

postmarketOS as target OS

More or less, the following command. See pmbootstrap install -h for details.

$ pmbootstrap install --ondev [--sdcard=...]

Foreign target OS

Create a working directory like ~/code/ondev-mydistro, where you store the configuration files below. These will be placed in the installer OS with the pmbootstrap command listed even further below.

mobile.conf

Create a mobile.conf based on the reference. The targetDeviceRoot will be filled out automatically (the line must exist in the config though).

# Example for what would probably be needed for Mobian
---
# Target OS
osName: "mobian"
userInterface: "Phosh"
version: "2020-11-26"
username: "mobian"

# Target device
arch: "aarch64"
device: "PINE64 PinePhone"

# Commands running in the installer OS
cmdMkfsRoot: "mkfs.ext4 -L 'mobian'"

# Commands running in the target OS (chroot)
# sshd isn't installed in mobian by default
cmdSshdEnable: "true"
cmdSshdDisable: "true"

# Placeholder to be filled in by ondev-boot.sh
targetDeviceRoot: "/dev/unknown"

shellprocess.conf

postmarketos-ondev configures Calamares to run the shellprocess module at the very end of the installation. Prepare a shellprocess.conf:

# Reference:
# https://github.com/calamares/calamares/blob/master/src/modules/shellprocess/shellprocess.conf
---
dontChroot: true
timeout: 1200
script:
    # Rename pmOS_install partition to pmOS_deleteme
    - tune2fs -L pmOS_deleteme /dev/disk/by-label/pmOS_install

    # Replace installer OS boot partition with the boot partition from image
    # NOTE: Probably need to update the bootloader of the install medium too.
    # When I tested it with mobian on the PinePhone, u-boot would look for the
    # wrong dtb, uInitrd, vmlinuz files. So add the file(s) with
    # 'pmbootstrap install --ondev --cp' (see below) and then put a new dd
    # command here to flash it to the install medium.
    - dd if=/mnt/postmarketos-ondev-boot-img of=$ONDEV_BOOT_PARTITION bs=1M

    - reboot

pmbootstrap install

Download the target OS image and extract it if necessary. postmarketos-ondev expects a foreign target OS image to have two partitions, boot and rootfs [1].

Once all files are ready, run the following command. The last line is optional for integrating the target OS logo into the installer. Leave them out if you just want to test this and don't have the logo in the right format.

$ cd ~/code/ondev-mydistro
$ pmbootstrap \
  --details-to-stdout \
  install \
    --ondev \
    --no-rootfs \
    --cp \
      os-image.img:/var/lib/rootfs.img \
      mobile.conf:/etc/calamares/modules/mobile.conf \
      shellprocess.conf:/etc/calamares/modules/shellprocess.conf \
      logo.png:/usr/share/calamares/branding/default-mobile/logo.png

The resulting image file is here:

$ cd $(pmbootstrap -q config work)/chroot_native/home/pmos/rootfs
$ ls
pine64-pinephone.img

Development

Running in QEMU

You can get a relatively short test cycle by running everything in QEMU (1-2 minutes to build the new install image, run the whole installation and reboot into the target OS).

For the first install, configure pmbootstrap and build the postmarketOS target rootfs with UI=none.

#!/bin/sh -ex
pmbootstrap -q -y zap
pmbootstrap -q config device qemu-amd64
pmbootstrap -q config ui none
pmbootstrap -q config boot_size 128
pmbootstrap -q config extra_space 0

yes | pmbootstrap --details-to-stdout install --ondev

export DISPLAY=:0
pmbootstrap qemu --image-size=8G

For all following installs, use --no-rootfs to save time.

#!/bin/sh -ex
yes | pmbootstrap --details-to-stdout install --ondev --no-rootfs

export DISPLAY=:0
pmbootstrap qemu --image-size=8G

Building components from source

This section describes how to build packages for various components from their local git trees. pmbootstrap install will use these versions instead of the official binary packages. Run pmbootstrap zap -p to delete all previously built packages.

Packages will get usually get built for the host arch. Add --arch=aarch64 or similar to each pmbootstrap build line if necessary.

postmarketos-ondev

$ git clone https://gitlab.com/postmarketOS/postmarketos-ondev.git
$ pmbootstrap aportgen --fork-alpine postmarketos-ondev
$ pmbootstrap build --src=postmarketos-ondev postmarketos-ondev

calamares-extensions

$ git clone https://github.com/calamares/calamares-extensions
$ pmbootstrap aportgen --fork-alpine calamares-extensions
$ pmbootstrap build --src=calamares-extensions calamares-extensions

calamares

$ git clone https://github.com/calamares/calamares.git
$ pmbootstrap aportgen --fork-alpine calamares
$ pmbootstrap build --src=calamares calamares

How it works

pmbootstrap install --ondev does the following:

  • prepares the device rootfs, and create an image from it as usually (more or less)
  • prepares a new installer rootfs
  • copies the device rootfs image into the installer rootfs as /var/lib/rootfs.img
  • installs postmarketos-ondev, postmarketos-base and the device-package into the installer rootfs
  • runs ondev-prepare.sh
  • creates an image from the installer rootfs, with reserved space

The installer image has the following partition layout:

  • p1: label=pmOS_boot
  • p2: (reserved space)
  • p3: label=pmOS_install

This installer image can be transmitted to the device via the usual methods (fastboot flash, via SD card, ...). When booting into the installer's initramfs, the usual postmarketOS initramfs code will prefer pmOS_install over pmOS_root (so we don't end up in an invalid state if an installation was aborted). So the pmOS_install partition will be mounted as / and the boot continues.

ondev-boot.sh runs:

  • calculates ONDEV_PARTITION_TARGET
  • configures tinydm and i3, then starts tinydm (we may use something other than i3 in the future, but this works well even with framebuffer devices)
  • the config will start Calamares

Calamares runs:

  • loads settings.conf
  • shows the custom mobile module
  • this module asks for the FDE password, formats the target partition with cryptsetup and ext4 and mounts it (bypassing some of the usual Calamares logic, as Calamares relies on grub2 and encrypted initramfs)
  • "unpackfs" extracts rootfs.img and gives a nice progress bar
  • "shellprocess" renames pmOS_install to pmOS_deleteme and reboots (also replaces boot partition in foreign target OS case)

The partition layout looks like this now:

  • p1: label=pmOS_boot
  • p2: label=pmOS_root
  • p3: label=pmOS_deleteme

postmarketOS initramfs does the following as target OS during first boot:

  • deletes the pmOS_deleteme partition (postmarketos-ondev#1)
  • resizes pmOS_root to use up all available free space (even beyond pmOS_deleteme)
  • runs osk-sdl as usually for encrypted installations
  • boots into pmOS_root

Related