diff --git a/main/postmarketos-base/APKBUILD b/main/postmarketos-base/APKBUILD index 518874dd0..54844b448 100644 --- a/main/postmarketos-base/APKBUILD +++ b/main/postmarketos-base/APKBUILD @@ -1,10 +1,11 @@ pkgname=postmarketos-base pkgver=32 -pkgrel=4 +pkgrel=5 pkgdesc="Meta package for minimal postmarketOS base" url="https://postmarketos.org" arch="noarch" license="GPL-3.0-or-later" +makedepends="postmarketos-initramfs" depends=" alpine-base devicepkg-utils>=0.2.0 @@ -12,7 +13,8 @@ depends=" logbookd openssh postmarketos-bootsplash - postmarketos-initramfs + postmarketos-ramdisk + postmarketos-mvcfg postmarketos-keys sudo-virt zram-init diff --git a/main/postmarketos-initramfs-minimal/00-default.modules b/main/postmarketos-initramfs-minimal/00-default.modules new file mode 100644 index 000000000..768857c65 --- /dev/null +++ b/main/postmarketos-initramfs-minimal/00-default.modules @@ -0,0 +1,23 @@ +aes +btrfs +crc32_generic +crc32c_generic +dm-crypt +dm_crypt +evdev +ext4 +f2fs +kernel/arch/*/crypto/ +kernel/crypto/ +kernel/fs/overlayfs/ +libaes +loop +nls_ascii +nls_cp437 +nls_iso8859_1 +nls_utf8 +usb_f_rndis +usb_f_ncm +usb-storage +vfat +xts diff --git a/main/postmarketos-initramfs-minimal/00-initramfs-base.dirs b/main/postmarketos-initramfs-minimal/00-initramfs-base.dirs new file mode 100644 index 000000000..bfde470c6 --- /dev/null +++ b/main/postmarketos-initramfs-minimal/00-initramfs-base.dirs @@ -0,0 +1,12 @@ +/bin +/boot +/dev +/etc +/lib +/proc +/run +/sbin +/sys +/sysroot +/tmp +/usr diff --git a/main/postmarketos-initramfs-minimal/00-initramfs-base.files b/main/postmarketos-initramfs-minimal/00-initramfs-base.files new file mode 100644 index 000000000..4485d4fcf --- /dev/null +++ b/main/postmarketos-initramfs-minimal/00-initramfs-base.files @@ -0,0 +1,14 @@ +/bin/busybox +/bin/busybox-extras +/bin/sh +/etc/deviceinfo +/etc/unudhcpd.conf +/lib/mdev/persistent-storage +/sbin/blkid +/usr/bin/unudhcpd +/usr/sbin/kpartx +/usr/share/deviceinfo/deviceinfo +/usr/share/initramfs/init.sh:/init +/usr/share/initramfs/init_functions.sh:/init_functions.sh +/usr/share/initramfs/mdev.conf:/etc/mdev.conf +/usr/share/misc/source_deviceinfo diff --git a/main/postmarketos-initramfs-minimal/00-initramfs-extra-base.files b/main/postmarketos-initramfs-minimal/00-initramfs-extra-base.files new file mode 100644 index 000000000..dc574917b --- /dev/null +++ b/main/postmarketos-initramfs-minimal/00-initramfs-extra-base.files @@ -0,0 +1,8 @@ +/lib/libz.so.1 +/sbin/btrfs +/sbin/dmsetup +/sbin/e2fsck +/sbin/switch_root +/usr/sbin/parted +/usr/sbin/resize2fs +/usr/sbin/resize.f2fs diff --git a/main/postmarketos-initramfs-minimal/APKBUILD b/main/postmarketos-initramfs-minimal/APKBUILD new file mode 100644 index 000000000..41859678e --- /dev/null +++ b/main/postmarketos-initramfs-minimal/APKBUILD @@ -0,0 +1,97 @@ +# WARNING: this package is deprecated and currently unmaintained. It should +# only be used on devices that do not have space for the full size ramdisk. +pkgname=postmarketos-initramfs-minimal +pkgver=2.5.6 +pkgrel=0 +pkgdesc="Deprecated minimal initramfs for space constrained devices" +url="https://postmarketos.org" +options="!check" # no tests +provides="postmarketos-ramdisk" +provider_priority=1 +depends=" + blkid + btrfs-progs + busybox-extras + bzip2 + cryptsetup + device-mapper + devicepkg-utils>=0.2.0 + e2fsprogs + e2fsprogs-extra + f2fs-tools + lz4 + mdev-conf + multipath-tools + parted + postmarketos-fde-unlocker + postmarketos-mkinitfs>=2.2 + unudhcpd + util-linux-misc + xz + " +source=" + 00-default.modules + 00-initramfs-base.dirs + 00-initramfs-base.files + 00-initramfs-extra-base.files + init.sh + init_functions.sh + unudhcpd.conf + mdev.conf + " +arch="noarch" +license="GPL-2.0-or-later" + +build() { + mkdir -p "$builddir" + + # Replace <> with the actual version + sed "s|<>|$pkgver-r$pkgrel|" \ + "$srcdir/init.sh" > "$builddir/init.sh" +} + +package() { + install -Dm644 "$srcdir/unudhcpd.conf" \ + "$pkgdir/etc/unudhcpd.conf" + + install -Dm644 "$srcdir/init_functions.sh" \ + "$pkgdir/usr/share/initramfs/init_functions.sh" + + install -Dm755 "$builddir/init.sh" \ + "$pkgdir/usr/share/initramfs/init.sh" + + install -Dm644 "$srcdir"/00-initramfs-base.dirs \ + -t "$pkgdir"/usr/share/mkinitfs/dirs/ + mkdir -p "$pkgdir"/etc/mkinitfs/dirs + + install -Dm644 "$srcdir"/00-default.modules \ + -t "$pkgdir"/usr/share/mkinitfs/modules/ + mkdir -p "$pkgdir"/etc/mkinitfs/modules + + install -Dm644 "$srcdir"/00-initramfs-base.files \ + -t "$pkgdir"/usr/share/mkinitfs/files/ + mkdir -p "$pkgdir"/etc/mkinitfs/files + + install -Dm644 "$srcdir"/00-initramfs-extra-base.files \ + -t "$pkgdir"/usr/share/mkinitfs/files-extra/ + mkdir -p "$pkgdir"/etc/mkinitfs/files-extra + + install -Dm644 "$srcdir"/mdev.conf \ + -t "$pkgdir"/usr/share/initramfs/ + + mkdir -p "$pkgdir"/usr/share/mkinitfs/hooks + mkdir -p "$pkgdir"/usr/share/mkinitfs/hooks-extra + mkdir -p "$pkgdir"/etc/mkinitfs/hooks + mkdir -p "$pkgdir"/etc/mkinitfs/hooks-extra +} + +sha512sums=" +59be0649ed87a72d93624bd8a2e3f8c99a0f32f7b7a26f99436de782beba55671472c269eeee86440efc87e0d7148a0bb335fa537791092e73878ca21330544a 00-default.modules +5b364300f31c91fd0591eb0715f67cbf5383f45246a5fb9f34b79f7cb2e3b15768b2130e5f32f816cc169950f988c1beabc879ba31645c58ce131a288dbc071d 00-initramfs-base.dirs +ab41b45b0613f25a61114ed8c8b92bc53c60838f6e2e0ba18c76e5369b2984e6023a0661887692673aca3f647f268c468a468f6b1ac424cfee609017a89481dd 00-initramfs-base.files +8a4adad3785af474b36a09a05f6a3b2c4b4f43aac331a53b903abfa51ea12be1e3d1d807b7a6e66a1346815f3b0044daf8cd62e21e2dc75d2db13ee265a72985 00-initramfs-extra-base.files +b1462989633c65a102e7ae7ced9c41a00673b4c7434cd3fe16c229c39bb870d7e850b0d317487191b7d7ca32c75b2eed1561b1facf405bd4d876d32c71badcf3 init.sh +8ff684e8245217cc9461297bce55dd8b417f0478ef38cdbddf7662f9ecc4324ca09e75978261084e00f5a1f2e36cc3b25a609a9bdca4addc5a60d3c370cbdb90 init_functions.sh +ba3275a9af788c7c782322a22a0f144d5e50e3498ea6886486a29331f23ae89cd32d500a3635cfa7cab369afba92edc18aeca64ccbf0cd589061cce23d15b46c unudhcpd.conf +675e7d5bee39b2df7d322117f8dcaccc274d61beaf4d50ead19bbf2109446d64b1c0aa0c5b4f9846eb6c1c403418f28f6364eff4537ba41120fbfcbc484b7da7 mdev.conf +" diff --git a/main/postmarketos-initramfs-minimal/init.sh b/main/postmarketos-initramfs-minimal/init.sh new file mode 100644 index 000000000..69f302e1b --- /dev/null +++ b/main/postmarketos-initramfs-minimal/init.sh @@ -0,0 +1,97 @@ +#!/bin/sh +# shellcheck disable=SC1091 + +IN_CI="false" + +[ -e /hooks/10-verbose-initfs.sh ] && set -x + +[ -e /hooks/05-ci.sh ] && IN_CI="true" + +[ -e /etc/unudhcpd.conf ] && . /etc/unudhcpd.conf +. ./init_functions.sh +. /usr/share/misc/source_deviceinfo +[ -e /etc/os-release ] && . /etc/os-release +# provide a default for os-release's VERSION in case the file doesn't exist +VERSION="${VERSION:-unknown}" + +# This is set during packaging and is used when triaging bug reports +INITRAMFS_PKG_VERSION="<>" + +export PATH=/usr/bin:/bin:/usr/sbin:/sbin +/bin/busybox --install -s +/bin/busybox-extras --install -s + +# Mount everything, set up logging, modules, mdev +mount_proc_sys_dev +setup_log +setup_firmware_path + +if [ "$IN_CI" = "false" ]; then + # shellcheck disable=SC2154 + load_modules /lib/modules/initramfs.load "libcomposite" + setup_framebuffer + show_splash "Loading..." + setup_mdev + setup_dynamic_partitions "${deviceinfo_super_partitions:=}" + mount_subpartitions +else + # loads all modules + setup_udev +fi +run_hooks /hooks + +if [ "$IN_CI" = "true" ]; then + echo "PMOS: CI tests done, disabling console and looping forever" + dmesg -n 1 + fail_halt_boot +fi + +# Always run dhcp daemon/usb networking for now (later this should only +# be enabled, when having the debug-shell hook installed for debugging, +# or get activated after the initramfs is done with an OpenRC service). +setup_usb_network +start_unudhcpd + +wait_boot_partition +mount_boot_partition /boot +extract_initramfs_extra /boot/initramfs-extra +setup_udev +run_hooks /hooks-extra + +# For testing the mass storage gadget log export function. We use a flag +# file on /boot so that we can test it on all devices as modifying the +# kernel cmdline is not always possible. +if [ -e /boot/.pmos_export_logs ]; then + echo "PMOS: Exporting logs via mass storage gadget" + show_splash "Exporting boot logs..." + # Delete the flag so we don't soft-brick the device by always booting + # to the log export mode. + mount -o remount,rw /boot + rm -f /boot/.pmos_export_logs + fail_halt_boot +fi + +wait_root_partition +delete_old_install_partition +resize_root_partition +unlock_root_partition +resize_root_filesystem +mount_root_partition + +# Mount boot partition into sysroot, so OpenRC doesn't need to do it (#664) +umount /boot +mount_boot_partition /sysroot/boot "rw" + +init="/sbin/init" +setup_bootchart2 + +# Switch root +killall telnetd mdev udevd msm-fb-refresher 2>/dev/null + +# shellcheck disable=SC2093 +exec switch_root /sysroot "$init" + +echo "ERROR: switch_root failed!" +echo "Looping forever. Install and use the debug-shell hook to debug this." +echo "For more information, see " +fail_halt_boot diff --git a/main/postmarketos-initramfs-minimal/init_functions.sh b/main/postmarketos-initramfs-minimal/init_functions.sh new file mode 100644 index 000000000..cc584bffb --- /dev/null +++ b/main/postmarketos-initramfs-minimal/init_functions.sh @@ -0,0 +1,907 @@ +#!/bin/sh +# This file will be in /init_functions.sh inside the initramfs. +ROOT_PARTITION_UNLOCKED=0 +ROOT_PARTITION_RESIZED=0 +PMOS_BOOT="" +PMOS_ROOT="" + +# Redirect stdout and stderr to logfile +setup_log() { + local log_to_console="" + + grep -q PMOS_NO_OUTPUT_REDIRECT /proc/cmdline && log_to_console="true" + + echo "### postmarketOS initramfs ###" + + if [ -z "$log_to_console" ]; then + echo "Add PMOS_NO_OUTPUT_REDIRECT to your kernel command line" + echo "to enable initramfs logging to console (e.g. for serial)." + fi + + # Start redirect + exec >/pmOS_init.log 2>&1 + echo "### postmarketOS initramfs ###" + + # Pipe logs to console if PMOS_NO_OUTPUT_REDIRECT is set + if [ -n "$log_to_console" ]; then + tail -f /pmOS_init.log > /dev/console & + fi + + # Pipe logs to pmsg if PMOS_NO_OUTPUT_REDIRECT is set and /dev/pmsg0 is available + if [ -n "$log_to_console" ] && [ -e "/dev/pmsg0" ]; then + tail -f /pmOS_init.log > /dev/pmsg0 & + fi +} + +mount_proc_sys_dev() { + # mdev + mount -t proc -o nodev,noexec,nosuid proc /proc || echo "Couldn't mount /proc" + mount -t sysfs -o nodev,noexec,nosuid sysfs /sys || echo "Couldn't mount /sys" + mount -t devtmpfs -o mode=0755,nosuid dev /dev || echo "Couldn't mount /dev" + mount -t tmpfs -o nosuid,nodev,mode=0755 run /run || echo "Couldn't mount /run" + + mkdir /config + mount -t configfs -o nodev,noexec,nosuid configfs /config + + # /dev/pts (needed for telnet) + mkdir -p /dev/pts + mount -t devpts devpts /dev/pts +} + +setup_firmware_path() { + # Add the postmarketOS-specific path to the firmware search paths. + # This should be sufficient on kernel 3.10+, before that we need + # the kernel calling udev (and in our case /usr/lib/firmwareload.sh) + # to load the firmware for the kernel. + echo "Configuring kernel firmware image search path" + SYS=/sys/module/firmware_class/parameters/path + if ! [ -e "$SYS" ]; then + echo "Kernel does not support setting the firmware image search path. Skipping." + return + fi + # shellcheck disable=SC3037 + echo -n /lib/firmware/postmarketos >$SYS +} + +# shellcheck disable=SC3043 +load_modules() { + local file="$1" + local modules="$2" + [ -f "$file" ] && modules="$modules $(grep -v ^\# "$file")" + # shellcheck disable=SC2086 + modprobe -a $modules +} + +setup_mdev() { + # Start mdev daemon + mdev -d +} + +setup_udev() { + # Use udev to coldplug all devices so that they can be used via libinput (e.g. + # by unl0kr). This is the same series of steps performed by the udev, + # udev-trigger and udev-settle RC services. See also: + # - https://git.alpinelinux.org/aports/tree/main/eudev/setup-udev + # - https://git.alpinelinux.org/aports/tree/main/udev-init-scripts/APKBUILD + if command -v udevd > /dev/null && command -v udevadm > /dev/null; then + udevd -d + udevadm trigger --type=devices --action=add + udevadm settle + fi +} + +get_uptime_seconds() { + # Get the current system uptime in seconds - ignore the two decimal places. + awk -F '.' '{print $1}' /proc/uptime +} + +setup_dynamic_partitions() { + command -v make-dynpart-mappings > /dev/null || return + attempt_start=$(get_uptime_seconds) + wait_seconds=10 + slot_number=0 + for super_partition in $1; do + # Wait for mdev + echo "Waiting for super partition $super_partition..." + while [ ! -b "$super_partition" ]; do + if [ "$(get_uptime_seconds)" -ge $(( attempt_start + wait_seconds )) ]; then + echo "ERROR: Super partition $super_partition failed to show up!" + return; + fi + sleep 0.1 + done + make-dynpart-mappings "$super_partition" "$slot_number" + slot_number=$(( slot_number + 1 )) + done +} + +mount_subpartitions() { + try_parts="/dev/disk/by-partlabel/userdata /dev/disk/by-partlabel/system* /dev/mapper/system*" + android_parts="" + for x in $try_parts; do + [ -e "$x" ] && android_parts="$android_parts $x" + done + + attempt_start=$(get_uptime_seconds) + wait_seconds=10 + echo "Trying to mount subpartitions for $wait_seconds seconds..." + while [ -z "$(find_boot_partition)" ]; do + partitions="$android_parts $(grep -v "loop\|ram" < /proc/diskstats |\ + sed 's/\(\s\+[0-9]\+\)\+\s\+//;s/ .*//;s/^/\/dev\//')" + for partition in $partitions; do + case "$(kpartx -l "$partition" 2>/dev/null | wc -l)" in + 2) + echo "Mount subpartitions of $partition" + kpartx -afs "$partition" + # Ensure that this was the *correct* subpartition + # Some devices have mmc partitions that appear to have + # subpartitions, but aren't our subpartition. + if [ -n "$(find_boot_partition)" ]; then + break + fi + kpartx -d "$partition" + continue + ;; + *) + continue + ;; + esac + done + if [ "$(get_uptime_seconds)" -ge $(( attempt_start + wait_seconds )) ]; then + echo "ERROR: failed to mount subpartitions!" + return; + fi + sleep 0.1; + done +} + +# Rewrite /dev/dm-X paths to /dev/mapper/... +pretty_dm_path() { + dm="$1" + n="${dm#/dev/dm-}" + + # If the substitution didn't do anything, then we're done + [ "$n" = "$dm" ] && echo "$dm" && return + + # Get the name of the device mapper device + name="/dev/mapper/$(cat "/sys/class/block/dm-${n}/dm/name")" + echo "$name" +} + +find_root_partition() { + [ -n "$PMOS_ROOT" ] && echo "$PMOS_ROOT" && return + + # The partition layout is one of the following: + # a) boot, root partitions on sdcard + # b) boot, root partition on the "system" partition (which has its + # own partition header! so we have partitions on partitions!) + # + # mount_subpartitions() must get executed before calling + # find_root_partition(), so partitions from b) also get found. + + # Short circuit all autodetection logic if pmos_root= or + # pmos_root_uuid= is supplied on the kernel cmdline + # shellcheck disable=SC2013 + if [ "$ROOT_PARTITION_UNLOCKED" = 0 ]; then + for x in $(cat /proc/cmdline); do + if ! [ "$x" = "${x#pmos_root_uuid=}" ]; then + path="$(blkid --uuid "${x#pmos_root_uuid=}")" + if [ -n "$path" ]; then + PMOS_ROOT="$path" + break + fi + fi + done + + if [ -z "$PMOS_ROOT" ]; then + for x in $(cat /proc/cmdline); do + if ! [ "$x" = "${x#pmos_root=}" ]; then + path="${x#pmos_root=}" + if [ -e "$path" ]; then + PMOS_ROOT="$path" + break + fi + fi + done + fi + + # On-device installer: before postmarketOS is installed, + # we want to use the installer partition as root. It is the + # partition behind pmos_root. pmos_root will either point to + # reserved space, or to an unfinished installation. + # p1: boot + # p2: (reserved space) <--- pmos_root + # p3: pmOS_install + # Details: https://postmarketos.org/on-device-installer + if [ -n "$PMOS_ROOT" ]; then + next="$(echo "$PMOS_ROOT" | sed 's/2$/3/')" + + # If the next partition is labeled pmOS_install (and + # not pmOS_deleteme), then postmarketOS is not + # installed yet. + if blkid | grep "$next" | grep -q pmOS_install; then + PMOS_ROOT="$next" + fi + fi + fi + + if [ -z "$PMOS_ROOT" ]; then + for id in pmOS_install pmOS_root; do + PMOS_ROOT="$(blkid --label "$id")" + [ -n "$PMOS_ROOT" ] && break + done + fi + + # Search for luks partition. + # Note: This should always be after the filesystem search, since this + # function may be called after the luks partition is unlocked and we don't + # want to keep returning the luks partition if a valid root filesystem + # exists + if [ -z "$PMOS_ROOT" ]; then + PMOS_ROOT="$(blkid | grep "crypto_LUKS" | cut -d ":" -f 1 | head -n 1)" + fi + + PMOS_ROOT=$(pretty_dm_path "$PMOS_ROOT") + echo "$PMOS_ROOT" +} + +find_boot_partition() { + [ -n "$PMOS_BOOT" ] && echo "$PMOS_BOOT" && return + + # First check for pmos_boot_uuid on the cmdline + # this should be set on all new installs. + # shellcheck disable=SC2013 + for x in $(cat /proc/cmdline); do + if ! [ "$x" = "${x#pmos_boot_uuid=}" ]; then + # Check if there is a partition with a matching UUID + path="$(blkid --uuid "${x#pmos_boot_uuid=}")" + if [ -n "$path" ]; then + PMOS_BOOT="$path" + break + else + # Don't fall back to anything if the given UUID wasn't + # found + return + fi + fi + done + + if [ -z "$PMOS_BOOT" ]; then + # shellcheck disable=SC2013 + for x in $(cat /proc/cmdline); do + if ! [ "$x" = "${x#pmos_boot=}" ]; then + # If the boot partition is specified explicitly + # then we need to check if it's a valid path, and + # fall back if not... + path="${x#pmos_boot=}" + if [ -e "$path" ]; then + PMOS_BOOT="$path" + break + else + # Don't fall back to anything if the given path doesn't + # exist + return + fi + fi + done + fi + + # Finally fall back to searching by label + if [ -z "$PMOS_BOOT" ]; then + # * "pmOS_i_boot" installer boot partition (fits 11 chars for fat32) + # * "pmOS_inst_boot" old installer boot partition (backwards compat) + # * "pmOS_boot" boot partition after installation + for p in pmOS_i_boot pmOS_inst_boot pmOS_boot; do + PMOS_BOOT="$(blkid --label "$p")" + [ -n "$PMOS_BOOT" ] && break + done + fi + + PMOS_BOOT=$(pretty_dm_path "$PMOS_BOOT") + echo "$PMOS_BOOT" +} + +get_partition_type() { + partition="$1" + blkid "$partition" | sed 's/^.*TYPE="\([a-zA-z0-9_]*\)".*$/\1/' +} + +# $1: path +# $2: set to "rw" for read-write +# Mount the boot partition. It gets mounted twice, first at /boot (ro), then at +# /sysroot/boot (rw), after root has been mounted at /sysroot, so we can +# switch_root to /sysroot and have the boot partition properly mounted. +mount_boot_partition() { + partition=$(find_boot_partition) + + if [ "$2" = "rw" ]; then + mount_opts="" + echo "Mount boot partition ($partition) to $1 (read-write)" + else + mount_opts="-o ro" + echo "Mount boot partition ($partition) to $1 (read-only)" + fi + + type="$(get_partition_type "$partition")" + case "$type" in + ext*) + echo "Detected ext filesystem" + modprobe ext4 + # ext2 might be handled by the ext2 or ext4 kernel module + # so let mount detect that automatically by omitting -t + ;; + vfat) + echo "Detected vfat filesystem" + modprobe vfat + mount_opts="-t vfat $mount_opts" + ;; + *) echo "WARNING: Detected unsupported '$type' filesystem ($partition)." ;; + esac + + # shellcheck disable=SC2086 + mount $mount_opts "$partition" "$1" +} + +# $1: initramfs-extra path +extract_initramfs_extra() { + initramfs_extra="$1" + if [ ! -e "$initramfs_extra" ]; then + echo "ERROR: initramfs-extra not found!" + show_splash "ERROR: initramfs-extra not found\\nhttps://postmarketos.org/troubleshooting" + fail_halt_boot + fi + echo "Extract $initramfs_extra" + # uncompressed: + # cpio -di < "$initramfs_extra" + gzip -d -c "$initramfs_extra" | cpio -iu +} + +wait_boot_partition() { + while [ -z "$(find_boot_partition)" ]; do + show_splash "ERROR: boot partition not found, retrying...\\nhttps://postmarketos.org/troubleshooting" + echo "Could not find the boot partition." + echo "If your install is on a removable disk, maybe you need to insert it?" + echo "Trying again..." + sleep 1 + done +} + +wait_root_partition() { + while [ -z "$(find_root_partition)" ]; do + show_splash "ERROR: root partition not found, retrying...\\nhttps://postmarketos.org/troubleshooting" + echo "Could not find the rootfs." + echo "If your install is on a removable disk, maybe you need to insert it?" + echo "Trying again..." + sleep 1 + done +} + +delete_old_install_partition() { + # The on-device installer leaves a "pmOS_deleteme" (p3) partition after + # successful installation, located after "pmOS_root" (p2). Delete it, + # so we can use the space. + partition="$(find_root_partition | sed 's/2$/3/')" + if ! blkid "$partition" | grep -q pmOS_deleteme; then + return + fi + + device="$(echo "$partition" | sed -E 's/p?3$//')" + echo "First boot after running on-device installer - deleting old" \ + "install partition: $partition" + parted -s "$device" rm 3 +} + +# $1: path to device +has_unallocated_space() { + # Check if there is unallocated space at the end of the device + parted -s "$1" print free | tail -n2 | \ + head -n1 | grep -qi "free space" +} + +resize_root_partition() { + partition=$(find_root_partition) + + # Do not resize the installer partition + if [ "$(blkid --label pmOS_install)" = "$partition" ]; then + echo "Resize root partition: skipped (on-device installer)" + return + fi + + # Only resize the partition if it's inside the device-mapper, which means + # that the partition is stored as a subpartition inside another one. + # In this case we want to resize it to use all the unused space of the + # external partition. + if [ -z "${partition##"/dev/mapper/"*}" ] || [ -z "${partition##"/dev/dm-"*}" ]; then + # Get physical device + partition_dev=$(dmsetup deps -o blkdevname "$partition" | \ + awk -F "[()]" '{print "/dev/"$2}') + if has_unallocated_space "$partition_dev"; then + echo "Resize root partition ($partition)" + # unmount subpartition, resize and remount it + kpartx -d "$partition" + parted -f -s "$partition_dev" resizepart 2 100% + kpartx -afs "$partition_dev" + ROOT_PARTITION_RESIZED=1 + else + echo "Not resizing root partition ($partition): no free space left" + fi + + # Resize the root partition (non-subpartitions). Usually we do not want + # this, except for QEMU devices and non-android devices (e.g. + # PinePhone). For them, it is fine to use the whole storage device and + # so we pass PMOS_FORCE_PARTITION_RESIZE as kernel parameter. + elif grep -q PMOS_FORCE_PARTITION_RESIZE /proc/cmdline; then + partition_dev="$(echo "$partition" | sed -E 's/p?2$//')" + if has_unallocated_space "$partition_dev"; then + echo "Resize root partition ($partition)" + parted -f -s "$partition_dev" resizepart 2 100% + partprobe + ROOT_PARTITION_RESIZED=1 + else + echo "Not resizing root partition ($partition): no free space left" + fi + + # Resize the root partition (non-subpartitions) on Chrome OS devices. + # Match $deviceinfo_cgpt_kpart not being empty instead of cmdline + # because it does not make sense here as all these devices use the same + # partitioning methods. This also resizes third partition instead of + # second, because these devices have an additional kernel partition + # at the start. + elif [ -n "$deviceinfo_cgpt_kpart" ]; then + partition_dev="$(echo "$partition" | sed -E 's/p?3$//')" + if has_unallocated_space "$partition_dev"; then + echo "Resize root partition ($partition)" + parted -f -s "$partition_dev" resizepart 3 100% + partprobe + ROOT_PARTITION_RESIZED=1 + else + echo "Not resizing root partition ($partition): no free space left" + fi + + else + echo "Unable to resize root partition: failed to find qualifying partition" + fi +} + +unlock_root_partition() { + command -v cryptsetup >/dev/null || return + partition="$(find_root_partition)" + if cryptsetup isLuks "$partition"; then + # Make sure the splash doesn't interfere + hide_splash + tried=0 + until cryptsetup status root | grep -qwi active; do + fde-unlock "$partition" "$tried" + tried=$((tried + 1)) + done + ROOT_PARTITION_UNLOCKED=1 + # Show again the loading splashscreen + show_splash "Loading..." + fi +} + +resize_root_filesystem() { + if [ "$ROOT_PARTITION_RESIZED" = 1 ]; then + show_splash "Resizing filesystem during initial boot..." + partition="$(find_root_partition)" + touch /etc/mtab # see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=673323 + type="$(get_partition_type "$partition")" + case "$type" in + ext4) + echo "Resize 'ext4' root filesystem ($partition)" + modprobe ext4 + resize2fs -f "$partition" + ;; + f2fs) + echo "Resize 'f2fs' root filesystem ($partition)" + modprobe f2fs + resize.f2fs "$partition" + ;; + btrfs) + echo "Resize 'btrfs' root filesystem ($partition)" + modprobe btrfs + resize_root_filesystem_tmp_btrfs="$(mktemp -d)" + mount -t btrfs "$partition" "$resize_root_filesystem_tmp_btrfs" + btrfs filesystem resize max "$resize_root_filesystem_tmp_btrfs" + umount "$resize_root_filesystem_tmp_btrfs" + unset resize_root_filesystem_tmp_btrfs + ;; + *) echo "WARNING: Can not resize '$type' filesystem ($partition)." ;; + esac + show_splash "Loading..." + fi +} + +mount_root_partition() { + partition="$(find_root_partition)" + rootfsopts="" + + # shellcheck disable=SC2013 + for x in $(cat /proc/cmdline); do + [ "$x" = "${x#pmos_rootfsopts=}" ] && continue + # Prepend a comma because this will be appended to "ro" below + rootfsopts=",${x#pmos_rootfsopts=}" + done + + echo "Mount root partition ($partition) to /sysroot (read-only) with options ${rootfsopts#,}" + type="$(get_partition_type "$partition")" + echo "Detected $type filesystem" + + if ! { [ "$type" = "ext4" ] || [ "$type" = "f2fs" ] || [ "$type" = "btrfs" ]; } then + echo "ERROR: Detected unsupported '$type' filesystem ($partition)." + show_splash "ERROR: unsupported '$type' filesystem ($partition)\\nhttps://postmarketos.org/troubleshooting" + fail_halt_boot + fi + + if ! modprobe "$type"; then + echo "INFO: unable to load module '$type' - maybe it's built in" + fi + if ! mount -t "$type" -o ro"$rootfsopts" "$partition" /sysroot; then + echo "ERROR: unable to mount root partition!" + show_splash "ERROR: unable to mount root partition\\nhttps://postmarketos.org/troubleshooting" + fail_halt_boot + fi + + if ! [ -e /sysroot/usr ]; then + echo "ERROR: root partition appeared to mount but does not contain a root filesystem!" + show_splash "ERROR: root partition does not contain a root filesystem\\nhttps://postmarketos.org/troubleshooting" + fail_halt_boot + fi +} + +# $1: path to the hooks dir +run_hooks() { + scriptsdir="$1" + + if ! [ -d "$scriptsdir" ]; then + return + fi + + for hook in "$scriptsdir"/*.sh; do + echo "Running initramfs hook: $hook" + sh "$hook" + done +} + +setup_usb_network_android() { + # Only run, when we have the android usb driver + SYS=/sys/class/android_usb/android0 + if ! [ -e "$SYS" ]; then + echo " /sys/class/android_usb does not exist, skipping android_usb" + return + fi + + echo " Setting up an USB gadget through android_usb" + + usb_idVendor="$(echo "${deviceinfo_usb_idVendor:-0x18D1}" | sed "s/0x//g")" # default: Google Inc. + usb_idProduct="$(echo "${deviceinfo_usb_idProduct:-0xD001}" | sed "s/0x//g")" # default: Nexus 4 (fastboot) + + # Do the setup + echo "0" >"$SYS/enable" + echo "$usb_idVendor" >"$SYS/idVendor" + echo "$usb_idProduct" >"$SYS/idProduct" + echo "rndis" >"$SYS/functions" + echo "1" >"$SYS/enable" +} + + +setup_usb_configfs_udc() { + # Check if there's an USB Device Controller + local _udc_dev="${deviceinfo_usb_network_udc:-}" + if [ -z "$_udc_dev" ]; then + _udc_dev=$(ls /sys/class/udc) + if [ -z "$_udc_dev" ]; then + echo " No USB Device Controller available" + return + fi + fi + + # Remove any existing UDC to avoid "write error: Resource busy" when setting UDC again + echo "" > /config/usb_gadget/g1/UDC || echo " Couldn't write to clear UDC" + # Link the gadget instance to an USB Device Controller. This activates the gadget. + # See also: https://gitlab.com/postmarketOS/pmbootstrap/issues/338 + echo "$_udc_dev" > /config/usb_gadget/g1/UDC || echo " Couldn't write new UDC" +} + +# $1: if set, skip writing to the UDC +setup_usb_network_configfs() { + # See: https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt + CONFIGFS=/config/usb_gadget + local skip_udc="$1" + + if ! [ -e "$CONFIGFS" ]; then + echo " /config/usb_gadget does not exist, skipping configfs usb gadget" + return + fi + + # Default values for USB-related deviceinfo variables + usb_idVendor="${deviceinfo_usb_idVendor:-0x18D1}" # default: Google Inc. + usb_idProduct="${deviceinfo_usb_idProduct:-0xD001}" # default: Nexus 4 (fastboot) + usb_serialnumber="${deviceinfo_usb_serialnumber:-postmarketOS}" + usb_network_function="${deviceinfo_usb_network_function:-ncm.usb0}" + usb_network_function_fallback="rndis.usb0" + + echo " Setting up an USB gadget through configfs" + # Create an usb gadet configuration + mkdir $CONFIGFS/g1 || echo " Couldn't create $CONFIGFS/g1" + echo "$usb_idVendor" > "$CONFIGFS/g1/idVendor" + echo "$usb_idProduct" > "$CONFIGFS/g1/idProduct" + + # Create english (0x409) strings + mkdir $CONFIGFS/g1/strings/0x409 || echo " Couldn't create $CONFIGFS/g1/strings/0x409" + + # shellcheck disable=SC2154 + echo "$deviceinfo_manufacturer" > "$CONFIGFS/g1/strings/0x409/manufacturer" + echo "$usb_serialnumber" > "$CONFIGFS/g1/strings/0x409/serialnumber" + # shellcheck disable=SC2154 + echo "$deviceinfo_name" > "$CONFIGFS/g1/strings/0x409/product" + + # Create network function. + if ! mkdir $CONFIGFS/g1/functions/"$usb_network_function"; then + echo " Couldn't create $CONFIGFS/g1/functions/$usb_network_function" + # Try the fallback function next + if mkdir $CONFIGFS/g1/functions/"$usb_network_function_fallback"; then + usb_network_function="$usb_network_function_fallback" + else + echo " Couldn't create $CONFIGFS/g1/functions/$usb_network_function_fallback" + fi + fi + + # Create configuration instance for the gadget + mkdir $CONFIGFS/g1/configs/c.1 \ + || echo " Couldn't create $CONFIGFS/g1/configs/c.1" + mkdir $CONFIGFS/g1/configs/c.1/strings/0x409 \ + || echo " Couldn't create $CONFIGFS/g1/configs/c.1/strings/0x409" + echo "USB network" > $CONFIGFS/g1/configs/c.1/strings/0x409/configuration \ + || echo " Couldn't write configration name" + + # Link the network instance to the configuration + ln -s $CONFIGFS/g1/functions/"$usb_network_function" $CONFIGFS/g1/configs/c.1 \ + || echo " Couldn't symlink $usb_network_function" + + # If an argument was supplied then skip writing to the UDC (only used for mass storage + # log recovery) + if [ -z "$skip_udc" ]; then + setup_usb_configfs_udc + fi +} + +setup_usb_network() { + # Only run once + _marker="/tmp/_setup_usb_network" + [ -e "$_marker" ] && return + touch "$_marker" + echo "Setup usb network" + # Run all usb network setup functions (add more below!) + setup_usb_network_android + setup_usb_network_configfs +} + +start_unudhcpd() { + # Only run once + [ "$(pidof unudhcpd)" ] && return + + # Skip if disabled + # shellcheck disable=SC2154 + if [ "$deviceinfo_disable_dhcpd" = "true" ]; then + echo "NOTE: start of dhcpd is disabled (deviceinfo_disable_dhcpd)" + return + fi + + local host_ip="${unudhcpd_host_ip:-172.16.42.1}" + local client_ip="${unudhcpd_client_ip:-172.16.42.2}" + echo "Starting unudhcpd with server ip $host_ip, client ip: $client_ip" + + # Get usb interface + usb_network_function="${deviceinfo_usb_network_function:-ncm.usb0}" + usb_network_function_fallback="rndis.usb0" + if [ -n "$(cat /config/usb_gadget/g1/UDC)" ]; then + INTERFACE="$( + cat "/config/usb_gadget/g1/functions/$usb_network_function/ifname" 2>/dev/null || + cat "/config/usb_gadget/g1/functions/$usb_network_function_fallback/ifname" 2>/dev/null || + echo '' + )" + else + INTERFACE="" + fi + if [ -n "$INTERFACE" ]; then + ifconfig "$INTERFACE" "$host_ip" + elif ifconfig rndis0 "$host_ip" 2>/dev/null; then + INTERFACE=rndis0 + elif ifconfig usb0 "$host_ip" 2>/dev/null; then + INTERFACE=usb0 + elif ifconfig eth0 "$host_ip" 2>/dev/null; then + INTERFACE=eth0 + fi + + if [ -z "$INTERFACE" ]; then + echo " Could not find an interface to run a dhcp server on" + echo " Interfaces:" + ip link + return + fi + + echo " Using interface $INTERFACE" + echo " Starting the DHCP daemon" + ( + unudhcpd -i "$INTERFACE" -s "$host_ip" -c "$client_ip" + ) & +} + +# $1: Message to show +show_splash() { + # Skip for non-framebuffer devices + # shellcheck disable=SC2154 + if [ "$deviceinfo_no_framebuffer" = "true" ]; then + echo "NOTE: Skipping framebuffer splashscreen (deviceinfo_no_framebuffer)" + return + fi + + # Disable splash + if grep -q PMOS_NOSPLASH /proc/cmdline; then + return + fi + + hide_splash + + # shellcheck disable=SC2154,SC2059 + /usr/bin/pbsplash -s /usr/share/pbsplash/pmos-logo-text.svg \ + -b "$VERSION | Linux $(uname -r) | $deviceinfo_codename" \ + -m "$(printf "$1")" >/dev/null & +} + +hide_splash() { + killall pbsplash 2>/dev/null + + while pgrep pbsplash >/dev/null; do + sleep 0.01 + done +} + +set_framebuffer_mode() { + [ -e "/sys/class/graphics/fb0/modes" ] || return + [ -z "$(cat /sys/class/graphics/fb0/mode)" ] || return + + _mode="$(cat /sys/class/graphics/fb0/modes)" + echo "Setting framebuffer mode to: $_mode" + echo "$_mode" > /sys/class/graphics/fb0/mode +} + +setup_framebuffer() { + # Skip for non-framebuffer devices + # shellcheck disable=SC2154 + if [ "$deviceinfo_no_framebuffer" = "true" ]; then + echo "NOTE: Skipping framebuffer setup (deviceinfo_no_framebuffer)" + return + fi + + # Wait for /dev/fb0 + for _ in $(seq 1 100); do + [ -e "/dev/fb0" ] && break + sleep 0.1 + done + if ! [ -e "/dev/fb0" ]; then + echo "ERROR: /dev/fb0 did not appear after waiting 10 seconds!" + echo "If your device does not have a framebuffer, disable this with:" + echo "no_framebuffer=true in " + return + fi + + set_framebuffer_mode +} + +setup_bootchart2() { + if grep -q PMOS_BOOTCHART2 /proc/cmdline; then + if [ -f "/sysroot/sbin/bootchartd" ]; then + # shellcheck disable=SC2034 + init="/sbin/bootchartd" + echo "remounting /sysroot as rw for /sbin/bootchartd" + mount -o remount, rw /sysroot + + # /dev/null may not exist at the first boot after + # the root filesystem has been created. + [ -c /sysroot/dev/null ] && return + echo "creating /sysroot/dev/null for /sbin/bootchartd" + mknod -m 666 "/sysroot/dev/null" c 1 3 + else + echo "WARNING: bootchart2 is not installed." + fi + fi +} + +mkhash() { + sha256sum "$1" | cut -d " " -f 1 +} + +# Create a small disk image and copy logs to it so they can be exposed via mass storage +create_logs_disk() { + local loop_dev="$1" + local upload_file="" + echo "Creating logs disk" + + fallocate -l 32M /tmp/logs.img + # The log device used is assumed to be $loop_dev + losetup -f /tmp/logs.img + mkfs.vfat -n "PMOS_LOGS" "$loop_dev" + mkdir -p /tmp/logs + mount "$loop_dev" /tmp/logs + + # Copy logs + cp /pmOS_init.log /tmp/logs/pmOS_init.txt + dmesg > /tmp/logs/dmesg.txt + blkid > /tmp/logs/blkid.txt + cat /proc/cmdline > /tmp/logs/cmdline.txt + cat /proc/partitions > /tmp/logs/partitions.txt + # Include FDT if it exists + [ -e /sys/firmware/fdt ] && cp /sys/firmware/fdt /tmp/logs/fdt.dtb + + # Additional info about the initramfs + { + echo "initramfs-version: $INITRAMFS_PKG_VERSION" + # Take hashes of the initramfs files so we can be sure they weren't modified inadvertantly + echo "init-hash: $(mkhash /init)" + echo "init-functions-hash: $(mkhash /init_functions.sh)" + } >> /tmp/logs/_info + + # Create a tar file with all the logs. We don't include the date because on many devices + # (especially Qualcomm) the RTC is likely wrong. + upload_file="${deviceinfo_codename}-${VERSION}-$(uname -r).tar.gz" + # Done in a subshell to not change the working directory of init + (cd /tmp/logs || ( echo "Couldn't cd to /tmp/logs"; return ); tar -cv ./* | gzip -6 -c > "/tmp/$upload_file") + mv "/tmp/$upload_file" /tmp/logs/ + + # Create a README with instructions on how to report an issue + cat > /tmp/logs/README.txt <<-EOF + Something went wrong and your device did not boot properly. If this was unexpected + then please open a new issue by visiting + + https://gitlab.com/postmarketOS/pmaports/-/issues/new + + and attach the following file by dragging it onto the page: + + * $upload_file + + You are running postmarketOS $VERSION on kernel $(uname -r). + EOF + + # Unmount + umount /tmp/logs +} + +# Make logs available via mass storage gadget +export_logs() { + local loop_dev="" + usb_mass_storage_function="mass_storage.0" + active_udc="$(cat /config/usb_gadget/g1/UDC)" + + loop_dev="$(losetup -f)" + + create_logs_disk "$loop_dev" + + echo "Making logs available via mass storage" + + # Set up network gadget if not already done + if [ -z "$active_udc" ]; then + setup_usb_network_configfs "skip_udc" + else + # Unset UDC + echo "" > /config/usb_gadget/g1/UDC + fi + + mkdir "$CONFIGFS"/g1/functions/"$usb_mass_storage_function" || return + + echo "$loop_dev" > "$CONFIGFS"/g1/functions/"$usb_mass_storage_function"/lun.0/file + + ln -s "$CONFIGFS"/g1/functions/"$usb_mass_storage_function" \ + "$CONFIGFS"/g1/configs/c.1 || return + + setup_usb_configfs_udc +} + +fail_halt_boot() { + export_logs + echo "Looping forever" + while true; do + sleep 1 + done +} diff --git a/main/postmarketos-initramfs-minimal/mdev.conf b/main/postmarketos-initramfs-minimal/mdev.conf new file mode 100644 index 000000000..4be94f2fa --- /dev/null +++ b/main/postmarketos-initramfs-minimal/mdev.conf @@ -0,0 +1,33 @@ +# +# This is an mdev config for the postmarketOS initramfs +# + +# Devices: +# Syntax: %s %d:%d %s +# devices user:group mode + +$MODALIAS=.* 0:0 0660 @modprobe -q -b "$MODALIAS" + +# null does already exist; therefore ownership has to be changed with command +null 0:0 0666 @chmod 666 $MDEV +zero 0:0 0666 +full 0:0 0666 + +random 0:0 0666 +urandom 0:0 0444 +hwrandom 0:0 0660 + +console 0:0 0600 + +kmem 0:0 0640 +mem 0:0 0640 + +# Set up /dev/disk/by-* symlinks +dasd.* 0:0 0660 */lib/mdev/persistent-storage +mmcblk.* 0:0 0660 */lib/mdev/persistent-storage +nbd.* 0:0 0660 */lib/mdev/persistent-storage +nvme.* 0:0 0660 */lib/mdev/persistent-storage +sd[a-z].* 0:0 0660 */lib/mdev/persistent-storage +sr[0-9]+ 0:0 0660 */lib/mdev/persistent-storage +vd[a-z].* 0:0 0660 */lib/mdev/persistent-storage +xvd[a-z].* 0:0 0660 */lib/mdev/persistent-storage diff --git a/main/postmarketos-initramfs-minimal/unudhcpd.conf b/main/postmarketos-initramfs-minimal/unudhcpd.conf new file mode 100644 index 000000000..9fab1a84a --- /dev/null +++ b/main/postmarketos-initramfs-minimal/unudhcpd.conf @@ -0,0 +1,6 @@ +# This configuration file is used by the initramfs to run unudhcpd. +# It is *not* used directly by unudhcpd! +# Note: The initramfs must be recreated by running the 'mkinitfs' command +# for changes here to be applied/used during boot. +unudhcpd_host_ip=172.16.42.1 +unudhcpd_client_ip=172.16.42.2 diff --git a/main/postmarketos-initramfs/APKBUILD b/main/postmarketos-initramfs/APKBUILD index bcc8c94c3..83c7487c5 100644 --- a/main/postmarketos-initramfs/APKBUILD +++ b/main/postmarketos-initramfs/APKBUILD @@ -6,6 +6,8 @@ pkgrel=0 pkgdesc="Base files for the postmarketOS initramfs / initramfs-extra" url="https://postmarketos.org" options="!check" # no tests +provides="postmarketos-ramdisk" +provider_priority=10 depends=" blkid btrfs-progs