#!/usr/bin/env bash # # Copyright 2022-2023 RnD Center "ELVEES", JSC # # SPDX-License-Identifier: GPLv3 # set -euo pipefail error() { echo "Error: $*" > /dev/stderr exit 1 } hideifok() { local out out=$(eval "$*" 2>&1) || (echo "$out" && exit 1) } # NOTE: There's false positive here: dm-xxx, but who cares... iswholedisk() { test -e "/sys/block/${1##*/}" } getpart() { local partprefix= [[ "$1" =~ [0-9]$ ]] && partprefix=p echo "$1$partprefix$2" } cmdexists() { hash "$1" 2>/dev/null } unmount() { if cmdexists udisksctl; then udisksctl unmount -b "$1" elif cmdexists systemd-umount; then systemd-umount "$1" else umount "$1" fi } unmountall() { # shellcheck disable=SC2013 for i in $(grep -F "$1" /etc/mtab | cut -f1 -d' '); do unmount "$i" done } # Up to here the code is sourceable if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then return 0 fi help() { echo 'tar2dev - Tool to unpack a tarball onto block device' echo ' Partition, format and unpack tarball in one command' echo echo 'Usage: tar2dev [options] ' echo echo 'Options:' echo ' -h Print this help' echo ' -n Set hostname' echo ' -s Comma-separated systemd services to enable' echo ' -v Verbosely list files processed' echo echo 'After extraction /usr/bin/extlinux-gen is called in target root filesystem.' echo 'It gets its parameters using environment variables. For more information see' echo 'extlinux-gen source code.' echo 'If ROOT variable is not set or empty then will be used automatic PARTUUID detect.' echo echo 'Examples:' echo ' FDT=board.dtb tar2dev -n myhost rootfs.tar /dev/sdX' echo ' ssh root@myboard zcat \| [vars] tar2dev [options] - /dev/mmcblkX < rootfs.tar.gz' echo ' ROOT=/dev/mmcblk0p1 FDT=board.dtb tar2dev -s stress-test rootfs.tar /dev/sdX' exit 1 } unset HOSTNAME TAR_VERBOSE="" while getopts 'hvn:s:' opt; do case $opt in h) help;; v) TAR_VERBOSE="-v";; n) HOSTNAME="$OPTARG";; s) SYSTEMD_SERVICES="$OPTARG";; *) help;; esac done shift $((OPTIND-1)) set +u [[ -z "$1" ]] && help [[ -z "$2" ]] && error "Not enough arguments"; set -u TAR="$1" DEV="$2" [[ "$EUID" != "0" ]] && error "This script must be run as root" [[ "$TAR" == "-" || -r "$TAR" ]] || error "'$TAR' is not readable" iswholedisk "$DEV" || error "'$DEV' is not a disk" [[ -w "$DEV" ]] || error "'$DEV' is not writable" # Real work starts here! echo "Unmount all partitions on '$DEV'..." unmountall "$DEV" echo "Partition '$DEV'..." hideifok "echo ';' | sfdisk -f '$DEV'" # Use automatic PARTUUID detection if ROOT is not specified or empty. # Universal way is to get part UUID via "lsblk -po NAME,PARTUUID", but in lsblk # PARTUUID will available in ~1 second after previous sfdisk call. This way is # difficult to do because delay may depend on PC speed. More reliable way is # to use sfdisk to read part UUID from device. # For GPT part UUID can be read via sfdisk --part-uuid. # For MBR part UUID can be got by concatenation of MBR label-id and partition number. if [[ ! -v ROOT || -z "$ROOT" ]]; then LABEL=$(sfdisk -d "$DEV" | grep "label:" | awk '{ print $2 }') if [[ "$LABEL" == "dos" ]]; then ID=$(sfdisk -d "$DEV" | grep "label-id:" | awk '{ print $2 }') ROOT="PARTUUID=${ID:2}-01" else GPT_PARTUUID=$(sfdisk --part-uuid "$DEV" 1) # sfdisk return uppercase PARTUUID for GPT partitions, this is why some # distributions won't boot, instead lowercase are always booted. ROOT="PARTUUID=${GPT_PARTUUID,,*}" fi export ROOT fi PART=$(getpart "$DEV" 1) echo "Format '$PART'..." hideifok "mkfs.ext4 -F -L root '$PART'" echo "Unpack '$TAR' on '$PART'..." DIR=$(mktemp -d) # shellcheck disable=SC2064 trap "echo 'Clean...'; rmdir '$DIR'" EXIT mount "$PART" "$DIR" # shellcheck disable=SC2064 trap "echo 'Unmount...'; unmount $PART; echo 'Clean...'; rmdir '$DIR'" EXIT tar -C "$DIR" $TAR_VERBOSE -xf "$TAR" if [[ -x "$DIR"/usr/bin/extlinux-gen ]]; then echo "Generate extlinux.conf..." TARGET_DIR="$DIR" "$DIR"/usr/bin/extlinux-gen fi if [[ -v HOSTNAME ]]; then echo "Set hostname to '$HOSTNAME'..." echo "$HOSTNAME" > "$DIR"/etc/hostname sed -i -e "s|buildroot|$HOSTNAME|" "$DIR"/etc/hosts fi if [[ -v SYSTEMD_SERVICES ]]; then for SERVICE in ${SYSTEMD_SERVICES//,/ }; do echo "Enable '$SERVICE' systemd service ..." ln -s /usr/lib/systemd/system/"$SERVICE".service \ "$DIR"/etc/systemd/system/multi-user.target.wants/"$SERVICE".service done fi