#!/usr/bin/env bash # use TERM to exit on error trap "exit 1" TERM export TOP_PID=$$ # coreboot repo COREBOOT_REPO_PURISM="https://source.puri.sm/coreboot/coreboot.git" COREBOOT_REPO_TAG="4.14-Purism-1" # dependencies DEPS_BASE=(dmidecode wget sha256sum gzip) DEPS_FLASH=(git build-essential libpci-dev libudev-dev zlib1g-dev) DEPS_FLASH_FEDORA=(git gcc g++ make xz bzip2 pciutils-devel) DEPS_COREBOOT=(gnat m4 bison flex libncurses5-dev python python2) DEPS_COREBOOT_FEDORA=(gcc-gnat bison flex ncurses-devel zlib-devel python python2 patch) DEPS_GRUB2=(autoconf automake autopoint libfreetype-dev fonts-unifont) DEPS_GRUB2_FEDORA=(autoconf automake gettext-devel freetype-devel unifont-fonts) #local dirs LOCAL_TOOLS_PATH="./tools" LOCAL_TEMP_DIR="$(mktemp -d)" # locally compiled coreboot utils CBFSTOOL_CB="./coreboot/util/cbfstool/cbfstool" IFDTOOL_CB="./coreboot/util/ifdtool/ifdtool" BACKUP_DIR="./backups" # functions, functions, functions check_root() { if [[ "$EUID" != 0 ]]; then die "This script must be run as root; re-run prefixed with sudo" fi } print_info () { echo "################################" echo "Librem 14 coreboot+Grub2 builder " echo "################################" } initial_setup () { # get model, firmware version, serial # via dmidecode vendor=$(dmidecode -s bios-vendor) model=$(dmidecode -s system-product-name | grep -i Librem) CURRENT_FW_VER=$(dmidecode -s bios-version) CURRENT_FW_DATE=$(dmidecode -s bios-release-date) DMIDECODE_SERIAL_NUMBER=$(dmidecode -t 1 | grep "Serial Number" | cut -d' ' -f 3-) # set CURRENT_FW_TYPE type if [ "$vendor" = "coreboot" ]; then case ${CURRENT_FW_VER^^} in *"PUREBOOT"* ) CURRENT_FW_TYPE="HEADS";; *"HEADS"* ) CURRENT_FW_TYPE="HEADS";; *"GRUB2"* ) CURRENT_FW_TYPE="GRUB2";; * ) CURRENT_FW_TYPE="STANDARD";; esac else CURRENT_FW_TYPE="UNKNOWN" fi FW_UPDATE="" case $CURRENT_FW_TYPE in "HEADS" ) FW_TYPE_STRING="PureBoot (coreboot+Heads)" CURR_FW_NUM=`echo ${CURRENT_FW_VER} | sed -e "s/^PureBoot-beta-//" -e "s/^PureBoot-Release-//"` UPD_FW_NUM=`echo ${COREBOOT_HEADS_VERSION} | sed -e "s/^PureBoot-beta-//" -e "s/^PureBoot-Release-//"` [[ "$UPD_FW_NUM" > "$CURR_FW_NUM" ]] \ && FW_UPDATE=`echo ${COREBOOT_HEADS_VERSION} | tr '-' ' '` CURRENT_FW_VER=`echo ${CURRENT_FW_VER#PureBoot-} | tr '-' ' '` CURRENT_FW_DATE="" ;; "STANDARD" ) FW_TYPE_STRING="Standard (coreboot+SeaBIOS)" [[ "$COREBOOT_SEABIOS_VERSION" > "$CURRENT_FW_VER" ]] \ && FW_UPDATE="$COREBOOT_SEABIOS_VERSION"; CURRENT_FW_DATE="(${CURRENT_FW_DATE})" ;; "GRUB2" ) FW_TYPE_STRING="Grub2 (coreboot+Grub2)" [[ "$COREBOOT_GRUB2_VERSION" > "$CURRENT_FW_VER" ]] \ && FW_UPDATE="$COREBOOT_GRUB2_VERSION"; CURRENT_FW_DATE="(${CURRENT_FW_DATE})" ;; * ) FW_TYPE_STRING="Unknown" ;; esac # set LIBREM_MODEL, MODEL_INDEX # strip leading 'librem' + space/underscore model=`echo ${model,,} | sed -e "s/^librem//" -e "s/^ //" -e "s/^_//"` case ${model} in "14") LIBREM_MODEL="14"; PLATFORM="CML"; MODEL_INDEX="9";; *) LIBREM_MODEL="unknown"; PLATFORM="UNK"; MODEL_INDEX="0";; esac } cleanup() { # ensure all files owned by calling/non-root user chown -R -f $(logname). . } die () { local msg=$1 if [ ! -z "$msg" ]; then echo "" echo -e "$msg" echo "" fi kill -s TERM $TOP_PID cleanup exit 1 } get_flashrom () { # check if flashrom exists on system echo "" echo "Checking for usable version of flashrom" FLASHROM_CMD=$(which flashrom) if [[ ! -z "$FLASHROM_CMD" && "$PLATFORM" != "CML" ]]; then #check version version=$($FLASHROM_CMD -R | awk -F" " '{print $2;exit}' | grep "1.") if [[ "$version" > "v1.1" ]]; then # verify build supports logging (some PureOS/Debian versions broken) if $FLASHROM_CMD -L -o /tmp/flashrom.log >/dev/null 2>&1 ; then #use system flashrom echo "Found built-in flashrom version $version" return fi fi fi # no system flashrom, not version 1.2+, or PLATFORM == CML: # build from upstream flashrom source # check deps first check_dependencies "flashrom" if [ ! -d ${LOCAL_TOOLS_PATH}/flashrom ]; then ( mkdir -p ${LOCAL_TOOLS_PATH} cd ${LOCAL_TOOLS_PATH} echo -n "Cloning flashrom git repo..." git clone --single-branch --branch master https://review.coreboot.org/flashrom.git >/dev/null [ $? -ne 0 ] && die "Error cloning flashrom repo from git" echo "done" ) fi ( echo -n "Building flashrom from source..." cd ${LOCAL_TOOLS_PATH}/flashrom git fetch origin >/dev/null 2>&1 if ! git show c64486b >/dev/null 2>&1; then cd .. rm -rf ./flashrom git clone --single-branch --branch master https://review.coreboot.org/flashrom.git >/dev/null [ $? -ne 0 ] && die "Error cloning flashrom repo from git" cd flashrom fi git reset --hard c64486b >/dev/null 2>&1 [ $? -ne 0 ] && die "Error checking out flashrom via git" make CONFIG_NOTHING=yes CONFIG_DUMMY=yes CONFIG_INTERNAL=yes WARNERROR=no [ $? -ne 0 ] && die "Error building flashrom (missing build dependency libpci-dev?)" echo "done" ) FLASHROM_CMD="${LOCAL_TOOLS_PATH}/flashrom/flashrom" } get_serial () { if [ "$PLATFORM" = "BDW" ]; then #no serial to set, clear file if exists rm -f ${LOCAL_TEMP_DIR}/serial_number.txt >/dev/null return; fi #prompt user for serial # selection/entry echo "" echo "Set the device serial number:" echo "" if [ "$DMIDECODE_SERIAL_NUMBER" != "" ]; then DMIDECODE_SERIAL_OPT=1 MANUAL_SERIAL_OPT=2 NO_SERIAL_OPT=3 echo "${DMIDECODE_SERIAL_OPT} - Extracted from your local system (${DMIDECODE_SERIAL_NUMBER})" else DMIDECODE_SERIAL_OPT=0 MANUAL_SERIAL_OPT=1 NO_SERIAL_OPT=2 fi echo "${MANUAL_SERIAL_OPT} - Enter serial number manually" echo "${NO_SERIAL_OPT} - Do not set a serial number" echo "" serial=0 [ ${MODEL_INDEX} = 0 ] && default=2 || default=1 while [ "$serial" == "0" ]; do read -r -p "Enter your choice (default: $default): " serial [ "$serial" = "" ] && serial=$default case $serial in ${DMIDECODE_SERIAL_OPT} ) if [ "$DMIDECODE_SERIAL_OPT" = 1 ]; then echo -n "$DMIDECODE_SERIAL_NUMBER" > ${LOCAL_TEMP_DIR}/serial_number.txt else echo "Invalid option" serial=0 fi ;; ${MANUAL_SERIAL_OPT} ) read -r -p "Enter the machine's serial number : " serial_number echo -n "$serial_number" > ${LOCAL_TEMP_DIR}/serial_number.txt ;; ${NO_SERIAL_OPT} ) echo -n "" > ${LOCAL_TEMP_DIR}/serial_number.txt ;; * ) echo "Invalid option" serial=0 ;; esac done echo "" } flashrom_progress() { local current=0 local total_bytes=0 local percent=0 local IN='' local spin='-\|/' local spin_idx=0 local progressbar='' local progressbar2='' local status='init' local prev_word='' local prev_prev_word='' progressbar2=$(for ((i=0; i < 49; i++)) do echo -ne ' ' ; done) echo "Initializing internal Flash Programmer" while true ; do prev_prev_word=$prev_word prev_word=$IN read -r -d' ' IN || break if [ "$total_bytes" != "0" ]; then current=$(echo "$IN" | grep -E -o '0x[0-9a-f]+-0x[0-9a-f]+:.*' | grep -E -o "0x[0-9a-f]+" | tail -n 1) if [ "${current}" != "" ]; then percent=$((100 * (current + 1) / total_bytes)) progressbar=$(for ((i=0; i < $((percent / 2)); i++)) do echo -ne '#' ; done) progressbar2=$(for ((i=0; i < $((49 - percent / 2)); i++)) do echo -ne ' ' ; done) fi else if [ "$prev_prev_word" == "Reading" ] && [ "$IN" == "bytes" ]; then total_bytes=$prev_word echo "Total flash size : $total_bytes bytes" fi fi if [ "$percent" -eq 100 ]; then spin_idx=4 else spin_idx=$(( (spin_idx+1) %4 )) fi if [ "$status" == "init" ]; then if [ "$IN" == "contents..." ]; then status="reading" echo "Reading old flash contents. Please wait..." fi fi if [ "$status" == "reading" ]; then if echo "${IN}" | grep "done." > /dev/null ; then status="writing" fi fi if [ "$status" == "writing" ]; then echo -ne "Flashing: [${progressbar}${spin:$spin_idx:1}${progressbar2}] (${percent}%)\\r" if echo "$IN" | grep "Verifying" > /dev/null ; then status="verifying" echo "" echo "Verifying flash contents. Please wait..." fi if echo "$IN" | grep "identical" > /dev/null ; then status="done" echo "" echo "The flash contents are identical to the image being flashed." fi fi if [ "$status" == "verifying" ]; then if echo "${IN}" | grep "VERIFIED." > /dev/null ; then status="done" echo "The flash contents were verified and the image was flashed correctly." fi fi done echo "" if [ "$status" == "done" ]; then return 0 else echo 'Error flashing coreboot -- see timestampped flashrom log in current directory for more info' echo "" return 1 fi } set_serial_number () { # pass in full path of file in which to inject serial [[ -z "$1" || ! -f "$1" ]] && die "Error: a valid firmware filename is required" # check if we have a serial # to add to cbfs if [ -f ${LOCAL_TEMP_DIR}/serial_number.txt ]; then # get cbfstool if needed get_cbfstool # inject serial into coreboot image echo "Injecting serial number into firmware image" $CBFSTOOL_BIN "$1" remove -n serial_number -r COREBOOT >/dev/null 2>&1 $CBFSTOOL_BIN "$1" add -n serial_number -t raw -f resources/serial_number.txt -r COREBOOT >/dev/null 2>&1 [ $? -ne 0 ] && die "Error adding serial number to file ${1}" fi } backup_firmware () { # assume FLASHROM_CMD exists/is set already, LOCAL_TEMP_DIR set up mkdir -p ${BACKUP_DIR} DATE_FIX=$(date '+%Y%m%d-%H%M%S') $FLASHROM_CMD -p internal:ich_spi_mode=hwseq -r ${BACKUP_DIR}/backup-${DATE_FIX}.bin -o ${BACKUP_DIR}/backup-$(date '+%Y%m%d-%H%M%S').log >/dev/null 2>&1 if [ $? -ne 0 ]; then die "Error reading current firmware; see flashrom log for more info." fi } flash_firmware_internal () { # assume FLASHROM_CMD exists/is set already # pass in full path of file to be flashed [[ -z "$1" || ! -f "$1" ]] && die "Error: a valid filename to be flashed is required" $FLASHROM_CMD -p internal:ich_spi_mode=hwseq -w "$1" -V -o "./flashrom-$(date '+%Y%m%d-%H%M%S').log" 2>&1 | flashrom_progress return $? } update_serial_number () { # ensure using SeaBIOS if [[ "${CURRENT_FW_TYPE}" != "STANDARD" ]]; then echo "This feature is only valid for use with the standard/SeaBIOS firmware" echo "" return fi # show serial menu / create serial.txt get_serial # check for / get flashrom get_flashrom # read current firmware echo "" if [ ! -f ${LOCAL_TEMP_DIR}/${CURRENT_FW_BIN} ]; then echo "Reading current firmware..." read_current_firmware else echo "Current firmware already read from flash" fi # inject into current firmware set_serial_number "${LOCAL_TEMP_DIR}/${CURRENT_FW_BIN}" # prompt to update echo "" flash=0 while [ "$flash" != "y" ] && [ "$flash" != "n" ]; do read -r -p "Do you want to update the serial number now (Y/n) ? " flash [[ "$flash" == "N" ]] && flash="n" [[ "$flash" = "" || "$flash" == "Y" ]] && flash="y" done if [ "$flash" == "y" ]; then echo "" echo "coreboot flashing in progress. Do NOT interrupt this process." echo "" flash_firmware_internal "${LOCAL_TEMP_DIR}/${CURRENT_FW_BIN}" if [ $? -eq 0 ]; then echo "Serial number successfully updated; change will take effect on next boot" echo "" fi fi } update_crossgcc_toolchain() { # assume called from coreboot root dir local CURRENT_TOOLCHAIN_VERSION=0 local GCC_FILE='util/crossgcc/xgcc/bin/i386-elf-gcc' local TARGET_TOOLCHAIN_VERSION="$(git log -n 1 --pretty=%h util/crossgcc)" if [ -f "${GCC_FILE}" ]; then CURRENT_TOOLCHAIN_VERSION=$(${GCC_FILE} --version | grep -m 1 'coreboot toolchain' \ | cut -f2 -d'v' | cut -f2 -d'_' | cut -f1 -d')') fi if [ "${CURRENT_TOOLCHAIN_VERSION}" != "${TARGET_TOOLCHAIN_VERSION}" ]; then echo "coreboot toolchain version changed from ${CURRENT_TOOLCHAIN_VERSION} to ${TARGET_TOOLCHAIN_VERSION}" echo "Cleaning crossgcc compiler before rebuilding it" make crossgcc-clean retries=0 until [ "$retries" -ge 5 ] do make crossgcc-i386 CPUS=$(nproc) && break retries=$((retries+1)) done [ $? -ne 0 ] && die "Error building coreboot toolchain" || true fi } build_coreboot() { # extract / set device serial # to be injected later get_serial echo "" echo "Checking out/updating coreboot repo..." echo "" # check dir, clone if needed if [ ! -d coreboot ]; then echo "" git clone --branch ${COREBOOT_REPO_TAG} ${COREBOOT_REPO_PURISM} coreboot [ $? -ne 0 ] && die "Error cloning coreboot git repo" ( cd coreboot # It can fail if you don't have a git global user.name/user.email setup make gitconfig 2>/dev/null || true # init/update submodules git submodule update --init --force --checkout >/dev/null 2>&1 [ $? -ne 0 ] && die "Error checking out/updating coreboot submodules" ) fi # check out correct branch ( cd coreboot git fetch 2>/dev/null [ $? -ne 0 ] && die "Error fetching coreboot git repo" git fetch --tags 2>/dev/null [ $? -ne 0 ] && die "Error fetching coreboot git repo" git checkout --detach ${COREBOOT_REPO_TAG} 2>/dev/null [ $? -ne 0 ] && die "Error checking out coreboot git repo" # ensure submodules sane if [[ "`git diff 3rdparty`" != "" ]]; then git submodule update --force --checkout >/dev/null 2>&1 [[ "`git diff 3rdparty`" != "" ]] && \ die "submodules have been modified; build would not be reproducible" fi #build cbfstool and ifdtool ( cd util/cbfstool make [ $? -ne 0 ] && die "Error building cbfstool" ) ( cd util/ifdtool make [ $? -ne 0 ] && die "Error building ifdtool" ) ) # build coreboot ( # set pwd cd coreboot # let user know this will take a few echo -e "\n\n" echo "Ready to build coreboot - this will take some time depending on your connection" echo "speed and CPU/RAM, esp if the toolchain needs to be built." echo "" # check/build toolchain update_crossgcc_toolchain || die # get git version GIT_VERSION=$(git describe --tags --dirty) # do a clean build make distclean # copy config cp ../resources/config .config # copt grub config cp ../resources/grub.cfg grub.cfg echo "CONFIG_LOCALVERSION=\"${GIT_VERSION}\"" >> .config make olddefconfig >/dev/null # build coreboot and payload(s) make [ $? -ne 0 ] && die "Error building coreboot" # copy to root dir #cp build/coreboot.rom ../${COREBOOT_IMAGE} ) # calculate hash of BIOS region before injecting bootorder/serial ${IFDTOOL_CB} -x ${COREBOOT_IMAGE} # set serial set_serial_number ${COREBOOT_IMAGE} # print CBFS contents ${CBFSTOOL_CB} ${COREBOOT_IMAGE} print echo "" echo "" echo "Finished building coreboot for Librem ${LIBREM_MODEL^}" echo "" # prompt to flash echo "" flash=0 while [ "$flash" != "y" ] && [ "$flash" != "n" ]; do read -r -p "Do you want to flash the coreboot update now (y/N) ? " flash if [ "$flash" = "" ] || [ "$flash" == "N" ]; then flash="n" fi if [ "$flash" == "Y" ]; then flash="y" fi done if [ "$flash" == "y" ]; then # check for / get flashrom get_flashrom echo "" echo "coreboot flashing in progress. Do NOT interrupt this process." echo "" flash_firmware_internal ${COREBOOT_IMAGE} if [ $? -eq 0 ]; then echo "" echo "You must reboot for the coreboot update to take effect." echo "" read -r -p "Reboot now? (y/N) ? " rb if [ "$rb" = "Y" ] || [ "$rb" == "y" ]; then cleanup reboot fi fi else echo "" fi } check_dependencies() { local dep_type=$1 local DEPS local use_dnf=false [[ `which dnf 2>/dev/null` ]] && use_dnf=true case $dep_type in "base") DEPS=${DEPS_BASE[@]} ;; "flashrom") if [ $use_dnf = true ]; then DEPS=(${DEPS_BASE[@]} ${DEPS_FLASH_FEDORA[@]}) ; else DEPS=(${DEPS_BASE[@]} ${DEPS_FLASH[@]}) ; fi ;; "coreboot") if [ $use_dnf = true ]; then DEPS=(${DEPS_BASE[@]} ${DEPS_FLASH_FEDORA[@]} ${DEPS_GRUB2_FEDORA[@]}) ; else DEPS=(${DEPS_BASE[@]} ${DEPS_FLASH[@]} ${DEPS_COREBOOT[@]} ${DEPS_GRUB2[@]}) ; fi ;; *) ;; esac local pkg needed=() for pkg in "${DEPS[@]}"; do if [[ ! `which ${pkg} 2>/dev/null` \ && ! `dnf list installed 2>/dev/null | grep "^${pkg}"` \ && ! `apt list --installed 2>/dev/null | grep "^${pkg}"` \ && ! -f "/usr/share/doc/${pkg}/copyright" ]]; then needed+=("${pkg}") fi done if [[ "${#needed[@]}" -gt 0 ]]; then echo "" echo "One or more required dependencies are missing: ${needed[@]}" echo "The script will now attempt to install them for you" echo "" if [ $use_dnf = true ]; then if ! sudo dnf -y install "${needed[@]}" ; then die "Some required dependencies could not be installed automatically" fi elif which apt >/dev/null ; then sudo apt-get update >/dev/null 2>&1 if ! sudo apt-get -y install "${needed[@]}" ; then die "Some required dependencies could not be installed automatically" fi else echo "The script was unable to install the required dependencies automatically" echo "" die "Please ensure all required dependencies are installed and re-run this script" fi fi } # Start of main script # let's do stuff check_root check_dependencies "base" check_dependencies "flashrom" check_dependencies "coreboot" initial_setup print_info get_flashrom build_coreboot