diff --git a/.bin/Scripts/build-ufd b/.bin/Scripts/build-ufd index 5f272d7d..bb0813a8 100755 --- a/.bin/Scripts/build-ufd +++ b/.bin/Scripts/build-ufd @@ -1,671 +1,148 @@ -#!/usr/bin/env bash +#!/bin/env python3 # -## Wizard Kit: UFD Build Tool -# -# Based on a template by BASH3 Boilerplate v2.3.0 -# http://bash3boilerplate.sh/#authors -# -# The MIT License (MIT) -# Copyright (c) 2013 Kevin van Zonneveld and contributors -# You are not obligated to bundle the LICENSE file with your b3bp projects as long -# as you leave these references intact in the header comments of your source files. - -# Exit on error. Append "|| true" if you expect an error. -set -o errexit -# Exit on error inside any functions or subshells. -set -o errtrace -# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR -set -o nounset -# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip` -set -o pipefail -# Turn on traces, useful while debugging but commented out by default -# set -o xtrace - -if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then - __i_am_main_script="0" # false - - if [[ "${__usage+x}" ]]; then - if [[ "${BASH_SOURCE[1]}" = "${0}" ]]; then - __i_am_main_script="1" # true - fi - - __b3bp_external_usage="true" - __b3bp_tmp_source_idx=1 - fi -else - __i_am_main_script="1" # true - [[ "${__usage+x}" ]] && unset -v __usage - [[ "${__helptext+x}" ]] && unset -v __helptext -fi - -# Set magic variables for current file, directory, os, etc. -__dir="$(cd "$(dirname "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" && pwd)" -__file="${__dir}/$(basename "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" -__base="$(basename "${__file}" .sh)" -__wd="$(pwd)" -__usage_example="Usage: sudo $(basename "${0}") --ufd-device [device] --linux-iso [path] --main-kit [path] --winpe-iso [path]" -__all_args="" -for a in "${@}"; do - if [[ "${a:0:1}" == "-" ]]; then - __all_args="${__all_args} ${a}" - else - __all_args="${__all_args} \"${a}\"" - fi -done - - -# Define the environment variables (and their defaults) that this script depends on -LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency -NO_COLOR="${NO_COLOR:-}" # true = disable color. otherwise autodetected - - -### Functions -############################################################################## - -function __b3bp_log () { - local log_level="${1}" - shift - - # shellcheck disable=SC2034 - local color_debug="\x1b[35m" - # shellcheck disable=SC2034 - local color_info="\x1b[32m" - # shellcheck disable=SC2034 - local color_notice="\x1b[34m" - # shellcheck disable=SC2034 - local color_warning="\x1b[33m" - # shellcheck disable=SC2034 - local color_error="\x1b[31m" - # shellcheck disable=SC2034 - local color_critical="\x1b[1;31m" - # shellcheck disable=SC2034 - local color_alert="\x1b[1;33;41m" - # shellcheck disable=SC2034 - local color_emergency="\x1b[1;4;5;33;41m" - - local colorvar="color_${log_level}" - - local color="${!colorvar:-${color_error}}" - local color_reset="\x1b[0m" - - if [[ "${NO_COLOR:-}" = "true" ]] || ( [[ "${TERM:-}" != *"256color"* ]] && [[ "${TERM:-}" != "xterm"* ]] && [[ "${TERM:-}" != "screen"* ]] ) || [[ ! -t 2 ]]; then - if [[ "${NO_COLOR:-}" != "false" ]]; then - # Don't use colors on pipes or non-recognized terminals - color=""; color_reset="" - fi - fi - - # all remaining arguments are to be printed - local log_line="" - - while IFS=$'\n' read -r log_line; do - echo -e "$(date -u +"%Y-%m-%d %H:%M:%S UTC") ${color}$(printf "[%9s]" "${log_level}")${color_reset} ${log_line}" 1>&2 - done <<< "${@:-}" -} - -function emergency () { __b3bp_log emergency "${@}"; exit 1; } -function alert () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && __b3bp_log alert "${@}"; true; } -function critical () { [[ "${LOG_LEVEL:-0}" -ge 2 ]] && __b3bp_log critical "${@}"; true; } -function error () { [[ "${LOG_LEVEL:-0}" -ge 3 ]] && __b3bp_log error "${@}"; true; } -function warning () { [[ "${LOG_LEVEL:-0}" -ge 4 ]] && __b3bp_log warning "${@}"; true; } -function notice () { [[ "${LOG_LEVEL:-0}" -ge 5 ]] && __b3bp_log notice "${@}"; true; } -function info () { [[ "${LOG_LEVEL:-0}" -ge 6 ]] && __b3bp_log info "${@}"; true; } -function debug () { [[ "${LOG_LEVEL:-0}" -ge 7 ]] && __b3bp_log debug "${@}"; true; } - -function help () { - echo "" 1>&2 - echo " ${*}" 1>&2 - echo "" 1>&2 - echo " ${__usage:-No usage available}" 1>&2 - echo "" 1>&2 - - if [[ "${__helptext:-}" ]]; then - echo " ${__helptext}" 1>&2 - echo "" 1>&2 - fi - - exit 1 -} - - -### Parse commandline options -############################################################################## - -# Commandline options. This defines the usage page, and is used to parse cli -# opts & defaults from. The parsing is unforgiving so be precise in your syntax -# - A short option must be preset for every long option; but every short option -# need not have a long option -# - `--` is respected as the separator between options and arguments -# - We do not bash-expand defaults, so setting '~/app' as a default will not resolve to ${HOME}. -# you can use bash variables to work around this (so use ${HOME} instead) - -# shellcheck disable=SC2015 -[[ "${__usage+x}" ]] || read -r -d '' __usage <<-'EOF' || true # exits non-zero when EOF encountered - OPTIONS: - -u --ufd-device [arg] Device to which the kit will be applied - -l --linux-iso [arg] Path to the Linux ISO - - -e --extra-dir [arg] Path to the Extra folder (optional) - -m --main-kit [arg] Path to the Main Kit (optional) - -w --winpe-iso [arg] Path to the WinPE ISO (optional) - -h --help This page - - ADVANCED: - -d --debug Enable debug mode - -v --verbose Enable verbose mode - -M --use-mbr Use real MBR instead of GPT w/ Protective MBR - -F --force Bypass all confirmation messages. USE WITH EXTREME CAUTION! -EOF - -# shellcheck disable=SC2015 -[[ "${__helptext+x}" ]] || read -r -d '' __helptext <<-'EOF' || true # exits non-zero when EOF encountered - Paths can be relative to the current working directory or absolute -EOF - -# Translate usage string -> getopts arguments, and set $arg_ defaults -while read -r __b3bp_tmp_line; do - if [[ "${__b3bp_tmp_line}" =~ ^- ]]; then - # fetch single character version of option string - __b3bp_tmp_opt="${__b3bp_tmp_line%% *}" - __b3bp_tmp_opt="${__b3bp_tmp_opt:1}" - - # fetch long version if present - __b3bp_tmp_long_opt="" - - if [[ "${__b3bp_tmp_line}" = *"--"* ]]; then - __b3bp_tmp_long_opt="${__b3bp_tmp_line#*--}" - __b3bp_tmp_long_opt="${__b3bp_tmp_long_opt%% *}" - fi - - # map opt long name to+from opt short name - printf -v "__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" '%s' "${__b3bp_tmp_opt}" - printf -v "__b3bp_tmp_opt_short2long_${__b3bp_tmp_opt}" '%s' "${__b3bp_tmp_long_opt//-/_}" - - # check if option takes an argument - if [[ "${__b3bp_tmp_line}" =~ \[.*\] ]]; then - __b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg - __b3bp_tmp_init="" # it has an arg. init with "" - printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "1" - elif [[ "${__b3bp_tmp_line}" =~ \{.*\} ]]; then - __b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg - __b3bp_tmp_init="" # it has an arg. init with "" - # remember that this option requires an argument - printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2" - else - __b3bp_tmp_init="0" # it's a flag. init with 0 - printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "0" - fi - __b3bp_tmp_opts="${__b3bp_tmp_opts:-}${__b3bp_tmp_opt}" - fi - - [[ "${__b3bp_tmp_opt:-}" ]] || continue - - if [[ "${__b3bp_tmp_line}" =~ (^|\.\ *)Default= ]]; then - # ignore default value if option does not have an argument - __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" - - if [[ "${!__b3bp_tmp_varname}" != "0" ]]; then - __b3bp_tmp_init="${__b3bp_tmp_line##*Default=}" - __b3bp_tmp_re='^"(.*)"$' - if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then - __b3bp_tmp_init="${BASH_REMATCH[1]}" - else - __b3bp_tmp_re="^'(.*)'$" - if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then - __b3bp_tmp_init="${BASH_REMATCH[1]}" - fi - fi - fi - fi - - if [[ "${__b3bp_tmp_line}" =~ (^|\.\ *)Required\. ]]; then - # remember that this option requires an argument - printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2" - fi - - printf -v "arg_${__b3bp_tmp_opt:0:1}" '%s' "${__b3bp_tmp_init}" -done <<< "${__usage:-}" - -# run getopts only if options were specified in __usage -if [[ "${__b3bp_tmp_opts:-}" ]]; then - # Allow long options like --this - __b3bp_tmp_opts="${__b3bp_tmp_opts}-:" - - # Reset in case getopts has been used previously in the shell. - OPTIND=1 - - # start parsing command line - set +o nounset # unexpected arguments will cause unbound variables - # to be dereferenced - # Overwrite $arg_ defaults with the actual CLI options - while getopts "${__b3bp_tmp_opts}" __b3bp_tmp_opt; do - [[ "${__b3bp_tmp_opt}" = "?" ]] && help "Invalid use of script: ${*} " - - if [[ "${__b3bp_tmp_opt}" = "-" ]]; then - # OPTARG is long-option-name or long-option=value - if [[ "${OPTARG}" =~ .*=.* ]]; then - # --key=value format - __b3bp_tmp_long_opt=${OPTARG/=*/} - # Set opt to the short option corresponding to the long option - __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" - printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" - OPTARG=${OPTARG#*=} - else - # --key value format - # Map long name to short version of option - __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${OPTARG//-/_}" - printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" - # Only assign OPTARG if option takes an argument - __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt}" - printf -v "OPTARG" '%s' "${@:OPTIND:${!__b3bp_tmp_varname}}" - # shift over the argument if argument is expected - ((OPTIND+=__b3bp_tmp_has_arg_${__b3bp_tmp_opt})) - fi - # we have set opt/OPTARG to the short value and the argument as OPTARG if it exists - fi - __b3bp_tmp_varname="arg_${__b3bp_tmp_opt:0:1}" - __b3bp_tmp_default="${!__b3bp_tmp_varname}" - - __b3bp_tmp_value="${OPTARG}" - if [[ -z "${OPTARG}" ]] && [[ "${__b3bp_tmp_default}" = "0" ]]; then - __b3bp_tmp_value="1" - fi - - printf -v "${__b3bp_tmp_varname}" '%s' "${__b3bp_tmp_value}" - debug "cli arg ${__b3bp_tmp_varname} = (${__b3bp_tmp_default}) -> ${!__b3bp_tmp_varname}" - done - set -o nounset # no more unbound variable references expected - - shift $((OPTIND-1)) - - if [[ "${1:-}" = "--" ]] ; then - shift - fi -fi - - -### Automatic validation of required option arguments -############################################################################## - -for __b3bp_tmp_varname in ${!__b3bp_tmp_has_arg_*}; do - # validate only options which required an argument - [[ "${!__b3bp_tmp_varname}" = "2" ]] || continue - - __b3bp_tmp_opt_short="${__b3bp_tmp_varname##*_}" - __b3bp_tmp_varname="arg_${__b3bp_tmp_opt_short}" - [[ "${!__b3bp_tmp_varname}" ]] && continue - - __b3bp_tmp_varname="__b3bp_tmp_opt_short2long_${__b3bp_tmp_opt_short}" - printf -v "__b3bp_tmp_opt_long" '%s' "${!__b3bp_tmp_varname}" - [[ "${__b3bp_tmp_opt_long:-}" ]] && __b3bp_tmp_opt_long=" (--${__b3bp_tmp_opt_long//_/-})" - - help "Option -${__b3bp_tmp_opt_short}${__b3bp_tmp_opt_long:-} requires an argument" -done - - -### Cleanup Environment variables -############################################################################## - -for __tmp_varname in ${!__b3bp_tmp_*}; do - unset -v "${__tmp_varname}" -done - -unset -v __tmp_varname - - -### Externally supplied __usage. Nothing else to do here -############################################################################## - -if [[ "${__b3bp_external_usage:-}" = "true" ]]; then - unset -v __b3bp_external_usage - return -fi - - -### Signal trapping and backtracing -############################################################################## - -function __b3bp_cleanup_before_exit () { - if [[ "$EUID" -eq 0 ]]; then - for d in Dest Linux WinPE; do - if [[ -d "/mnt/${d}" ]]; then - umount "/mnt/${d}" || true - rmdir "/mnt/${d}" || true - fi - done - fi - if [[ "${?}" != "0" ]]; then - info "Sources unmounted" - fi - if [[ ${arg_F:-} == 0 && "${SILENT:-False}" == "False" ]]; then - read -r -p "Press Enter to exit... " ignored_var 2>&1 - fi -} -trap __b3bp_cleanup_before_exit EXIT - -# requires `set -o errtrace` -__b3bp_err_report() { - local error_code - error_code=${?} - error "Error in ${__file} in function ${1} on line ${2}" - exit ${error_code} -} -# Uncomment the following line for always providing an error backtrace -trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR - - -### Command-line argument switches (like -d for debugmode, -h for showing helppage) -############################################################################## - -# debug mode -if [[ "${arg_d:?}" = "1" ]]; then - set -o xtrace - LOG_LEVEL="7" - # Enable error backtracing - trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR -fi - -# verbose mode -if [[ "${arg_v:?}" = "1" ]]; then - set -o verbose -fi - - -### Validation. Error out if the things required for your script are not present -############################################################################## - -if [[ "${arg_F:?}" == 1 ]]; then - SILENT="True" -else - SILENT="False" -fi -if [[ "${arg_M:?}" == 1 ]]; then - USE_MBR="True" -else - USE_MBR="False" -fi - -if [[ "${arg_h:?}" == 1 ]]; then - help "${__usage_example}" -else - # Print warning line - [[ "${arg_u:-}" ]] || echo " -u or --ufd-device is required" - [[ "${arg_l:-}" ]] || echo " -l or --linux-iso is required" - - # Bail if necessary - [[ "${arg_u:-}" ]] || help "${__usage_example}" - [[ "${arg_l:-}" ]] || help "${__usage_example}" -fi -[[ "${LOG_LEVEL:-}" ]] || emergency "Cannot continue without LOG_LEVEL. " - - -### More functions -############################################################################## - -function abort () { - local abort_message="Aborted" - [[ "${1:-}" ]] && abort_message="${1}" || true - error "${abort_message}" - #echo -e "${YELLOW}${abort_message}${CLEAR}" - exit 1 -} - -function ask() { - if [[ "${SILENT}" == "True" ]]; then - echo -e "${1:-} Yes ${BLUE}(Silent)${CLEAR}" - return 0 - fi - while :; do - read -p "${1:-} [Y/N] " -r answer - if echo "$answer" | egrep -iq '^(y|yes|sure)$'; then - return 0 - elif echo "$answer" | egrep -iq '^(n|no|nope)$'; then - return 1 - fi - done -} - - -### Runtime -############################################################################## - -# VARIABLES -DEST_DEV="${arg_u}" -DEST_PAR="${DEST_DEV}1" -LOG_FILE="$(getent passwd "$SUDO_USER" | cut -d: -f6)/Logs/build-ufd_${DEST_DEV##*/}_$(date +%Y-%m-%d_%H%M_%z).log" -MAIN_PY="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/settings/main.py" -RSYNC_ARGS="-hrtuvS --modify-window=1 --progress" -MAIN_KIT="$(realpath "${arg_m:-}" 2>/dev/null || true)" -LINUX_ISO="$(realpath "${arg_l:-}" 2>/dev/null || true)" -WINPE_ISO="$(realpath "${arg_w:-}" 2>/dev/null || true)" -EXTRA_DIR="$(realpath "${arg_e:-}" 2>/dev/null || true)" -mkdir -p "$(dirname "$LOG_FILE")" -chown "$SUDO_USER:$SUDO_USER" -R "$(dirname "$LOG_FILE")" - -# COLORS -CLEAR="\e[0m" -RED="\e[31m" -GREEN="\e[32m" -YELLOW="\e[33m" -BLUE="\e[34m" - -# Load main.py settings -if [ ! -f "${MAIN_PY}" ]; then - echo -e "${RED}ERROR${CLEAR}: ${MAIN_PY} not found." - abort -fi -while read line; do - if echo "${line}" | egrep -q "^\w+='"; then - line="$(echo "${line}" | sed -r 's/[\r\n]+//')" - eval "${line}" - fi -done < "${MAIN_PY}" -if [ -z ${KIT_NAME_FULL+x} ]; then - # KIT_NAME_FULL is not set, assume main.py missing or malformatted - echo -e "${RED}ERROR${CLEAR}: failed to load settings from ${MAIN_PY}" - abort -fi -ISO_LABEL="${KIT_NAME_SHORT}_LINUX" -UFD_LABEL="${KIT_NAME_SHORT}_UFD" - -# Check if root -if [[ "$EUID" -ne 0 ]]; then - echo -e "${RED}ERROR${CLEAR}: This script must be run as root." - abort -fi - -# Check if in tmux -if ! tmux list-session 2>/dev/null | grep -q "build-ufd"; then - # Reload in tmux - eval tmux new-session -s "build-ufd" "${0:-}" ${__all_args} - SILENT="True" # avoid two "Press Enter to exit..." prompts - exit 0 -fi - -# Header -echo -e "${GREEN}${KIT_NAME_FULL}${CLEAR}: UFD Build Tool" -echo "" - -# Verify sources -[[ -b "${DEST_DEV}" ]] || abort "${DEST_DEV} is not a valid device." -[[ -e "${LINUX_ISO}" ]] || abort "Linux ISO not found." -if [[ ! -z "${arg_m:-}" ]]; then - [[ -d "${MAIN_KIT}/.bin" ]] || abort "Invalid Main Kit, ${MAIN_KIT}/.bin not found." -fi -if [[ ! -z "${arg_w:-}" ]]; then - [[ -e "${WINPE_ISO}" ]] || abort "WinPE ISO not found." -fi -if [[ ! -z "${arg_e:-}" ]]; then - [[ -d "${EXTRA_DIR}" ]] || abort "Extra Dir not found." -fi - -# Print Info -echo -e "${BLUE}Sources${CLEAR}" | tee -a "${LOG_FILE}" -echo "Main Kit: ${MAIN_KIT}" | tee -a "${LOG_FILE}" -echo "Linux ISO: ${LINUX_ISO}" | tee -a "${LOG_FILE}" -echo "WinPE ISO: ${WINPE_ISO}" | tee -a "${LOG_FILE}" -echo "Extra Dir: ${EXTRA_DIR:-(Not Specified)}" | tee -a "${LOG_FILE}" -echo "" | tee -a "${LOG_FILE}" -echo -e "${BLUE}Destination${CLEAR}" | tee -a "${LOG_FILE}" -lsblk -n -o NAME,LABEL,SIZE,MODEL,SERIAL "${DEST_DEV}" | tee -a "${LOG_FILE}" -if [[ "${USE_MBR}" == "True" ]]; then - echo -e "${YELLOW}Formatting using legacy MBR${CLEAR}" | tee -a "${LOG_FILE}" -fi -echo "" | tee -a "${LOG_FILE}" - -# Ask before starting job -echo "" -if ask "Is the above information correct?"; then - echo "" - echo -e "${YELLOW}SAFETY CHECK${CLEAR}" - echo "All data will be DELETED from the disk and partition(s) listed above." - echo -e "This is irreversible and will lead to ${RED}DATA LOSS.${CLEAR}" - if ! ask "Asking again to confirm, is this correct?"; then - abort - fi -else - abort -fi - -# Start Build -echo "" | tee -a "${LOG_FILE}" -echo -e "${GREEN}Building Kit${CLEAR}" | tee -a "${LOG_FILE}" -touch "${LOG_FILE}" -tmux split-window -dl 10 tail -f "${LOG_FILE}" - -# Zero beginning of device -dd bs=4M count=16 if=/dev/zero of="${DEST_DEV}" >> "${LOG_FILE}" 2>&1 - -# Format -echo "Formatting drive..." | tee -a "${LOG_FILE}" -if [[ "${USE_MBR}" == "True" ]]; then - parted "${DEST_DEV}" --script -- mklabel msdos mkpart primary fat32 4MiB -1s >> "${LOG_FILE}" 2>&1 - parted "${DEST_DEV}" set 1 boot on >> "${LOG_FILE}" 2>&1 -else - parted "${DEST_DEV}" --script -- mklabel gpt mkpart primary fat32 4MiB -4MiB >> "${LOG_FILE}" 2>&1 - parted "${DEST_DEV}" set 1 legacy_boot on >> "${LOG_FILE}" 2>&1 - #parted "${DEST_DEV}" disk_set pmbr_boot on >> "${LOG_FILE}" 2>&1 - # pmbr_boot breaks detection on some UEFI MOBOs -fi -mkfs.vfat -F 32 -n "${UFD_LABEL}" "${DEST_PAR}" >> "${LOG_FILE}" 2>&1 - -# Mount sources and dest -echo "Mounting sources and destination..." | tee -a "${LOG_FILE}" -mkdir /mnt/{Dest,Linux,WinPE} -p >> "${LOG_FILE}" 2>&1 -mount ${DEST_PAR} /mnt/Dest >> "${LOG_FILE}" 2>&1 -mount "${LINUX_ISO}" /mnt/Linux -r >> "${LOG_FILE}" 2>&1 -if [[ ! -z "${arg_w:-}" ]]; then - mount "${WINPE_ISO}" /mnt/WinPE -r >> "${LOG_FILE}" 2>&1 -fi - -# Find WinPE source -w_boot="$(find /mnt/WinPE -iwholename "/mnt/WinPE/Boot")" -w_boot_bcd="$(find /mnt/WinPE -iwholename "/mnt/WinPE/Boot/BCD")" -w_boot_sdi="$(find /mnt/WinPE -iwholename "/mnt/WinPE/Boot/boot.sdi")" -w_bootmgr="$(find /mnt/WinPE -iwholename "/mnt/WinPE/bootmgr")" -w_bootmgr_efi="$(find /mnt/WinPE -iwholename "/mnt/WinPE/bootmgr.efi")" -w_efi_boot="$(find /mnt/WinPE -iwholename "/mnt/WinPE/EFI/Boot")" -w_efi_microsoft="$(find /mnt/WinPE -iwholename "/mnt/WinPE/EFI/Microsoft")" -w_en_us="$(find /mnt/WinPE -iwholename "/mnt/WinPE/en-us")" -w_sources="$(find /mnt/WinPE -iwholename "/mnt/WinPE/sources")" - -# Copy files -echo "Copying Linux files..." | tee -a "${LOG_FILE}" -rsync ${RSYNC_ARGS} /mnt/Linux/* /mnt/Dest/ >> "${LOG_FILE}" 2>&1 -sed -i "s/${ISO_LABEL}/${UFD_LABEL}/" /mnt/Dest/EFI/boot/refind.conf -sed -i "s/${ISO_LABEL}/${UFD_LABEL}/" /mnt/Dest/arch/boot/syslinux/*cfg - -echo "Copying WinPE files..." | tee -a "${LOG_FILE}" -if [[ ! -z "${arg_w:-}" ]]; then - if [[ ! -z "${w_bootmgr:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_bootmgr}" /mnt/Dest/ >> "${LOG_FILE}" 2>&1 - fi - if [[ ! -z "${w_bootmgr_efi:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_bootmgr_efi}" /mnt/Dest/ >> "${LOG_FILE}" 2>&1 - fi - if [[ ! -z "${w_en_us:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_en_us}" /mnt/Dest/ >> "${LOG_FILE}" 2>&1 - fi - if [[ ! -z "${w_boot:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_boot}"/* /mnt/Dest/Boot/ >> "${LOG_FILE}" 2>&1 - fi - if [[ ! -z "${w_efi_boot:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_efi_boot}"/* /mnt/Dest/EFI/Microsoft/ >> "${LOG_FILE}" 2>&1 - fi - if [[ ! -z "${w_efi_microsoft:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_efi_microsoft}"/* /mnt/Dest/EFI/Microsoft/ >> "${LOG_FILE}" 2>&1 - fi - if [[ ! -z "${w_boot_bcd:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_boot_bcd}" /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1 - fi - if [[ ! -z "${w_boot_sdi:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_boot_sdi}" /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1 - fi - if [[ ! -z "${w_bootmgr:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_bootmgr}" /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1 - fi - if [[ ! -z "${w_sources:-}" ]]; then - rsync ${RSYNC_ARGS} "${w_sources}"/* /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1 - fi - - # Uncomment boot entries - sed -i "s/#UFD-WINPE#//" /mnt/Dest/EFI/boot/refind.conf - sed -i "s/#UFD-WINPE#//" /mnt/Dest/arch/boot/syslinux/*cfg -fi - -echo "Copying Main Kit..." | tee -a "${LOG_FILE}" -if [[ ! -z "${arg_m:-}" ]]; then - rsync ${RSYNC_ARGS} \ - "${MAIN_KIT}/" \ - "/mnt/Dest/${KIT_NAME_FULL}/" >> "${LOG_FILE}" 2>&1 -fi - -if [[ ! -z "${EXTRA_DIR:-}" ]]; then - echo "Copying Extra files..." | tee -a "${LOG_FILE}" - rsync ${RSYNC_ARGS} \ - "${EXTRA_DIR}"/ \ - /mnt/Dest/ >> "${LOG_FILE}" 2>&1 -fi - -# Install syslinux -echo "Copying Syslinux files..." | tee -a "${LOG_FILE}" -rsync ${RSYNC_ARGS} /usr/lib/syslinux/bios/*.c32 /mnt/Dest/arch/boot/syslinux/ >> "${LOG_FILE}" 2>&1 -syslinux --install -d /arch/boot/syslinux/ ${DEST_PAR} >> "${LOG_FILE}" 2>&1 - -echo "Unmounting destination..." | tee -a "${LOG_FILE}" -umount /mnt/Dest >> "${LOG_FILE}" 2>&1 -rmdir /mnt/Dest >> "${LOG_FILE}" 2>&1 -sync - -echo "Installing Syslinux MBR..." | tee -a "${LOG_FILE}" -if [[ "${USE_MBR}" == "True" ]]; then - dd bs=440 count=1 if=/usr/lib/syslinux/bios/mbr.bin of=${DEST_DEV} >> "${LOG_FILE}" 2>&1 -else - dd bs=440 count=1 if=/usr/lib/syslinux/bios/gptmbr.bin of=${DEST_DEV} >> "${LOG_FILE}" 2>&1 -fi -sync - -# Cleanup -echo "Hiding boot files..." | tee -a "${LOG_FILE}" -echo "drive s: file=\"${DEST_PAR}\"" > /root/.mtoolsrc -echo 'mtools_skip_check=1' >> /root/.mtoolsrc -for item in arch Boot bootmgr{,.efi} EFI en-us images isolinux sources "${KIT_NAME_FULL}"/{.bin,.cbin}; do - yes | mattrib +h "S:/${item}" >> "${LOG_FILE}" 2>&1 || true -done -sync - -# Unmount Sources -echo "Unmounting sources..." | tee -a "${LOG_FILE}" -for d in Linux WinPE; do - umount "/mnt/${d}" >> "${LOG_FILE}" 2>&1 || true - rmdir "/mnt/${d}" >> "${LOG_FILE}" 2>&1 || true -done - -# Close progress pane -pkill -f "tail.*${LOG_FILE}" - -# Done -echo "" | tee -a "${LOG_FILE}" -echo "Done." | tee -a "${LOG_FILE}" -echo "" -exit 0 +# pylint: disable=no-name-in-module,wildcard-import +# vim: sts=2 sw=2 ts=2 +"""Wizard Kit: UFD build tool""" + +import os +import sys + +# Init +sys.path.append(os.path.dirname(os.path.realpath(__file__))) +from docopt import docopt +from functions.common import * +from functions.ufd import * +from settings.ufd import * +init_global_vars(silent=True) + +# Main section +if __name__ == '__main__': + # pylint: disable=invalid-name + # Set log + try: + global_vars['LogDir'] = '{}/Logs'.format( + get_user_home(get_user_name())) + set_log_file('Build UFD ({Date-Time}).log'.format(**global_vars)) + except: # pylint: disable=bare-except + major_exception() + + # Header + print_success(KIT_NAME_FULL) + print_standard('UFD Build Tool') + print_standard(' ') + + # Check if running as root + if not running_as_root(): + print_error('ERROR: This script is meant to be run as root.') + abort(False) + + # Docopt + try: + args = docopt(DOCSTRING) + except SystemExit as sys_exit: + # Catch docopt exits + exit_script(sys_exit.code) + except: # pylint: disable=bare-except + major_exception() + + try: + # Verify selections + ufd_dev = verify_ufd(args['--ufd-device']) + sources = verify_sources(args, UFD_SOURCES) + show_selections(args, sources, ufd_dev, UFD_SOURCES) + if not args['--force']: + confirm_selections(args) + + # Prep UFD + print_info('Prep UFD') + if args['--update']: + # Remove arch folder + try_and_print( + indent=2, + message='Removing Linux...', + function=remove_arch, + ) + else: + # Format and partition + prep_device(ufd_dev, UFD_LABEL, use_mbr=args['--use-mbr']) + + # Mount UFD + try_and_print( + indent=2, + message='Mounting UFD...', + function=mount, + mount_source=find_first_partition(ufd_dev), + mount_point='/mnt/UFD', + ) + + # Copy sources + print_standard(' ') + print_info('Copy Sources') + for s_label, s_path in sources.items(): + try_and_print( + indent=2, + message='Copying {}...'.format(s_label), + function=copy_source, + source=s_path, + items=ITEMS[s_label], + overwrite=True, + ) + + # Update boot entries + print_standard(' ') + print_info('Boot Setup') + try_and_print( + indent=2, + message='Updating boot entries...', + function=update_boot_entries, + boot_entries=BOOT_ENTRIES, + boot_files=BOOT_FILES, + iso_label=ISO_LABEL, + ufd_label=UFD_LABEL, + ) + + # Install syslinux (to partition) + try_and_print( + indent=2, + message='Syslinux (partition)...', + function=install_syslinux_to_partition, + partition=find_first_partition(ufd_dev), + ) + + # Unmount UFD + try_and_print( + indent=2, + message='Unmounting UFD...', + function=unmount, + mount_point='/mnt/UFD', + ) + + # Install syslinux (to device) + try_and_print( + indent=2, + message='Syslinux (device)...', + function=install_syslinux_to_dev, + ufd_dev=ufd_dev, + use_mbr=args['--use-mbr'], + ) + + # Hide items + print_standard(' ') + print_info('Final Touches') + try_and_print( + indent=2, + message='Hiding items...', + function=hide_items, + ufd_dev=ufd_dev, + items=ITEMS_HIDDEN, + ) + + # Done + if not args['--force']: + print_standard('\nDone.') + pause('Press Enter to exit...') + exit_script() + except SystemExit as sys_exit: + exit_script(sys_exit.code) + except: # pylint: disable=bare-except + major_exception() diff --git a/.bin/Scripts/build-ufd.bash b/.bin/Scripts/build-ufd.bash new file mode 100755 index 00000000..5f272d7d --- /dev/null +++ b/.bin/Scripts/build-ufd.bash @@ -0,0 +1,671 @@ +#!/usr/bin/env bash +# +## Wizard Kit: UFD Build Tool +# +# Based on a template by BASH3 Boilerplate v2.3.0 +# http://bash3boilerplate.sh/#authors +# +# The MIT License (MIT) +# Copyright (c) 2013 Kevin van Zonneveld and contributors +# You are not obligated to bundle the LICENSE file with your b3bp projects as long +# as you leave these references intact in the header comments of your source files. + +# Exit on error. Append "|| true" if you expect an error. +set -o errexit +# Exit on error inside any functions or subshells. +set -o errtrace +# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR +set -o nounset +# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip` +set -o pipefail +# Turn on traces, useful while debugging but commented out by default +# set -o xtrace + +if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + __i_am_main_script="0" # false + + if [[ "${__usage+x}" ]]; then + if [[ "${BASH_SOURCE[1]}" = "${0}" ]]; then + __i_am_main_script="1" # true + fi + + __b3bp_external_usage="true" + __b3bp_tmp_source_idx=1 + fi +else + __i_am_main_script="1" # true + [[ "${__usage+x}" ]] && unset -v __usage + [[ "${__helptext+x}" ]] && unset -v __helptext +fi + +# Set magic variables for current file, directory, os, etc. +__dir="$(cd "$(dirname "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" && pwd)" +__file="${__dir}/$(basename "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" +__base="$(basename "${__file}" .sh)" +__wd="$(pwd)" +__usage_example="Usage: sudo $(basename "${0}") --ufd-device [device] --linux-iso [path] --main-kit [path] --winpe-iso [path]" +__all_args="" +for a in "${@}"; do + if [[ "${a:0:1}" == "-" ]]; then + __all_args="${__all_args} ${a}" + else + __all_args="${__all_args} \"${a}\"" + fi +done + + +# Define the environment variables (and their defaults) that this script depends on +LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency +NO_COLOR="${NO_COLOR:-}" # true = disable color. otherwise autodetected + + +### Functions +############################################################################## + +function __b3bp_log () { + local log_level="${1}" + shift + + # shellcheck disable=SC2034 + local color_debug="\x1b[35m" + # shellcheck disable=SC2034 + local color_info="\x1b[32m" + # shellcheck disable=SC2034 + local color_notice="\x1b[34m" + # shellcheck disable=SC2034 + local color_warning="\x1b[33m" + # shellcheck disable=SC2034 + local color_error="\x1b[31m" + # shellcheck disable=SC2034 + local color_critical="\x1b[1;31m" + # shellcheck disable=SC2034 + local color_alert="\x1b[1;33;41m" + # shellcheck disable=SC2034 + local color_emergency="\x1b[1;4;5;33;41m" + + local colorvar="color_${log_level}" + + local color="${!colorvar:-${color_error}}" + local color_reset="\x1b[0m" + + if [[ "${NO_COLOR:-}" = "true" ]] || ( [[ "${TERM:-}" != *"256color"* ]] && [[ "${TERM:-}" != "xterm"* ]] && [[ "${TERM:-}" != "screen"* ]] ) || [[ ! -t 2 ]]; then + if [[ "${NO_COLOR:-}" != "false" ]]; then + # Don't use colors on pipes or non-recognized terminals + color=""; color_reset="" + fi + fi + + # all remaining arguments are to be printed + local log_line="" + + while IFS=$'\n' read -r log_line; do + echo -e "$(date -u +"%Y-%m-%d %H:%M:%S UTC") ${color}$(printf "[%9s]" "${log_level}")${color_reset} ${log_line}" 1>&2 + done <<< "${@:-}" +} + +function emergency () { __b3bp_log emergency "${@}"; exit 1; } +function alert () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && __b3bp_log alert "${@}"; true; } +function critical () { [[ "${LOG_LEVEL:-0}" -ge 2 ]] && __b3bp_log critical "${@}"; true; } +function error () { [[ "${LOG_LEVEL:-0}" -ge 3 ]] && __b3bp_log error "${@}"; true; } +function warning () { [[ "${LOG_LEVEL:-0}" -ge 4 ]] && __b3bp_log warning "${@}"; true; } +function notice () { [[ "${LOG_LEVEL:-0}" -ge 5 ]] && __b3bp_log notice "${@}"; true; } +function info () { [[ "${LOG_LEVEL:-0}" -ge 6 ]] && __b3bp_log info "${@}"; true; } +function debug () { [[ "${LOG_LEVEL:-0}" -ge 7 ]] && __b3bp_log debug "${@}"; true; } + +function help () { + echo "" 1>&2 + echo " ${*}" 1>&2 + echo "" 1>&2 + echo " ${__usage:-No usage available}" 1>&2 + echo "" 1>&2 + + if [[ "${__helptext:-}" ]]; then + echo " ${__helptext}" 1>&2 + echo "" 1>&2 + fi + + exit 1 +} + + +### Parse commandline options +############################################################################## + +# Commandline options. This defines the usage page, and is used to parse cli +# opts & defaults from. The parsing is unforgiving so be precise in your syntax +# - A short option must be preset for every long option; but every short option +# need not have a long option +# - `--` is respected as the separator between options and arguments +# - We do not bash-expand defaults, so setting '~/app' as a default will not resolve to ${HOME}. +# you can use bash variables to work around this (so use ${HOME} instead) + +# shellcheck disable=SC2015 +[[ "${__usage+x}" ]] || read -r -d '' __usage <<-'EOF' || true # exits non-zero when EOF encountered + OPTIONS: + -u --ufd-device [arg] Device to which the kit will be applied + -l --linux-iso [arg] Path to the Linux ISO + + -e --extra-dir [arg] Path to the Extra folder (optional) + -m --main-kit [arg] Path to the Main Kit (optional) + -w --winpe-iso [arg] Path to the WinPE ISO (optional) + -h --help This page + + ADVANCED: + -d --debug Enable debug mode + -v --verbose Enable verbose mode + -M --use-mbr Use real MBR instead of GPT w/ Protective MBR + -F --force Bypass all confirmation messages. USE WITH EXTREME CAUTION! +EOF + +# shellcheck disable=SC2015 +[[ "${__helptext+x}" ]] || read -r -d '' __helptext <<-'EOF' || true # exits non-zero when EOF encountered + Paths can be relative to the current working directory or absolute +EOF + +# Translate usage string -> getopts arguments, and set $arg_ defaults +while read -r __b3bp_tmp_line; do + if [[ "${__b3bp_tmp_line}" =~ ^- ]]; then + # fetch single character version of option string + __b3bp_tmp_opt="${__b3bp_tmp_line%% *}" + __b3bp_tmp_opt="${__b3bp_tmp_opt:1}" + + # fetch long version if present + __b3bp_tmp_long_opt="" + + if [[ "${__b3bp_tmp_line}" = *"--"* ]]; then + __b3bp_tmp_long_opt="${__b3bp_tmp_line#*--}" + __b3bp_tmp_long_opt="${__b3bp_tmp_long_opt%% *}" + fi + + # map opt long name to+from opt short name + printf -v "__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" '%s' "${__b3bp_tmp_opt}" + printf -v "__b3bp_tmp_opt_short2long_${__b3bp_tmp_opt}" '%s' "${__b3bp_tmp_long_opt//-/_}" + + # check if option takes an argument + if [[ "${__b3bp_tmp_line}" =~ \[.*\] ]]; then + __b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg + __b3bp_tmp_init="" # it has an arg. init with "" + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "1" + elif [[ "${__b3bp_tmp_line}" =~ \{.*\} ]]; then + __b3bp_tmp_opt="${__b3bp_tmp_opt}:" # add : if opt has arg + __b3bp_tmp_init="" # it has an arg. init with "" + # remember that this option requires an argument + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2" + else + __b3bp_tmp_init="0" # it's a flag. init with 0 + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "0" + fi + __b3bp_tmp_opts="${__b3bp_tmp_opts:-}${__b3bp_tmp_opt}" + fi + + [[ "${__b3bp_tmp_opt:-}" ]] || continue + + if [[ "${__b3bp_tmp_line}" =~ (^|\.\ *)Default= ]]; then + # ignore default value if option does not have an argument + __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" + + if [[ "${!__b3bp_tmp_varname}" != "0" ]]; then + __b3bp_tmp_init="${__b3bp_tmp_line##*Default=}" + __b3bp_tmp_re='^"(.*)"$' + if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then + __b3bp_tmp_init="${BASH_REMATCH[1]}" + else + __b3bp_tmp_re="^'(.*)'$" + if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then + __b3bp_tmp_init="${BASH_REMATCH[1]}" + fi + fi + fi + fi + + if [[ "${__b3bp_tmp_line}" =~ (^|\.\ *)Required\. ]]; then + # remember that this option requires an argument + printf -v "__b3bp_tmp_has_arg_${__b3bp_tmp_opt:0:1}" '%s' "2" + fi + + printf -v "arg_${__b3bp_tmp_opt:0:1}" '%s' "${__b3bp_tmp_init}" +done <<< "${__usage:-}" + +# run getopts only if options were specified in __usage +if [[ "${__b3bp_tmp_opts:-}" ]]; then + # Allow long options like --this + __b3bp_tmp_opts="${__b3bp_tmp_opts}-:" + + # Reset in case getopts has been used previously in the shell. + OPTIND=1 + + # start parsing command line + set +o nounset # unexpected arguments will cause unbound variables + # to be dereferenced + # Overwrite $arg_ defaults with the actual CLI options + while getopts "${__b3bp_tmp_opts}" __b3bp_tmp_opt; do + [[ "${__b3bp_tmp_opt}" = "?" ]] && help "Invalid use of script: ${*} " + + if [[ "${__b3bp_tmp_opt}" = "-" ]]; then + # OPTARG is long-option-name or long-option=value + if [[ "${OPTARG}" =~ .*=.* ]]; then + # --key=value format + __b3bp_tmp_long_opt=${OPTARG/=*/} + # Set opt to the short option corresponding to the long option + __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" + printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" + OPTARG=${OPTARG#*=} + else + # --key value format + # Map long name to short version of option + __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${OPTARG//-/_}" + printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" + # Only assign OPTARG if option takes an argument + __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt}" + printf -v "OPTARG" '%s' "${@:OPTIND:${!__b3bp_tmp_varname}}" + # shift over the argument if argument is expected + ((OPTIND+=__b3bp_tmp_has_arg_${__b3bp_tmp_opt})) + fi + # we have set opt/OPTARG to the short value and the argument as OPTARG if it exists + fi + __b3bp_tmp_varname="arg_${__b3bp_tmp_opt:0:1}" + __b3bp_tmp_default="${!__b3bp_tmp_varname}" + + __b3bp_tmp_value="${OPTARG}" + if [[ -z "${OPTARG}" ]] && [[ "${__b3bp_tmp_default}" = "0" ]]; then + __b3bp_tmp_value="1" + fi + + printf -v "${__b3bp_tmp_varname}" '%s' "${__b3bp_tmp_value}" + debug "cli arg ${__b3bp_tmp_varname} = (${__b3bp_tmp_default}) -> ${!__b3bp_tmp_varname}" + done + set -o nounset # no more unbound variable references expected + + shift $((OPTIND-1)) + + if [[ "${1:-}" = "--" ]] ; then + shift + fi +fi + + +### Automatic validation of required option arguments +############################################################################## + +for __b3bp_tmp_varname in ${!__b3bp_tmp_has_arg_*}; do + # validate only options which required an argument + [[ "${!__b3bp_tmp_varname}" = "2" ]] || continue + + __b3bp_tmp_opt_short="${__b3bp_tmp_varname##*_}" + __b3bp_tmp_varname="arg_${__b3bp_tmp_opt_short}" + [[ "${!__b3bp_tmp_varname}" ]] && continue + + __b3bp_tmp_varname="__b3bp_tmp_opt_short2long_${__b3bp_tmp_opt_short}" + printf -v "__b3bp_tmp_opt_long" '%s' "${!__b3bp_tmp_varname}" + [[ "${__b3bp_tmp_opt_long:-}" ]] && __b3bp_tmp_opt_long=" (--${__b3bp_tmp_opt_long//_/-})" + + help "Option -${__b3bp_tmp_opt_short}${__b3bp_tmp_opt_long:-} requires an argument" +done + + +### Cleanup Environment variables +############################################################################## + +for __tmp_varname in ${!__b3bp_tmp_*}; do + unset -v "${__tmp_varname}" +done + +unset -v __tmp_varname + + +### Externally supplied __usage. Nothing else to do here +############################################################################## + +if [[ "${__b3bp_external_usage:-}" = "true" ]]; then + unset -v __b3bp_external_usage + return +fi + + +### Signal trapping and backtracing +############################################################################## + +function __b3bp_cleanup_before_exit () { + if [[ "$EUID" -eq 0 ]]; then + for d in Dest Linux WinPE; do + if [[ -d "/mnt/${d}" ]]; then + umount "/mnt/${d}" || true + rmdir "/mnt/${d}" || true + fi + done + fi + if [[ "${?}" != "0" ]]; then + info "Sources unmounted" + fi + if [[ ${arg_F:-} == 0 && "${SILENT:-False}" == "False" ]]; then + read -r -p "Press Enter to exit... " ignored_var 2>&1 + fi +} +trap __b3bp_cleanup_before_exit EXIT + +# requires `set -o errtrace` +__b3bp_err_report() { + local error_code + error_code=${?} + error "Error in ${__file} in function ${1} on line ${2}" + exit ${error_code} +} +# Uncomment the following line for always providing an error backtrace +trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR + + +### Command-line argument switches (like -d for debugmode, -h for showing helppage) +############################################################################## + +# debug mode +if [[ "${arg_d:?}" = "1" ]]; then + set -o xtrace + LOG_LEVEL="7" + # Enable error backtracing + trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR +fi + +# verbose mode +if [[ "${arg_v:?}" = "1" ]]; then + set -o verbose +fi + + +### Validation. Error out if the things required for your script are not present +############################################################################## + +if [[ "${arg_F:?}" == 1 ]]; then + SILENT="True" +else + SILENT="False" +fi +if [[ "${arg_M:?}" == 1 ]]; then + USE_MBR="True" +else + USE_MBR="False" +fi + +if [[ "${arg_h:?}" == 1 ]]; then + help "${__usage_example}" +else + # Print warning line + [[ "${arg_u:-}" ]] || echo " -u or --ufd-device is required" + [[ "${arg_l:-}" ]] || echo " -l or --linux-iso is required" + + # Bail if necessary + [[ "${arg_u:-}" ]] || help "${__usage_example}" + [[ "${arg_l:-}" ]] || help "${__usage_example}" +fi +[[ "${LOG_LEVEL:-}" ]] || emergency "Cannot continue without LOG_LEVEL. " + + +### More functions +############################################################################## + +function abort () { + local abort_message="Aborted" + [[ "${1:-}" ]] && abort_message="${1}" || true + error "${abort_message}" + #echo -e "${YELLOW}${abort_message}${CLEAR}" + exit 1 +} + +function ask() { + if [[ "${SILENT}" == "True" ]]; then + echo -e "${1:-} Yes ${BLUE}(Silent)${CLEAR}" + return 0 + fi + while :; do + read -p "${1:-} [Y/N] " -r answer + if echo "$answer" | egrep -iq '^(y|yes|sure)$'; then + return 0 + elif echo "$answer" | egrep -iq '^(n|no|nope)$'; then + return 1 + fi + done +} + + +### Runtime +############################################################################## + +# VARIABLES +DEST_DEV="${arg_u}" +DEST_PAR="${DEST_DEV}1" +LOG_FILE="$(getent passwd "$SUDO_USER" | cut -d: -f6)/Logs/build-ufd_${DEST_DEV##*/}_$(date +%Y-%m-%d_%H%M_%z).log" +MAIN_PY="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/settings/main.py" +RSYNC_ARGS="-hrtuvS --modify-window=1 --progress" +MAIN_KIT="$(realpath "${arg_m:-}" 2>/dev/null || true)" +LINUX_ISO="$(realpath "${arg_l:-}" 2>/dev/null || true)" +WINPE_ISO="$(realpath "${arg_w:-}" 2>/dev/null || true)" +EXTRA_DIR="$(realpath "${arg_e:-}" 2>/dev/null || true)" +mkdir -p "$(dirname "$LOG_FILE")" +chown "$SUDO_USER:$SUDO_USER" -R "$(dirname "$LOG_FILE")" + +# COLORS +CLEAR="\e[0m" +RED="\e[31m" +GREEN="\e[32m" +YELLOW="\e[33m" +BLUE="\e[34m" + +# Load main.py settings +if [ ! -f "${MAIN_PY}" ]; then + echo -e "${RED}ERROR${CLEAR}: ${MAIN_PY} not found." + abort +fi +while read line; do + if echo "${line}" | egrep -q "^\w+='"; then + line="$(echo "${line}" | sed -r 's/[\r\n]+//')" + eval "${line}" + fi +done < "${MAIN_PY}" +if [ -z ${KIT_NAME_FULL+x} ]; then + # KIT_NAME_FULL is not set, assume main.py missing or malformatted + echo -e "${RED}ERROR${CLEAR}: failed to load settings from ${MAIN_PY}" + abort +fi +ISO_LABEL="${KIT_NAME_SHORT}_LINUX" +UFD_LABEL="${KIT_NAME_SHORT}_UFD" + +# Check if root +if [[ "$EUID" -ne 0 ]]; then + echo -e "${RED}ERROR${CLEAR}: This script must be run as root." + abort +fi + +# Check if in tmux +if ! tmux list-session 2>/dev/null | grep -q "build-ufd"; then + # Reload in tmux + eval tmux new-session -s "build-ufd" "${0:-}" ${__all_args} + SILENT="True" # avoid two "Press Enter to exit..." prompts + exit 0 +fi + +# Header +echo -e "${GREEN}${KIT_NAME_FULL}${CLEAR}: UFD Build Tool" +echo "" + +# Verify sources +[[ -b "${DEST_DEV}" ]] || abort "${DEST_DEV} is not a valid device." +[[ -e "${LINUX_ISO}" ]] || abort "Linux ISO not found." +if [[ ! -z "${arg_m:-}" ]]; then + [[ -d "${MAIN_KIT}/.bin" ]] || abort "Invalid Main Kit, ${MAIN_KIT}/.bin not found." +fi +if [[ ! -z "${arg_w:-}" ]]; then + [[ -e "${WINPE_ISO}" ]] || abort "WinPE ISO not found." +fi +if [[ ! -z "${arg_e:-}" ]]; then + [[ -d "${EXTRA_DIR}" ]] || abort "Extra Dir not found." +fi + +# Print Info +echo -e "${BLUE}Sources${CLEAR}" | tee -a "${LOG_FILE}" +echo "Main Kit: ${MAIN_KIT}" | tee -a "${LOG_FILE}" +echo "Linux ISO: ${LINUX_ISO}" | tee -a "${LOG_FILE}" +echo "WinPE ISO: ${WINPE_ISO}" | tee -a "${LOG_FILE}" +echo "Extra Dir: ${EXTRA_DIR:-(Not Specified)}" | tee -a "${LOG_FILE}" +echo "" | tee -a "${LOG_FILE}" +echo -e "${BLUE}Destination${CLEAR}" | tee -a "${LOG_FILE}" +lsblk -n -o NAME,LABEL,SIZE,MODEL,SERIAL "${DEST_DEV}" | tee -a "${LOG_FILE}" +if [[ "${USE_MBR}" == "True" ]]; then + echo -e "${YELLOW}Formatting using legacy MBR${CLEAR}" | tee -a "${LOG_FILE}" +fi +echo "" | tee -a "${LOG_FILE}" + +# Ask before starting job +echo "" +if ask "Is the above information correct?"; then + echo "" + echo -e "${YELLOW}SAFETY CHECK${CLEAR}" + echo "All data will be DELETED from the disk and partition(s) listed above." + echo -e "This is irreversible and will lead to ${RED}DATA LOSS.${CLEAR}" + if ! ask "Asking again to confirm, is this correct?"; then + abort + fi +else + abort +fi + +# Start Build +echo "" | tee -a "${LOG_FILE}" +echo -e "${GREEN}Building Kit${CLEAR}" | tee -a "${LOG_FILE}" +touch "${LOG_FILE}" +tmux split-window -dl 10 tail -f "${LOG_FILE}" + +# Zero beginning of device +dd bs=4M count=16 if=/dev/zero of="${DEST_DEV}" >> "${LOG_FILE}" 2>&1 + +# Format +echo "Formatting drive..." | tee -a "${LOG_FILE}" +if [[ "${USE_MBR}" == "True" ]]; then + parted "${DEST_DEV}" --script -- mklabel msdos mkpart primary fat32 4MiB -1s >> "${LOG_FILE}" 2>&1 + parted "${DEST_DEV}" set 1 boot on >> "${LOG_FILE}" 2>&1 +else + parted "${DEST_DEV}" --script -- mklabel gpt mkpart primary fat32 4MiB -4MiB >> "${LOG_FILE}" 2>&1 + parted "${DEST_DEV}" set 1 legacy_boot on >> "${LOG_FILE}" 2>&1 + #parted "${DEST_DEV}" disk_set pmbr_boot on >> "${LOG_FILE}" 2>&1 + # pmbr_boot breaks detection on some UEFI MOBOs +fi +mkfs.vfat -F 32 -n "${UFD_LABEL}" "${DEST_PAR}" >> "${LOG_FILE}" 2>&1 + +# Mount sources and dest +echo "Mounting sources and destination..." | tee -a "${LOG_FILE}" +mkdir /mnt/{Dest,Linux,WinPE} -p >> "${LOG_FILE}" 2>&1 +mount ${DEST_PAR} /mnt/Dest >> "${LOG_FILE}" 2>&1 +mount "${LINUX_ISO}" /mnt/Linux -r >> "${LOG_FILE}" 2>&1 +if [[ ! -z "${arg_w:-}" ]]; then + mount "${WINPE_ISO}" /mnt/WinPE -r >> "${LOG_FILE}" 2>&1 +fi + +# Find WinPE source +w_boot="$(find /mnt/WinPE -iwholename "/mnt/WinPE/Boot")" +w_boot_bcd="$(find /mnt/WinPE -iwholename "/mnt/WinPE/Boot/BCD")" +w_boot_sdi="$(find /mnt/WinPE -iwholename "/mnt/WinPE/Boot/boot.sdi")" +w_bootmgr="$(find /mnt/WinPE -iwholename "/mnt/WinPE/bootmgr")" +w_bootmgr_efi="$(find /mnt/WinPE -iwholename "/mnt/WinPE/bootmgr.efi")" +w_efi_boot="$(find /mnt/WinPE -iwholename "/mnt/WinPE/EFI/Boot")" +w_efi_microsoft="$(find /mnt/WinPE -iwholename "/mnt/WinPE/EFI/Microsoft")" +w_en_us="$(find /mnt/WinPE -iwholename "/mnt/WinPE/en-us")" +w_sources="$(find /mnt/WinPE -iwholename "/mnt/WinPE/sources")" + +# Copy files +echo "Copying Linux files..." | tee -a "${LOG_FILE}" +rsync ${RSYNC_ARGS} /mnt/Linux/* /mnt/Dest/ >> "${LOG_FILE}" 2>&1 +sed -i "s/${ISO_LABEL}/${UFD_LABEL}/" /mnt/Dest/EFI/boot/refind.conf +sed -i "s/${ISO_LABEL}/${UFD_LABEL}/" /mnt/Dest/arch/boot/syslinux/*cfg + +echo "Copying WinPE files..." | tee -a "${LOG_FILE}" +if [[ ! -z "${arg_w:-}" ]]; then + if [[ ! -z "${w_bootmgr:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_bootmgr}" /mnt/Dest/ >> "${LOG_FILE}" 2>&1 + fi + if [[ ! -z "${w_bootmgr_efi:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_bootmgr_efi}" /mnt/Dest/ >> "${LOG_FILE}" 2>&1 + fi + if [[ ! -z "${w_en_us:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_en_us}" /mnt/Dest/ >> "${LOG_FILE}" 2>&1 + fi + if [[ ! -z "${w_boot:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_boot}"/* /mnt/Dest/Boot/ >> "${LOG_FILE}" 2>&1 + fi + if [[ ! -z "${w_efi_boot:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_efi_boot}"/* /mnt/Dest/EFI/Microsoft/ >> "${LOG_FILE}" 2>&1 + fi + if [[ ! -z "${w_efi_microsoft:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_efi_microsoft}"/* /mnt/Dest/EFI/Microsoft/ >> "${LOG_FILE}" 2>&1 + fi + if [[ ! -z "${w_boot_bcd:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_boot_bcd}" /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1 + fi + if [[ ! -z "${w_boot_sdi:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_boot_sdi}" /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1 + fi + if [[ ! -z "${w_bootmgr:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_bootmgr}" /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1 + fi + if [[ ! -z "${w_sources:-}" ]]; then + rsync ${RSYNC_ARGS} "${w_sources}"/* /mnt/Dest/sources/ >> "${LOG_FILE}" 2>&1 + fi + + # Uncomment boot entries + sed -i "s/#UFD-WINPE#//" /mnt/Dest/EFI/boot/refind.conf + sed -i "s/#UFD-WINPE#//" /mnt/Dest/arch/boot/syslinux/*cfg +fi + +echo "Copying Main Kit..." | tee -a "${LOG_FILE}" +if [[ ! -z "${arg_m:-}" ]]; then + rsync ${RSYNC_ARGS} \ + "${MAIN_KIT}/" \ + "/mnt/Dest/${KIT_NAME_FULL}/" >> "${LOG_FILE}" 2>&1 +fi + +if [[ ! -z "${EXTRA_DIR:-}" ]]; then + echo "Copying Extra files..." | tee -a "${LOG_FILE}" + rsync ${RSYNC_ARGS} \ + "${EXTRA_DIR}"/ \ + /mnt/Dest/ >> "${LOG_FILE}" 2>&1 +fi + +# Install syslinux +echo "Copying Syslinux files..." | tee -a "${LOG_FILE}" +rsync ${RSYNC_ARGS} /usr/lib/syslinux/bios/*.c32 /mnt/Dest/arch/boot/syslinux/ >> "${LOG_FILE}" 2>&1 +syslinux --install -d /arch/boot/syslinux/ ${DEST_PAR} >> "${LOG_FILE}" 2>&1 + +echo "Unmounting destination..." | tee -a "${LOG_FILE}" +umount /mnt/Dest >> "${LOG_FILE}" 2>&1 +rmdir /mnt/Dest >> "${LOG_FILE}" 2>&1 +sync + +echo "Installing Syslinux MBR..." | tee -a "${LOG_FILE}" +if [[ "${USE_MBR}" == "True" ]]; then + dd bs=440 count=1 if=/usr/lib/syslinux/bios/mbr.bin of=${DEST_DEV} >> "${LOG_FILE}" 2>&1 +else + dd bs=440 count=1 if=/usr/lib/syslinux/bios/gptmbr.bin of=${DEST_DEV} >> "${LOG_FILE}" 2>&1 +fi +sync + +# Cleanup +echo "Hiding boot files..." | tee -a "${LOG_FILE}" +echo "drive s: file=\"${DEST_PAR}\"" > /root/.mtoolsrc +echo 'mtools_skip_check=1' >> /root/.mtoolsrc +for item in arch Boot bootmgr{,.efi} EFI en-us images isolinux sources "${KIT_NAME_FULL}"/{.bin,.cbin}; do + yes | mattrib +h "S:/${item}" >> "${LOG_FILE}" 2>&1 || true +done +sync + +# Unmount Sources +echo "Unmounting sources..." | tee -a "${LOG_FILE}" +for d in Linux WinPE; do + umount "/mnt/${d}" >> "${LOG_FILE}" 2>&1 || true + rmdir "/mnt/${d}" >> "${LOG_FILE}" 2>&1 || true +done + +# Close progress pane +pkill -f "tail.*${LOG_FILE}" + +# Done +echo "" | tee -a "${LOG_FILE}" +echo "Done." | tee -a "${LOG_FILE}" +echo "" +exit 0 diff --git a/.bin/Scripts/functions/common.py b/.bin/Scripts/functions/common.py index 502c985b..28834660 100644 --- a/.bin/Scripts/functions/common.py +++ b/.bin/Scripts/functions/common.py @@ -90,12 +90,13 @@ class SecureBootUnknownError(Exception): # General functions -def abort(): +def abort(show_prompt=True): """Abort script.""" print_warning('Aborted.') - sleep(1) - pause(prompt='Press Enter to exit... ') - exit_script() + if show_prompt: + sleep(1) + pause(prompt='Press Enter to exit... ') + exit_script(1) def ask(prompt='Kotaero!'): @@ -879,25 +880,19 @@ def make_tmp_dirs(): def set_common_vars(): """Set common variables.""" - global_vars['Date'] = time.strftime("%Y-%m-%d") - global_vars['Date-Time'] = time.strftime("%Y-%m-%d_%H%M_%z") - global_vars['Env'] = os.environ.copy() + global_vars['Date'] = time.strftime("%Y-%m-%d") + global_vars['Date-Time'] = time.strftime("%Y-%m-%d_%H%M_%z") + global_vars['Env'] = os.environ.copy() global_vars['ArchivePassword'] = ARCHIVE_PASSWORD - global_vars['BinDir'] = r'{BaseDir}\.bin'.format( - **global_vars) - global_vars['CBinDir'] = r'{BaseDir}\.cbin'.format( - **global_vars) - global_vars['ClientDir'] = r'{SYSTEMDRIVE}\{prefix}'.format( - prefix=KIT_NAME_SHORT, **global_vars['Env']) - global_vars['BackupDir'] = r'{ClientDir}\Backups'.format( - **global_vars) - global_vars['LogDir'] = r'{ClientDir}\Logs\{Date}'.format( - **global_vars) - global_vars['QuarantineDir'] = r'{ClientDir}\Quarantine'.format( - **global_vars) - global_vars['TmpDir'] = r'{BinDir}\tmp'.format( - **global_vars) + global_vars['BinDir'] = r'{BaseDir}\.bin'.format(**global_vars) + global_vars['CBinDir'] = r'{BaseDir}\.cbin'.format(**global_vars) + global_vars['ClientDir'] = r'{SYSTEMDRIVE}\{prefix}'.format( + prefix=KIT_NAME_SHORT, **global_vars['Env']) + global_vars['BackupDir'] = r'{ClientDir}\Backups'.format(**global_vars) + global_vars['LogDir'] = r'{ClientDir}\Logs\{Date}'.format(**global_vars) + global_vars['QuarantineDir'] = r'{ClientDir}\Quarantine'.format(**global_vars) + global_vars['TmpDir'] = r'{BinDir}\tmp'.format(**global_vars) def set_linux_vars(): @@ -905,12 +900,12 @@ def set_linux_vars(): These assume we're running under a WK-Linux build.""" result = run_program(['mktemp', '-d']) - global_vars['TmpDir'] = result.stdout.decode().strip() - global_vars['Date'] = time.strftime("%Y-%m-%d") - global_vars['Date-Time'] = time.strftime("%Y-%m-%d_%H%M_%z") - global_vars['Env'] = os.environ.copy() - global_vars['BinDir'] = '/usr/local/bin' - global_vars['LogDir'] = global_vars['TmpDir'] + global_vars['TmpDir'] = result.stdout.decode().strip() + global_vars['Date'] = time.strftime("%Y-%m-%d") + global_vars['Date-Time'] = time.strftime("%Y-%m-%d_%H%M_%z") + global_vars['Env'] = os.environ.copy() + global_vars['BinDir'] = '/usr/local/bin' + global_vars['LogDir'] = '{}/Logs'.format(global_vars['Env']['HOME']) global_vars['Tools'] = { 'wimlib-imagex': 'wimlib-imagex', 'SevenZip': '7z', @@ -919,10 +914,13 @@ def set_linux_vars(): def set_log_file(log_name): """Sets global var LogFile and creates path as needed.""" - folder_path = '{}{}{}'.format( - global_vars['LogDir'], - os.sep, - KIT_NAME_FULL) + if psutil.LINUX: + folder_path = global_vars['LogDir'] + else: + folder_path = '{}{}{}'.format( + global_vars['LogDir'], + os.sep, + KIT_NAME_FULL) log_file = '{}{}{}'.format( folder_path, os.sep, diff --git a/.bin/Scripts/functions/ufd.py b/.bin/Scripts/functions/ufd.py new file mode 100644 index 00000000..598985d3 --- /dev/null +++ b/.bin/Scripts/functions/ufd.py @@ -0,0 +1,463 @@ +"""Wizard Kit: Functions - UFD""" +# pylint: disable=broad-except,wildcard-import +# vim: sts=2 sw=2 ts=2 + +import os +import re +import shutil +import pathlib +from collections import OrderedDict +from functions.common import * + + +def case_insensitive_search(path, item): + """Search path for item case insensitively, returns str.""" + regex_match = '^{}$'.format(item) + real_path = '' + + # Quick check first + if os.path.exists('{}/{}'.format(path, item)): + real_path = '{}{}{}'.format( + path, + '' if path == '/' else '/', + item, + ) + + # Check all items in dir + for entry in os.scandir(path): + if re.match(regex_match, entry.name, re.IGNORECASE): + real_path = '{}{}{}'.format( + path, + '' if path == '/' else '/', + entry.name, + ) + + # Done + if not real_path: + raise FileNotFoundError('{}/{}'.format(path, item)) + + return real_path + + +def confirm_selections(args): + """Ask tech to confirm selections, twice if necessary.""" + if not ask('Is the above information correct?'): + abort(False) + ## Safety check + if not args['--update']: + print_standard(' ') + print_warning('SAFETY CHECK') + print_standard( + 'All data will be DELETED from the disk and partition(s) listed above.') + print_standard( + 'This is irreversible and will lead to {RED}DATA LOSS.{CLEAR}'.format( + **COLORS)) + if not ask('Asking again to confirm, is this correct?'): + abort(False) + + print_standard(' ') + + +def copy_source(source, items, overwrite=False): + """Copy source items to /mnt/UFD.""" + is_iso = source.name.lower().endswith('.iso') + + # Mount source if necessary + if is_iso: + mount(source, '/mnt/Source') + + # Copy items + for i_source, i_dest in items: + i_source = '{}{}'.format( + '/mnt/Source' if is_iso else source, + i_source, + ) + i_dest = '/mnt/UFD{}'.format(i_dest) + try: + recursive_copy(i_source, i_dest, overwrite=overwrite) + except FileNotFoundError: + # Going to assume (hope) that this is fine + pass + + # Unmount source if necessary + if is_iso: + unmount('/mnt/Source') + + +def find_first_partition(dev_path): + """Find path to first partition of dev, returns str.""" + cmd = [ + 'lsblk', + '--list', + '--noheadings', + '--output', 'name', + '--paths', + dev_path, + ] + result = run_program(cmd, encoding='utf-8', errors='ignore') + part_path = result.stdout.splitlines()[-1].strip() + + return part_path + + +def find_path(path): + """Find path case-insensitively, returns pathlib.Path obj.""" + path_obj = pathlib.Path(path).resolve() + + # Quick check first + if path_obj.exists(): + return path_obj + + # Fix case + parts = path_obj.relative_to('/').parts + real_path = '/' + for part in parts: + try: + real_path = case_insensitive_search(real_path, part) + except NotADirectoryError: + # Reclassify error + raise FileNotFoundError(path) + + # Raise error if path doesn't exist + path_obj = pathlib.Path(real_path) + if not path_obj.exists(): + raise FileNotFoundError(path_obj) + + # Done + return path_obj + + +def get_user_home(user): + """Get path to user's home dir, returns str.""" + home_dir = None + cmd = ['getent', 'passwd', user] + result = run_program(cmd, encoding='utf-8', errors='ignore', check=False) + try: + home_dir = result.stdout.split(':')[5] + except Exception: + # Just use HOME from ENV (or '/root' if that fails) + home_dir = os.environ.get('HOME', '/root') + + return home_dir + + +def get_user_name(): + """Get real user name, returns str.""" + user = None + if 'SUDO_USER' in os.environ: + user = os.environ.get('SUDO_USER', 'Unknown') + else: + user = os.environ.get('USER', 'Unknown') + + return user + + +def hide_items(ufd_dev, items): + """Set FAT32 hidden flag for items.""" + # pylint: disable=invalid-name + with open('/root/.mtoolsrc', 'w') as f: + f.write('drive U: file="{}"\n'.format( + find_first_partition(ufd_dev))) + f.write('mtools_skip_check=1\n') + + # Hide items + for item in items: + cmd = ['yes | mattrib +h "U:/{}"'.format(item)] + run_program(cmd, check=False, shell=True) + + +def install_syslinux_to_dev(ufd_dev, use_mbr): + """Install Syslinux to UFD (dev).""" + cmd = [ + 'dd', + 'bs=440', + 'count=1', + 'if=/usr/lib/syslinux/bios/{}.bin'.format( + 'mbr' if use_mbr else 'gptmbr', + ), + 'of={}'.format(ufd_dev), + ] + run_program(cmd) + + +def install_syslinux_to_partition(partition): + """Install Syslinux to UFD (partition).""" + cmd = [ + 'syslinux', + '--install', + '--directory', + '/arch/boot/syslinux/', + partition, + ] + run_program(cmd) + + +def is_valid_path(path_obj, path_type): + """Verify path_obj is valid by type, returns bool.""" + valid_path = False + if path_type == 'DIR': + valid_path = path_obj.is_dir() + elif path_type == 'KIT': + valid_path = path_obj.is_dir() and path_obj.joinpath('.bin').exists() + elif path_type == 'ISO': + valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.iso' + elif path_type == 'UFD': + valid_path = path_obj.is_block_device() + + return valid_path + + +def mount(mount_source, mount_point): + """Mount mount_source on mount_point.""" + os.makedirs(mount_point, exist_ok=True) + cmd = ['mount', mount_source, mount_point] + run_program(cmd) + + +def prep_device(dev_path, label, use_mbr=False, indent=2): + """Format device in preparation for applying the WizardKit components + + This is done is four steps: + 1. Zero-out first 64MB (this deletes the partition table and/or bootloader) + 2. Create a new partition table (GPT by default, optionally MBR) + 3. Set boot flag + 4. Format partition (FAT32, 4K aligned) + """ + # Zero-out first 64MB + cmd = 'dd bs=4M count=16 if=/dev/zero of={}'.format(dev_path).split() + try_and_print( + indent=indent, + message='Zeroing first 64MB...', + function=run_program, + cmd=cmd, + ) + + # Create partition table + cmd = 'parted {} --script -- mklabel {} mkpart primary fat32 4MiB {}'.format( + dev_path, + 'msdos' if use_mbr else 'gpt', + '-1s' if use_mbr else '-4MiB', + ).split() + try_and_print( + indent=indent, + message='Creating partition table...', + function=run_program, + cmd=cmd, + ) + + # Set boot flag + cmd = 'parted {} set 1 {} on'.format( + dev_path, + 'boot' if use_mbr else 'legacy_boot', + ).split() + try_and_print( + indent=indent, + message='Setting boot flag...', + function=run_program, + cmd=cmd, + ) + + # Format partition + cmd = [ + 'mkfs.vfat', '-F', '32', + '-n', label, + find_first_partition(dev_path), + ] + try_and_print( + indent=indent, + message='Formatting partition...', + function=run_program, + cmd=cmd, + ) + + +def recursive_copy(source, dest, overwrite=False): + """Copy source to dest recursively. + + NOTE: This uses rsync style source/dest syntax. + If the source has a trailing slash then it's contents are copied, + otherwise the source itself is copied. + + Examples assuming "ExDir/ExFile.txt" exists: + recursive_copy("ExDir", "Dest/") results in "Dest/ExDir/ExFile.txt" + recursive_copy("ExDir/", "Dest/") results in "Dest/ExFile.txt" + + NOTE 2: dest does not use find_path because it might not exist. + """ + copy_contents = source.endswith('/') + source = find_path(source) + dest = pathlib.Path(dest).resolve().joinpath(source.name) + os.makedirs(dest.parent, exist_ok=True) + + if source.is_dir(): + if copy_contents: + # Trailing slash syntax + for item in os.scandir(source): + recursive_copy(item.path, dest.parent, overwrite=overwrite) + elif not dest.exists(): + # No conflict, copying whole tree (no merging needed) + shutil.copytree(source, dest) + elif not dest.is_dir(): + # Refusing to replace file with dir + raise FileExistsError('Refusing to replace file: {}'.format(dest)) + else: + # Dest exists and is a dir, merge dirs + for item in os.scandir(source): + recursive_copy(item.path, dest, overwrite=overwrite) + elif source.is_file(): + if not dest.exists(): + # No conflict, copying file + shutil.copy2(source, dest) + elif not dest.is_file(): + # Refusing to replace dir with file + raise FileExistsError('Refusing to replace dir: {}'.format(dest)) + elif overwrite: + # Dest file exists, deleting and replacing file + os.remove(dest) + shutil.copy2(source, dest) + else: + # Refusing to delete file when overwrite=False + raise FileExistsError('Refusing to delete file: {}'.format(dest)) + + +def remove_arch(): + """Remove arch dir from UFD. + + This ensures a clean installation to the UFD and resets the boot files + """ + shutil.rmtree(find_path('/mnt/UFD/arch')) + + +def running_as_root(): + """Check if running with effective UID of 0, returns bool.""" + return os.geteuid() == 0 + + +def show_selections(args, sources, ufd_dev, ufd_sources): + """Show selections including non-specified options.""" + + # Sources + print_info('Sources') + for label in ufd_sources.keys(): + if label in sources: + print_standard(' {label:<18} {path}'.format( + label=label+':', + path=sources[label], + )) + else: + print_standard(' {label:<18} {YELLOW}Not Specified{CLEAR}'.format( + label=label+':', + **COLORS, + )) + print_standard(' ') + + # Destination + print_info('Destination') + cmd = [ + 'lsblk', '--nodeps', '--noheadings', '--paths', + '--output', 'NAME,FSTYPE,TRAN,SIZE,VENDOR,MODEL,SERIAL', + ufd_dev, + ] + result = run_program(cmd, check=False, encoding='utf-8', errors='ignore') + print_standard(result.stdout.strip()) + cmd = [ + 'lsblk', '--noheadings', '--paths', + '--output', 'NAME,SIZE,FSTYPE,LABEL,MOUNTPOINT', + ufd_dev, + ] + result = run_program(cmd, check=False, encoding='utf-8', errors='ignore') + for line in result.stdout.splitlines()[1:]: + print_standard(line) + + # Notes + if args['--update']: + print_warning('Updating kit in-place') + elif args['--use-mbr']: + print_warning('Formatting using legacy MBR') + print_standard(' ') + + +def unmount(mount_point): + """Unmount mount_point.""" + cmd = ['umount', mount_point] + run_program(cmd) + + +def update_boot_entries(boot_entries, boot_files, iso_label, ufd_label): + """Update boot files for UFD usage""" + configs = [] + + # Find config files + for c_path, c_ext in boot_files.items(): + c_path = find_path('/mnt/UFD{}'.format(c_path)) + for item in os.scandir(c_path): + if item.name.lower().endswith(c_ext.lower()): + configs.append(item.path) + + # Update Linux labels + cmd = [ + 'sed', + '--in-place', + '--regexp-extended', + 's/{}/{}/'.format(iso_label, ufd_label), + *configs, + ] + run_program(cmd) + + # Uncomment extra entries if present + for b_path, b_comment in boot_entries.items(): + try: + find_path('/mnt/UFD{}'.format(b_path)) + except (FileNotFoundError, NotADirectoryError): + # Entry not found, continue to next entry + continue + + # Entry found, update config files + cmd = [ + 'sed', + '--in-place', + 's/#{}#//'.format(b_comment), + *configs, + ] + run_program(cmd, check=False) + + +def verify_sources(args, ufd_sources): + """Check all sources and abort if necessary, returns dict.""" + sources = OrderedDict() + + for label, data in ufd_sources.items(): + s_path = args[data['Arg']] + if s_path: + try: + s_path_obj = find_path(s_path) + except FileNotFoundError: + print_error('ERROR: {} not found: {}'.format(label, s_path)) + abort(False) + if not is_valid_path(s_path_obj, data['Type']): + print_error('ERROR: Invalid {} source: {}'.format(label, s_path)) + abort(False) + sources[label] = s_path_obj + + return sources + + +def verify_ufd(dev_path): + """Check that dev_path is a valid UFD, returns pathlib.Path obj.""" + ufd_dev = None + + try: + ufd_dev = find_path(dev_path) + except FileNotFoundError: + print_error('ERROR: UFD device not found: {}'.format(dev_path)) + abort(False) + + if not is_valid_path(ufd_dev, 'UFD'): + print_error('ERROR: Invalid UFD device: {}'.format(ufd_dev)) + abort(False) + + return ufd_dev + + +if __name__ == '__main__': + print("This file is not meant to be called directly.") diff --git a/.bin/Scripts/settings/ufd.py b/.bin/Scripts/settings/ufd.py new file mode 100644 index 00000000..94cf333e --- /dev/null +++ b/.bin/Scripts/settings/ufd.py @@ -0,0 +1,115 @@ +'''Wizard Kit: Settings - UFD''' +# pylint: disable=C0326,E0611 +# vim: sts=2 sw=2 ts=2 + +from collections import OrderedDict +from settings.main import KIT_NAME_FULL,KIT_NAME_SHORT + +# General +DOCSTRING = '''WizardKit: Build UFD + +Usage: + build-ufd [options] --ufd-device PATH --linux PATH + [--linux-minimal PATH] + [--main-kit PATH] + [--winpe PATH] + [--extra-dir PATH] + build-ufd (-h | --help) + +Options: + -d PATH, --linux-dgpu PATH + -e PATH, --extra-dir PATH + -k PATH, --main-kit PATH + -l PATH, --linux PATH + -m PATH, --linux-minimal PATH + -u PATH, --ufd-device PATH + -w PATH, --winpe PATH + + -h --help Show this page + -M --use-mbr Use real MBR instead of GPT w/ Protective MBR + -F --force Bypass all confirmation messages. USE WITH EXTREME CAUTION! + -U --update Don't format device, just update +''' +ISO_LABEL = '{}_LINUX'.format(KIT_NAME_SHORT) +UFD_LABEL = '{}_UFD'.format(KIT_NAME_SHORT) +UFD_SOURCES = OrderedDict({ + 'Linux': {'Arg': '--linux', 'Type': 'ISO'}, + 'Linux (dGPU)': {'Arg': '--linux-dgpu', 'Type': 'ISO'}, + 'Linux (Minimal)': {'Arg': '--linux-minimal', 'Type': 'ISO'}, + 'WinPE': {'Arg': '--winpe', 'Type': 'ISO'}, + 'Main Kit': {'Arg': '--main-kit', 'Type': 'KIT'}, + 'Extra Dir': {'Arg': '--extra-dir', 'Type': 'DIR'}, + }) + +# Definitions: Boot entries +BOOT_ENTRIES = { + # Path to check: Comment to remove + '/arch_minimal': 'UFD-MINIMAL', + '/dgpu': 'UFD-DGPU', + '/sources/boot.wim': 'UFD-WINPE', + } +BOOT_FILES = { + # Directory: extension + '/arch/boot/syslinux': 'cfg', + '/EFI/boot': 'conf', + } + +# Definitions: Sources and Destinations +## NOTES: Paths are relative to the root of the ISO/UFD +## Sources use rsync's trailing slash syntax +ITEMS = { + 'Extra Dir': ( + ('/', '/'), + ), + 'Linux': ( + ('/arch', '/'), + ('/isolinux', '/'), + ('/EFI/boot', '/EFI/'), + ('/EFI/memtest86', '/EFI/'), + ), + 'Linux (dGPU)': ( + ('/arch/boot/x86_64/archiso.img', '/dgpu/'), + ('/arch/boot/x86_64/vmlinuz', '/dgpu/'), + ('/arch/pkglist.x86_64.txt', '/dgpu/'), + ('/arch/x86_64', '/dgpu/'), + ), + 'Linux (Minimal)': ( + ('/arch/boot/x86_64/archiso.img', '/arch_minimal/'), + ('/arch/boot/x86_64/vmlinuz', '/arch_minimal/'), + ('/arch/pkglist.x86_64.txt', '/arch_minimal/'), + ('/arch/x86_64', '/arch_minimal/'), + ), + 'Main Kit': ( + ('/', '/{}/'.format(KIT_NAME_FULL)), + ), + 'WinPE': ( + ('/bootmgr', '/'), + ('/bootmgr.efi', '/'), + ('/en_us', '/'), + ('/Boot/', '/boot/'), + ('/EFI/Boot/', '/EFI/Microsoft/'), + ('/EFI/Microsoft/', '/EFI/Microsoft/'), + ('/Boot/BCD', '/sources/'), + ('/Boot/boot.sdi', '/sources/'), + ('/bootmgr', '/sources/'), + ('/sources/boot.wim', '/sources/'), + ), + } +ITEMS_HIDDEN = ( + # Linux (all versions) + 'arch', + 'arch_minimal', + 'dgpu', + 'EFI', + 'isolinux', + # WinPE + 'boot', + 'bootmgr', + 'bootmgr.efi', + 'en-us', + 'images', + 'sources', + ) + +if __name__ == '__main__': + print("This file is not meant to be called directly.") diff --git a/.linux_items/include/EFI/boot/icons/dgpu.png b/.linux_items/include/EFI/boot/icons/dgpu.png new file mode 100644 index 00000000..e9770abd Binary files /dev/null and b/.linux_items/include/EFI/boot/icons/dgpu.png differ diff --git a/.linux_items/include/EFI/boot/refind.conf b/.linux_items/include/EFI/boot/refind.conf index 4ab3be7b..62a639f7 100644 --- a/.linux_items/include/EFI/boot/refind.conf +++ b/.linux_items/include/EFI/boot/refind.conf @@ -26,15 +26,30 @@ menuentry "Linux" { initrd /arch/boot/amd_ucode.img initrd /arch/boot/x86_64/archiso.img options "archisobasedir=arch archisolabel=%ARCHISO_LABEL% copytoram loglevel=3" - submenuentry "Linux (i3)" { - add_options "i3" - } submenuentry "Linux (CLI)" { - add_options "loglevel=4 nomodeset nox" + add_options "nox" } + #UFD-MINIMAL#submenuentry "Linux (Minimal)" { + #UFD-MINIMAL# loader /arch_minimal/vmlinuz + #UFD-MINIMAL# initrd + #UFD-MINIMAL# initrd /arch/boot/intel_ucode.img + #UFD-MINIMAL# initrd /arch/boot/amd_ucode.img + #UFD-MINIMAL# initrd /arch_minimal/archiso.img + #UFD-MINIMAL# options + #UFD-MINIMAL# options "archisobasedir=arch_minimal archisolabel=%ARCHISO_LABEL% copytoram loglevel=3" + #UFD-MINIMAL#} } #UFD-WINPE#menuentry "WindowsPE" { #UFD-WINPE# ostype windows #UFD-WINPE# icon /EFI/boot/icons/wk_win.png #UFD-WINPE# loader /EFI/microsoft/bootx64.efi #UFD-WINPE#} +#UFD-DGPU#menuentry "Mac dGPU Disable Tool" { +#UFD-DGPU# icon /EFI/boot/icons/dgpu.png +#UFD-DGPU# loader /dgpu/vmlinuz +#UFD-DGPU# initrd /arch/boot/intel_ucode.img +#UFD-DGPU# initrd /arch/boot/amd_ucode.img +#UFD-DGPU# initrd /dgpu/archiso.img +#UFD-DGPU# options "archisobasedir=dgpu archisolabel=%ARCHISO_LABEL% nomodeset" +#UFD-DGPU#} + diff --git a/.linux_items/include/syslinux/wk_sys_linux.cfg b/.linux_items/include/syslinux/wk_sys_linux.cfg index d4319c00..b6a9370c 100644 --- a/.linux_items/include/syslinux/wk_sys_linux.cfg +++ b/.linux_items/include/syslinux/wk_sys_linux.cfg @@ -8,17 +8,6 @@ LINUX boot/x86_64/vmlinuz INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram loglevel=3 -LABEL wk_linux_i3 -TEXT HELP -A live Linux environment (i3) - * HW diagnostics, file-based backups, data recovery, etc -ENDTEXT -MENU LABEL Linux (i3) -LINUX boot/x86_64/vmlinuz -INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img -APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram loglevel=3 i3 -SYSAPPEND 3 - LABEL wk_linux_cli TEXT HELP A live Linux environment (CLI) @@ -27,5 +16,17 @@ ENDTEXT MENU LABEL Linux (CLI) LINUX boot/x86_64/vmlinuz INITRD boot/intel_ucode.img,boot/amd_ucode.img,boot/x86_64/archiso.img -APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram nox nomodeset +APPEND archisobasedir=%INSTALL_DIR% archisolabel=%ARCHISO_LABEL% copytoram nox SYSAPPEND 3 + +#UFD-MINIMAL#LABEL wk_linux_minimal +#UFD-MINIMAL#TEXT HELP +#UFD-MINIMAL#A live Linux environment (Minimal) +#UFD-MINIMAL# * HW diagnostics, file-based backups, data recovery, etc +#UFD-MINIMAL#ENDTEXT +#UFD-MINIMAL#MENU LABEL Linux (Minimal) +#UFD-MINIMAL#LINUX ../arch_minimal/vmlinuz +#UFD-MINIMAL#INITRD boot/intel_ucode.img,boot/amd_ucode.img,../arch_minimal/archiso.img +#UFD-MINIMAL#APPEND archisobasedir=arch_minimal archisolabel=%ARCHISO_LABEL% copytoram loglevel=3 +#UFD-MINIMAL#SYSAPPEND 3 + diff --git a/.linux_items/packages/live_add b/.linux_items/packages/live_add index c01545c5..26c759c6 100644 --- a/.linux_items/packages/live_add +++ b/.linux_items/packages/live_add @@ -32,6 +32,7 @@ networkmanager p7zip progsreiserfs python +python-docopt python-psutil python-pytz python-requests