From fbe968f1abe151f7380fa393055c9d2db5a47c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Sz=C3=B6ll=C5=91si?= Date: Thu, 24 Aug 2017 23:07:36 +0200 Subject: [PATCH] Recovery installer zip (#404) Supports flashing with TWRP and other Android recovery OS through adb sideload, as well as exporting a generated recovery zip file. See also: https://github.com/postmarketOS/pmbootstrap/wiki/deviceinfo_flash_methods#recovery-mode-adb --- .../APKBUILD | 33 +++++ .../build_zip.sh | 52 ++++++++ .../pmos_install | 86 +++++++++++++ .../pmos_install_functions | 98 +++++++++++++++ .../pmos_setpw | 29 +++++ .../update-binary | 31 +++++ aports/main/postmarketos-mkinitfs/APKBUILD | 6 +- .../postmarketos-mkinitfs/init_functions.sh | 2 +- pmb/chroot/shutdown.py | 15 +++ pmb/config/__init__.py | 12 ++ pmb/flasher/export.py | 7 +- pmb/flasher/frontend.py | 21 ++++ pmb/flasher/run.py | 5 +- pmb/install/install.py | 118 +++++++++++------- pmb/install/recovery.py | 61 +++++++++ pmb/parse/arguments.py | 25 +++- test/static_code_analysis.sh | 5 + 17 files changed, 552 insertions(+), 54 deletions(-) create mode 100644 aports/main/postmarketos-android-recovery-installer/APKBUILD create mode 100644 aports/main/postmarketos-android-recovery-installer/build_zip.sh create mode 100755 aports/main/postmarketos-android-recovery-installer/pmos_install create mode 100755 aports/main/postmarketos-android-recovery-installer/pmos_install_functions create mode 100755 aports/main/postmarketos-android-recovery-installer/pmos_setpw create mode 100644 aports/main/postmarketos-android-recovery-installer/update-binary create mode 100644 pmb/install/recovery.py diff --git a/aports/main/postmarketos-android-recovery-installer/APKBUILD b/aports/main/postmarketos-android-recovery-installer/APKBUILD new file mode 100644 index 00000000..cd07925b --- /dev/null +++ b/aports/main/postmarketos-android-recovery-installer/APKBUILD @@ -0,0 +1,33 @@ +pkgname=postmarketos-android-recovery-installer +pkgver=0.0.1 +pkgrel=1 +pkgdesc="TWRP compatible postmarketOS installer script" +url="https://github.com/postmarketOS" +# multipath-tools: kpartx +depends="busybox-extras lddtree cryptsetup multipath-tools device-mapper parted zip" +source="build_zip.sh + update-binary + pmos_install + pmos_install_functions + pmos_setpw" +arch="noarch" +license="GPL3" + +package() { + install -Dm755 "$srcdir/build_zip.sh" \ + "$pkgdir/sbin/build-recovery-zip" + mkdir -p "$pkgdir/var/lib/postmarketos-android-recovery-installer/META-INF/com/google/android/" + install -Dm644 "$srcdir"/update-binary \ + "$pkgdir/var/lib/postmarketos-android-recovery-installer/META-INF/com/google/android/update-binary" + mkdir "$pkgdir/var/lib/postmarketos-android-recovery-installer/bin/" + for file in pmos_install pmos_install_functions pmos_setpw; do + install -Dm755 "$srcdir/$file" \ + "$pkgdir/var/lib/postmarketos-android-recovery-installer/bin/$file" + done + mkdir "$pkgdir/var/lib/postmarketos-android-recovery-installer/lib/" +} +sha512sums="9c7a90965aeb7f19ac166282066063510eeba6691ae695b2821e1a9e050463378c56492a27b3bfd4c8155380e6f24adc558dd0c98fc308ee7335b00c7b12fc0b build_zip.sh +874d7505f9940d98a67fd8e5881ab0b93ae9fd0c46d7f4004468a2e9bbe4853f4bf6db64380c27684a66ebbd45ebe3399219b3910799de24003b8399ab2a4497 update-binary +5647a089c95d291d5662bbe6931a01f8591823d63b0226832897a046f351121c2c5d6ebfc83dcf9762ac50774920393fea37c05a92f2079e9688d6fe58711e49 pmos_install +dba3da4d2c18a480fda3bda233052f946bfd5a5f4fe05115341d4dc1466519584930e116719c5338ef4309a51dfea7d2e531ed133723f59c8d6d0c5a4f73a26b pmos_install_functions +1969a467bc1e0f04ed445dd78db4eb1ad77b769a6e04c35211ad2c45cb8293243f864e499cdecf6016292d1accb26e6f62073b2afab023a20a79e0ea3dc15bd9 pmos_setpw" diff --git a/aports/main/postmarketos-android-recovery-installer/build_zip.sh b/aports/main/postmarketos-android-recovery-installer/build_zip.sh new file mode 100644 index 00000000..59d34285 --- /dev/null +++ b/aports/main/postmarketos-android-recovery-installer/build_zip.sh @@ -0,0 +1,52 @@ +#!/bin/ash + +# Copyright 2017 Attila Szöllősi +# +# This file is part of postmarketos-android-recovery-installer. +# +# postmarketos-android-recovery-installer is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# postmarketos-android-recovery-installer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with postmarketos-android-recovery-installer. If not, see . + +set -e + +# Copy files to the destination specified +# $1: files +# $2: destination +copy_files() +{ + for file in $1; do + filename=$(basename "$file") + install -Dm755 "$file" "$2"/"$filename" + done +} + +check_whether_exists() +{ + if [ ! -e "$1" ] + then + echo "$1 not found" + return 1 + fi +} + +# shellcheck disable=SC1091 +. ./install_options + +BINARIES="/sbin/cryptsetup /sbin/kpartx /usr/sbin/parted /usr/sbin/partprobe" +# shellcheck disable=SC2086 +LIBRARIES=$(lddtree -l $BINARIES | awk '/lib/ {print}' | sort -u) +copy_files "$BINARIES" bin/ +copy_files "$LIBRARIES" lib/ +check_whether_exists rootfs.tar.gz +[ "$FLASH_BOOT" = "true" ] && check_whether_exists boot.img +zip -0 -r "pmos-$DEVICE.zip" . diff --git a/aports/main/postmarketos-android-recovery-installer/pmos_install b/aports/main/postmarketos-android-recovery-installer/pmos_install new file mode 100755 index 00000000..20c06214 --- /dev/null +++ b/aports/main/postmarketos-android-recovery-installer/pmos_install @@ -0,0 +1,86 @@ +#!/sbin/ash + +# Copyright 2017 Attila Szöllősi +# +# This file is part of postmarketos-android-recovery-installer. +# +# postmarketos-android-recovery-installer is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# postmarketos-android-recovery-installer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with postmarketos-android-recovery-installer. If not, see . + +# shellcheck source=pmos_install_functions +. /tmp/postmarketos/bin/pmos_install_functions "$1" "$2" +# shellcheck source=/dev/null +. "$WORKING_DIR"/install_options + +exec > "$WORKING_DIR"/pmos.log 2>&1 +set -ex + +ui_print "postmarketOS recovery installer" + +ui_print "Extracting partition info from fstab..." +extract_partition_table +ui_print "Entering working directory..." +cd "$WORKING_DIR" +ui_print "Extracting files..." +unzip -o "$ZIP" +mkdir /lib +ui_print "Symlinking .so files to /lib/..." +ln -s "$WORKING_DIR"/lib/* /lib/ +ui_print "Unmounting /$INSTALL_PARTITION..." +umount_install_partition +ui_print "Creating partition table on $INSTALL_DEVICE..." +# parted returns nonzero even when command executed successfully +partition_install_device || : +if [ "$FDE" = "true" ] +then + ui_print "Generating temporary keyfile with random data..." + dd bs=512 count=4 if=/dev/urandom of="$WORKING_DIR"/lukskey + ui_print "Initializing LUKS device..." + cryptsetup luksFormat --use-urandom -c "$CIPHER" -q "$ROOT_PARTITION" "$WORKING_DIR"/lukskey + ui_print "Opening LUKS partition..." + cryptsetup luksOpen -d "$WORKING_DIR"/lukskey "$ROOT_PARTITION" pm_crypt + ui_print "Formatting LUKS partition..." + make_ext4fs -L 'pmOS_root' /dev/mapper/pm_crypt + ui_print "Mounting LUKS partition..." + mount -t ext4 -rw /dev/mapper/pm_crypt /"$INSTALL_PARTITION" +else + ui_print "Formatting root partition..." + make_ext4fs -L 'pmOS_root' "$ROOT_PARTITION" + ui_print "Mounting root partition..." + mount -t ext4 -rw "$ROOT_PARTITION" /"$INSTALL_PARTITION" +fi +ui_print "Formatting pmOS_boot..." +mkfs.ext2 -q -L 'pmOS_boot' "$PMOS_BOOT" +ui_print "Mounting pmOS_boot..." +mkdir /"$INSTALL_PARTITION"/boot +mount -t ext2 -rw "$PMOS_BOOT" /"$INSTALL_PARTITION"/boot || { + ui_print "Failed to format/mount ext2 partition." + ui_print "Trying ext4..." + make_ext4fs -L 'pmOS_boot' "$PMOS_BOOT" + mount -t ext4 -rw "$PMOS_BOOT" /"$INSTALL_PARTITION"/boot +} +ui_print "Installing rootfs..." +tar -xf rootfs.tar.gz -C /"$INSTALL_PARTITION" +if [ "$FLASH_BOOTIMG" = "true" ] +then + ui_print "Flashing boot.img..." + dd if=boot.img of="$BOOT" +fi +if [ "$FDE" = "true" ] +then + ui_print "Creating a symlink for password setting script in /sbin/..." + ln -s "$WORKING_DIR"/bin/pmos_setpw /sbin/ + ui_print "Do not forget to add a password to the LUKS partition!" + ui_print "Run the command: pmos_setpw from the terminal/adb shell!" +fi +ui_print "Installation done." diff --git a/aports/main/postmarketos-android-recovery-installer/pmos_install_functions b/aports/main/postmarketos-android-recovery-installer/pmos_install_functions new file mode 100755 index 00000000..688046ba --- /dev/null +++ b/aports/main/postmarketos-android-recovery-installer/pmos_install_functions @@ -0,0 +1,98 @@ +#!/sbin/ash + +# Copyright 2017 Attila Szöllősi +# +# This file is part of postmarketos-android-recovery-installer. +# +# postmarketos-android-recovery-installer is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# postmarketos-android-recovery-installer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with postmarketos-android-recovery-installer. If not, see . + +export OUTFD=$1 +export ZIP=$2 +export WORKING_DIR="/tmp/postmarketos" +export PATH=$PATH:"$WORKING_DIR"/bin + +# shellcheck source=/dev/null +. "$WORKING_DIR"/install_options + +# taken from https://github.com/Debuffer-XDA/Gov-Tuner/blob/master/META-INF/com/google/android/update-binary +# Copyright (c) 2016 - 2017 Debuffer +ui_print() { + echo -n -e "ui_print $1\n" > /proc/self/fd/"$OUTFD" + echo -n -e "ui_print\n" > /proc/self/fd/"$OUTFD" +} + +extract_partition_table() { + case "$INSTALL_PARTITION" in + "system") + # We need to resolve symlinks, to make set_subpartitions() work. + _INSTALL_DEVICE=$(readlink -fn "$(awk '/^\/system/ {print $3}' /etc/recovery.fstab)") + ;; + "external_sd") + _INSTALL_DEVICE=$(readlink -fn "$(awk '/^\/external_sd/ {print $4}' /etc/recovery.fstab)") + ;; + *) + echo "No support for flashing $INSTALL_PARTITION." + return 1 + ;; + esac + if [ ! -z "$_INSTALL_DEVICE" ] + then + echo "install device found at $_INSTALL_DEVICE" + export INSTALL_DEVICE=$_INSTALL_DEVICE + else + echo "Couldn't find /$INSTALL_PARTITION/ in fstab." + return 1 + fi + _BOOT=$(awk '/^\/boot/ {print $3}' /etc/recovery.fstab) + if [ ! -z "$_BOOT" ] + then + echo "boot partition found at $_BOOT" + export BOOT=$_BOOT + else + echo "Couldn't find /boot/ in fstab." + return 1 + fi +} + +partition_install_device() { + for command in "mktable msdos" \ + "mkpart primary ext2 2048s 100M" \ + "mkpart primary 100M 100%" \ + "set 1 boot on" + do + parted -s "$INSTALL_DEVICE" "$command" + done + partprobe + if [ "$INSTALL_PARTITION" = "system" ] + then + kpartx -afs "$INSTALL_DEVICE" + ln -s /dev/mapper/* /dev/block/ + fi + set_subpartitions +} + +set_subpartitions() { + export PMOS_BOOT="$INSTALL_DEVICE"p1 + export ROOT_PARTITION="$INSTALL_DEVICE"p2 +} + +umount_install_partition() { + if mountpoint -q "/$INSTALL_PARTITION/" + then + umount /"$INSTALL_PARTITION"/ + else + echo 'Continuing...' + return 0 + fi +} diff --git a/aports/main/postmarketos-android-recovery-installer/pmos_setpw b/aports/main/postmarketos-android-recovery-installer/pmos_setpw new file mode 100755 index 00000000..7da6ff21 --- /dev/null +++ b/aports/main/postmarketos-android-recovery-installer/pmos_setpw @@ -0,0 +1,29 @@ +#!/sbin/ash -e + +# Copyright 2017 Attila Szöllősi +# +# This file is part of postmarketos-android-recovery-installer. +# +# postmarketos-android-recovery-installer is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# postmarketos-android-recovery-installer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with postmarketos-android-recovery-installer. If not, see . + +# shellcheck source=pmos_install_functions +. /tmp/postmarketos/bin/pmos_install_functions + +extract_partition_table +set_subpartitions +echo "Set the password of the encrypted rootfs!" +cryptsetup luksAddKey -d "$WORKING_DIR"/lukskey "$ROOT_PARTITION" +# Remove temporary keyfile +cryptsetup luksRemoveKey "$ROOT_PARTITION" "$WORKING_DIR"/lukskey +echo "Successfully added key to the LUKS device." diff --git a/aports/main/postmarketos-android-recovery-installer/update-binary b/aports/main/postmarketos-android-recovery-installer/update-binary new file mode 100644 index 00000000..483ca7d3 --- /dev/null +++ b/aports/main/postmarketos-android-recovery-installer/update-binary @@ -0,0 +1,31 @@ +#!/sbin/ash + +# Copyright 2017 Attila Szöllősi +# +# This file is part of postmarketos-android-recovery-installer. +# +# postmarketos-android-recovery-installer is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# postmarketos-android-recovery-installer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with postmarketos-android-recovery-installer. If not, see . + +# Print fail information +OUTFD=$2 +fail_info() { + FAIL_MSG="Failed. Check /tmp/postmarketos/pmos.log for more info!" + echo -n -e "ui_print $FAIL_MSG\n" > /proc/self/fd/"$OUTFD" + echo -n -e "ui_print\n" > /proc/self/fd/"$OUTFD" +} +# Create working directory +mkdir /tmp/postmarketos/ +# Extract and start the installer script +unzip "$3" "bin/pmos_install" "bin/pmos_install_functions" "install_options" -d /tmp/postmarketos/ +/tmp/postmarketos/bin/pmos_install "$2" "$3" || { fail_info ; exit 1 ; } diff --git a/aports/main/postmarketos-mkinitfs/APKBUILD b/aports/main/postmarketos-mkinitfs/APKBUILD index 7bf9a3f5..d442acb3 100644 --- a/aports/main/postmarketos-mkinitfs/APKBUILD +++ b/aports/main/postmarketos-mkinitfs/APKBUILD @@ -1,6 +1,6 @@ pkgname=postmarketos-mkinitfs -pkgver=0.3.3 -pkgrel=6 +pkgver=0.3.4 +pkgrel=0 pkgdesc="Tool to generate initramfs images for postmarketOS" url="https://github.com/postmarketOS" # multipath-tools: kpartx @@ -26,5 +26,5 @@ package() { mkdir -p "$pkgdir/etc/postmarketos-mkinitfs/hooks/" } sha512sums="3d0215d61a34e846c6c3e4ff1911742a620cd1c6ff1de3cf26eaa4cb7643467da72bf9abc6a53992cc750bb76340be820149b25b806152b70fc0d40e0f8aa310 init.sh.in -2331fe9a89ba58348b41fbfdeb6f4daeff3f6ef161d1b7582c3e900baba377fa9411efa0b052ea5c2ae22f75bc48f6b8f38dafad0bd836a0319906e70482898c init_functions.sh +a47398cdbb5e68a34086038cf6d72df91f6e58dcae7ff1ea8a375cd44f21e4573a944122ca5a32dda7b002bb14ec5826435edd2512c3db198dc9c0c3756e3cbe init_functions.sh ef1481ef45e786486fb8e9939f756afb1d873a92546468d3dda3065ef46404be7e9847ab1f630fa6cf3e4ab99bdb116401093bbb1bbc882ea85ea824cdf7e389 mkinitfs.sh" diff --git a/aports/main/postmarketos-mkinitfs/init_functions.sh b/aports/main/postmarketos-mkinitfs/init_functions.sh index b31980b0..cf5c2a83 100644 --- a/aports/main/postmarketos-mkinitfs/init_functions.sh +++ b/aports/main/postmarketos-mkinitfs/init_functions.sh @@ -105,7 +105,7 @@ mount_boot_partition() { loop_forever fi echo "Mount boot partition ($partition)" - mount -r -t ext2 "$partition" /boot + mount -r "$partition" /boot } # $1: initramfs-extra path diff --git a/pmb/chroot/shutdown.py b/pmb/chroot/shutdown.py index f3db2a5f..194edc37 100644 --- a/pmb/chroot/shutdown.py +++ b/pmb/chroot/shutdown.py @@ -19,6 +19,8 @@ along with pmbootstrap. If not, see . import logging import glob import os +import socket +from contextlib import closing import pmb.chroot import pmb.chroot.distccd @@ -27,6 +29,16 @@ import pmb.install.losetup import pmb.parse.arch +def kill_adb(args): + """ + Kill adb daemon if it's running. + """ + port = 5038 + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + if sock.connect_ex(("127.0.0.1", port)) == 0: + pmb.chroot.root(args, ["adb", "-P", str(port), "kill-server"]) + + def shutdown_cryptsetup_device(args, name): """ :param name: cryptsetup device name, usually "pm_crypt" in pmbootstrap @@ -55,6 +67,9 @@ def shutdown_cryptsetup_device(args, name): def shutdown(args, only_install_related=False): pmb.chroot.distccd.stop(args) + # Stop adb server + kill_adb(args) + # Umount installation-related paths (order is important!) pmb.helpers.mount.umount_all(args, args.work + "/chroot_native/mnt/install") diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index d0daa0c7..9e8e1033 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -230,6 +230,7 @@ install_device_packages = [ # chroot, before the flash programs get started. flash_mount_bind = [ "/sys/bus/usb/devices/", + "/sys/dev/", "/sys/devices/", "/dev/bus/usb/" ] @@ -289,6 +290,17 @@ flashers = { ["heimdall", "flash", "--$PARTITION_KERNEL", "$BOOT/boot.img-$FLAVOR"]], }, }, + "adb": { + "depends": ["android-tools"], + "actions": + { + "list_devices": [["adb", "-P", "5038", "devices"]], + "sideload": [["echo", "< wait for any device >"], + ["adb", "-P", "5038", "wait-for-usb-sideload"], + ["adb", "-P", "5038", "sideload", + "$RECOVERY_ZIP"]], + } + }, } # diff --git a/pmb/flasher/export.py b/pmb/flasher/export.py index 18718bf1..1766db7d 100644 --- a/pmb/flasher/export.py +++ b/pmb/flasher/export.py @@ -48,13 +48,18 @@ def symlinks(args, flavor, folder): "uImage-" + flavor: "Kernel, legacy u-boot image format", "vmlinuz-" + flavor: "Linux kernel", args.device + ".img": "System partition", + "pmos-" + args.device + ".zip": "Android recovery flashable zip", } # Generate a list of patterns path_native = args.work + "/chroot_native" path_boot = args.work + "/chroot_rootfs_" + args.device + "/boot" + path_buildroot = args.work + "/chroot_buildroot_" + args.deviceinfo["arch"] patterns = [path_boot + "/*-" + flavor, - path_native + "/home/user/rootfs/" + args.device + ".img"] + path_native + "/home/user/rootfs/" + args.device + ".img", + path_buildroot + + "/var/lib/postmarketos-android-recovery-installer/pmos-" + + args.device + ".zip"] # Generate a list of files from the patterns files = [] diff --git a/pmb/flasher/frontend.py b/pmb/flasher/frontend.py index 9256f7ef..0b3c3541 100644 --- a/pmb/flasher/frontend.py +++ b/pmb/flasher/frontend.py @@ -95,6 +95,25 @@ def list_devices(args): pmb.flasher.run(args, "list_devices") +def sideload(args): + # Mount the buildroot + suffix = "buildroot_" + args.deviceinfo["arch"] + mountpoint = "/mnt/" + suffix + pmb.helpers.mount.bind(args, args.work + "/chroot_" + suffix, + args.work + "/chroot_native/" + mountpoint) + + # Missing recovery zip error + zip_path = ("/var/lib/postmarketos-android-recovery-installer/pmos-" + + args.device + ".zip") + if not os.path.exists(args.work + "/chroot_native" + mountpoint + + zip_path): + raise RuntimeError("The recovery zip has not been generated yet," + " please run 'pmbootstrap install' with the" + " '--android-recovery-zip' parameter first!") + + pmb.flasher.run(args, "sideload") + + def export(args): # Create the export folder if not os.path.exists(args.export_folder): @@ -123,5 +142,7 @@ def frontend(args): list_flavors(args) if action == "list_devices": list_devices(args) + if action == "sideload": + sideload(args) if action == "export": export(args) diff --git a/pmb/flasher/run.py b/pmb/flasher/run.py index 8dacb7ef..f6509f29 100644 --- a/pmb/flasher/run.py +++ b/pmb/flasher/run.py @@ -24,7 +24,7 @@ def run(args, action, flavor=None): pmb.flasher.init(args) # Verify action - method = args.deviceinfo["flash_methods"] + method = args.flash_method or args.deviceinfo["flash_methods"] cfg = pmb.config.flashers[method] if action not in cfg["actions"]: raise RuntimeError("action " + action + " is not" @@ -42,6 +42,9 @@ def run(args, action, flavor=None): "$KERNEL_CMDLINE": _cmdline, "$PARTITION_INITFS": args.deviceinfo["flash_heimdall_partition_initfs"], "$PARTITION_KERNEL": args.deviceinfo["flash_heimdall_partition_kernel"], + "$RECOVERY_ZIP": "/mnt/buildroot_" + args.deviceinfo["arch"] + + "/var/lib/postmarketos-android-recovery-installer" + "/pmos-" + args.device + ".zip", } # Run the commands of each action diff --git a/pmb/install/install.py b/pmb/install/install.py index f5c2283d..2f94d99f 100644 --- a/pmb/install/install.py +++ b/pmb/install/install.py @@ -28,14 +28,17 @@ import pmb.config import pmb.helpers.run import pmb.install.blockdevice import pmb.install.file +import pmb.install.recovery import pmb.install -def mount_device_rootfs(args): - # Mount the device rootfs +def mount_device_rootfs(args, suffix="native"): + """ + Mount the device rootfs. + """ mountpoint = "/mnt/rootfs_" + args.device pmb.helpers.mount.bind(args, args.work + "/chroot_rootfs_" + args.device, - args.work + "/chroot_native" + mountpoint) + args.work + "/chroot_" + suffix + mountpoint) return mountpoint @@ -153,45 +156,7 @@ def setup_keymap(args): logging.info("NOTE: No valid keymap specified for device") -def install(args): - # Install required programs in native chroot - logging.info("*** (1/5) PREPARE NATIVE CHROOT ***") - pmb.chroot.apk.install(args, pmb.config.install_native_packages, - build=False) - - # List all packages to be installed (including the ones specified by --add) - # and upgrade the installed packages/apkindexes - logging.info("*** (2/5) CREATE DEVICE ROOTFS (" + args.device + ") ***") - install_packages = (pmb.config.install_device_packages + - ["device-" + args.device]) - if args.ui.lower() != "none": - install_packages += ["postmarketos-ui-" + args.ui] - suffix = "rootfs_" + args.device - pmb.chroot.apk.upgrade(args, suffix) - - # Explicitly call build on the install packages, to re-build them or any - # dependency, in case the version increased - if args.extra_packages.lower() != "none": - install_packages += args.extra_packages.split(",") - if args.add: - install_packages += args.add.split(",") - for pkgname in install_packages: - pmb.build.package(args, pkgname, args.deviceinfo["arch"]) - - # Install all packages to device rootfs chroot (and rebuild the initramfs, - # because that doesn't always happen automatically yet, e.g. when the user - # installed a hook without pmbootstrap - see #69 for more info) - pmb.chroot.apk.install(args, install_packages, suffix) - pmb.install.file.write_os_release(args, suffix) - for flavor in pmb.chroot.other.kernel_flavors_installed(args, suffix): - pmb.chroot.initfs.build(args, flavor, suffix) - - # Set the user password - set_user_password(args) - - # Set the keymap if the device requires it - setup_keymap(args) - +def install_system_image(args): # Partition and fill image/sdcard logging.info("*** (3/5) PREPARE INSTALL BLOCKDEVICE ***") pmb.chroot.shutdown(args, True) @@ -249,3 +214,72 @@ def install(args): logging.info("* If the above steps do not work, you can also create" " symlinks to the generated files with 'pmbootstrap flasher" " export [export_folder]' and flash outside of pmbootstrap.") + + +def install_recovery_zip(args): + logging.info("*** (3/4) CREATING RECOVERY-FLASHABLE ZIP ***") + suffix = "buildroot_" + args.deviceinfo["arch"] + mount_device_rootfs(args, suffix) + pmb.install.recovery.create_zip(args, suffix) + + # Flash information + logging.info("*** (4/4) FLASHING TO DEVICE ***") + logging.info("Run the following to flash your installation to the" + " target device:") + logging.info("* pmbootstrap flasher --method adb sideload") + logging.info(" Flashes the installer zip to your device:") + + # Export information + logging.info("* If this does not work, you can also create a" + " symlink to the generated zip with 'pmbootstrap flasher" + " export --android-recovery-zip [export_folder]' and" + " flash outside of pmbootstrap.") + + +def install(args): + # Number of steps for the different installation methods. + steps = 4 if args.android_recovery_zip else 5 + + # Install required programs in native chroot + logging.info("*** (1/{}) PREPARE NATIVE CHROOT ***".format(steps)) + pmb.chroot.apk.install(args, pmb.config.install_native_packages, + build=False) + + # List all packages to be installed (including the ones specified by --add) + # and upgrade the installed packages/apkindexes + logging.info('*** (2/{0}) CREATE DEVICE ROOTFS ("{1}") ***'.format(steps, + args.device)) + install_packages = (pmb.config.install_device_packages + + ["device-" + args.device]) + if args.ui.lower() != "none": + install_packages += ["postmarketos-ui-" + args.ui] + suffix = "rootfs_" + args.device + pmb.chroot.apk.upgrade(args, suffix) + + # Explicitly call build on the install packages, to re-build them or any + # dependency, in case the version increased + if args.extra_packages.lower() != "none": + install_packages += args.extra_packages.split(",") + if args.add: + install_packages += args.add.split(",") + for pkgname in install_packages: + pmb.build.package(args, pkgname, args.deviceinfo["arch"]) + + # Install all packages to device rootfs chroot (and rebuild the initramfs, + # because that doesn't always happen automatically yet, e.g. when the user + # installed a hook without pmbootstrap - see #69 for more info) + pmb.chroot.apk.install(args, install_packages, suffix) + pmb.install.file.write_os_release(args, suffix) + for flavor in pmb.chroot.other.kernel_flavors_installed(args, suffix): + pmb.chroot.initfs.build(args, flavor, suffix) + + # Set the user password + set_user_password(args) + + # Set the keymap if the device requires it + setup_keymap(args) + + if args.android_recovery_zip: + install_recovery_zip(args) + else: + install_system_image(args) diff --git a/pmb/install/recovery.py b/pmb/install/recovery.py new file mode 100644 index 00000000..b0471c3f --- /dev/null +++ b/pmb/install/recovery.py @@ -0,0 +1,61 @@ +""" +Copyright 2017 Attila Szollosi + +This file is part of pmbootstrap. + +pmbootstrap is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +pmbootstrap is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with pmbootstrap. If not, see . +""" +import logging + +import pmb.chroot + + +def create_zip(args, suffix): + """ + Create android recovery compatible installer zip. + """ + zip_root = "/var/lib/postmarketos-android-recovery-installer/" + rootfs = "/mnt/rootfs_" + args.device + + # Install recovery installer package in buildroot + pmb.chroot.apk.install(args, + ["postmarketos-android-recovery-installer"], + suffix) + + logging.info("(" + suffix + ") create recovery zip") + + # Create config file for the recovery installer + with open(args.work + "/chroot_" + suffix + "/tmp/install_options", + "w") as install_options: + install_options.write( + "\n".join(['DEVICE="{}"'.format(args.device), + 'FLASH_BOOTIMG="{}"'.format( + str(args.recovery_flash_bootimg).lower()), + 'INSTALL_PARTITION="{}"'.format( + args.recovery_install_partition), + 'CIPHER="{}"'.format(args.cipher), + 'FDE="{}"'.format( + str(args.full_disk_encryption).lower())])) + + commands = [ + # Move config file from /tmp/ to zip root + ["mv", "/tmp/install_options", "install_options"], + # Copy boot.img to zip root + ["cp", rootfs + "/boot/boot.img-" + args.device, "boot.img"], + # Create tar archive of the rootfs + ["tar", "-pczf", "rootfs.tar.gz", "--exclude", "./home/user/*", + "-C", rootfs, "."], + ["build-recovery-zip"]] + for command in commands: + pmb.chroot.root(args, command, suffix, working_dir=zip_root) diff --git a/pmb/parse/arguments.py b/pmb/parse/arguments.py index 71fac305..1fa56bee 100644 --- a/pmb/parse/arguments.py +++ b/pmb/parse/arguments.py @@ -25,12 +25,8 @@ def arguments_flasher(subparser): ret = subparser.add_parser("flasher", help="flash something to the" " target device") sub = ret.add_subparsers(dest="action_flasher") - - # Other - sub.add_parser("flash_system", help="flash the system partition") - sub.add_parser("list_flavors", help="list installed kernel flavors" + - " inside the device rootfs chroot on this computer") - sub.add_parser("list_devices", help="show connected devices") + ret.add_argument("--method", help="override flash method", + dest="flash_method", default=None) # Boot, flash kernel, export boot = sub.add_parser("boot", help="boot a kernel once") @@ -42,6 +38,13 @@ def arguments_flasher(subparser): for action in [boot, flash_kernel, export]: action.add_argument("--flavor", default=None) + # Other + sub.add_parser("flash_system", help="flash the system partition") + sub.add_parser("list_flavors", help="list installed kernel flavors" + + " inside the device rootfs chroot on this computer") + sub.add_parser("list_devices", help="show connected devices") + sub.add_parser("sideload", help="sideload recovery zip") + # Export: additional arguments export.add_argument("export_folder", help="export folder, defaults to" " /tmp/postmarketOS-export", @@ -181,6 +184,16 @@ def arguments(): " added to the rootfs (e.g. 'vim,gcc')") install.add_argument("--no-fde", help="do not use full disk encryption", action="store_false", dest="full_disk_encryption") + install.add_argument("--android-recovery-zip", + help="generate TWRP flashable zip", + action="store_true", dest="android_recovery_zip") + install.add_argument("--recovery-flash-bootimg", + help="include kernel in recovery flashable zip", + action="store_true", dest="recovery_flash_bootimg") + install.add_argument("--recovery-install-partition", default="system", + help="partition to flash from recovery," + "eg. external_sd", + dest="recovery_install_partition") # Action: menuconfig / parse_apkbuild menuconfig = sub.add_parser("menuconfig", help="run menuconfig on" diff --git a/test/static_code_analysis.sh b/test/static_code_analysis.sh index e7638064..453201ef 100755 --- a/test/static_code_analysis.sh +++ b/test/static_code_analysis.sh @@ -26,6 +26,11 @@ sh_files=" ./aports/main/postmarketos-base/firmwareload.sh ./aports/main/postmarketos-mkinitfs/init.sh.in ./aports/main/postmarketos-mkinitfs/init_functions.sh + ./aports/main/postmarketos-android-recovery-installer/build_zip.sh + ./aports/main/postmarketos-android-recovery-installer/pmos_install + ./aports/main/postmarketos-android-recovery-installer/pmos_install_functions + ./aports/main/postmarketos-android-recovery-installer/pmos_setpw + ./aports/main/postmarketos-android-recovery-installer/update-binary $(find . -name '*.trigger') " for file in ${sh_files}; do