#!/usr/bin/env bash set -Eeuo pipefail IFS=$'\n\t' readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" readonly PROJECT_USERPATCHES="${SCRIPT_DIR}/userpatches" readonly SOURCE_LOCK="${PROJECT_USERPATCHES}/source-lock.env" readonly FRAMEWORK_OVERRIDES="${SCRIPT_DIR}/framework-overrides" die() { echo "build-image.sh: $*" >&2 exit 1 } usage() { cat <<'EOF' Usage: build-image.sh --ssh-public-key PATH [OPTIONS] Build the source-locked Armbian Debian Trixie image for Turing RK1. Options: --ssh-public-key PATH Required OpenSSH public-key/authorized_keys file. --work-dir PATH Managed framework/cache directory (default: .work). --prepare-only Clone/stage inputs, but do not invoke Armbian. --config-dump Stage inputs and run Armbian's non-building config dump. -h, --help Show this help. The default action performs the full image build. It may ask for sudo through Armbian's normal native-build launcher. No image is flashed by this command. EOF } validate_public_keys() { local key_file="${1}" [[ -f "${key_file}" ]] || die "public-key file does not exist: ${key_file}" [[ -s "${key_file}" ]] || die "public-key file is empty: ${key_file}" command -v ssh-keygen >/dev/null || die "ssh-keygen is required to validate the public key" awk ' /^[[:space:]]*($|#)/ { next } $1 !~ /^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)|sk-ssh-ed25519@openssh.com|sk-ecdsa-sha2-nistp256@openssh.com)$/ { exit 1 } { count++ } END { if (count < 1) exit 1 } ' "${key_file}" || die "use plain OpenSSH public keys without authorized_keys options" ssh-keygen -l -f "${key_file}" >/dev/null || die "ssh-keygen rejected the supplied public-key file" } prepare_framework() { local work_root="${1}" local framework_dir="${work_root}/armbian-build" local origin install -d -m 0755 "${work_root}" work_root="$(cd -- "${work_root}" && pwd -P)" framework_dir="${work_root}/armbian-build" [[ "${work_root}" != "/" ]] || die "refusing to use / as the work directory" if [[ ! -e "${framework_dir}" ]]; then git init "${framework_dir}" >/dev/null git -C "${framework_dir}" remote add origin "${RK1_MEDIA_ARMBIAN_BUILD_URL}" install -m 0600 /dev/null "${framework_dir}/.rk1-media-build-root" fi [[ -d "${framework_dir}/.git" ]] || die "work path is not a Git checkout: ${framework_dir}" [[ -f "${framework_dir}/.rk1-media-build-root" ]] || die "work checkout is not managed by this script: ${framework_dir}" origin="$(git -C "${framework_dir}" remote get-url origin)" [[ "${origin}" == "${RK1_MEDIA_ARMBIAN_BUILD_URL}" ]] || die "unexpected Armbian origin: ${origin}" if git -C "${framework_dir}" rev-parse --verify HEAD >/dev/null 2>&1; then git -C "${framework_dir}" diff --quiet || die "tracked modifications exist in ${framework_dir}" git -C "${framework_dir}" diff --cached --quiet || die "staged modifications exist in ${framework_dir}" fi if ! git -C "${framework_dir}" cat-file -e "${RK1_MEDIA_ARMBIAN_BUILD_COMMIT}^{commit}" 2>/dev/null; then git -C "${framework_dir}" fetch --depth=1 origin "${RK1_MEDIA_ARMBIAN_BUILD_COMMIT}" fi git -C "${framework_dir}" checkout --detach "${RK1_MEDIA_ARMBIAN_BUILD_COMMIT}" >/dev/null printf '%s\n' "${framework_dir}" } stage_userpatches() { local framework_dir="${1}" local public_key="${2}" local destination="${framework_dir}/userpatches" [[ -f "${framework_dir}/.rk1-media-build-root" ]] || die "refusing to stage into an unmanaged checkout" [[ "${destination}" == "${framework_dir}/userpatches" ]] || die "internal userpatch path check failed" [[ ! -L "${destination}" ]] || die "refusing to replace a symlinked userpatches directory" if [[ -e "${destination}" ]]; then [[ -f "${destination}/.rk1-media-managed" ]] || die "existing userpatches are not managed by this script" rm -rf -- "${destination}" fi install -d -m 0755 "${destination}" cp -a "${PROJECT_USERPATCHES}/." "${destination}/" install -m 0600 /dev/null "${destination}/.rk1-media-managed" install -d -m 0700 "${destination}/overlay/build-input" install -m 0600 "${public_key}" "${destination}/overlay/build-input/authorized_keys" install -m 0644 "${SOURCE_LOCK}" "${destination}/overlay/build-input/source-lock.env" # Armbian supports immutable resolutions for otherwise moving branch inputs # through config/sources/git_sources.json. This pins its firmware artifact. cp -a "${FRAMEWORK_OVERRIDES}/." "${framework_dir}/" } main() { local public_key="" local work_dir="${SCRIPT_DIR}/.work" local mode="build" local framework_dir while (($#)); do case "${1}" in --ssh-public-key) (($# >= 2)) || die "--ssh-public-key requires a path" public_key="${2}" shift 2 ;; --work-dir) (($# >= 2)) || die "--work-dir requires a path" work_dir="${2}" shift 2 ;; --prepare-only) mode="prepare" shift ;; --config-dump) mode="config-dump" shift ;; -h|--help) usage exit 0 ;; *) die "unknown option: ${1}" ;; esac done [[ -n "${public_key}" ]] || die "--ssh-public-key is required; the image never enables password login" [[ -r "${SOURCE_LOCK}" ]] || die "source lock is missing: ${SOURCE_LOCK}" # shellcheck source=userpatches/source-lock.env source "${SOURCE_LOCK}" validate_public_keys "${public_key}" framework_dir="$(prepare_framework "${work_dir}")" stage_userpatches "${framework_dir}" "${public_key}" echo "Prepared Armbian ${RK1_MEDIA_ARMBIAN_BUILD_COMMIT} in ${framework_dir}" case "${mode}" in prepare) echo "Configuration staged; no build was started." ;; config-dump) (cd "${framework_dir}" && CONFIG_DEFS_ONLY=yes ./compile.sh rk1-media config-dump) ;; build) (cd "${framework_dir}" && ./compile.sh rk1-media build) echo "Images and checksums: ${framework_dir}/output/images" ;; esac } main "$@"