| #!/bin/bash |
| |
| # Copyright 2016 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| SCRIPT_ROOT=$(dirname $(readlink -f "$0")) |
| . "${SCRIPT_ROOT}/build_library/build_common.sh" || exit 1 |
| |
| assert_inside_chroot "$@" |
| |
| DEFINE_string board "${DEFAULT_BOARD}" \ |
| "The board to build an image for." |
| DEFINE_string package "" \ |
| "Package whose bare minimum deps the final container should have." |
| DEFINE_string name "example" \ |
| "Name of the container." |
| DEFINE_string extra "" \ |
| "Comma separated extra packages to be included in the final image." |
| DEFINE_string argv "" \ |
| "The command (and args) to run inside the container." |
| DEFINE_integer minor_version -1 \ |
| "An extra minor version number to append to the version read from portage." |
| |
| FLAGS_HELP="USAGE: $(basename "$0") [flags] |
| |
| The --package and --argv options are required. |
| |
| To build a container for fastboot, you might want something like: |
| $ $(basename "$0") --package dev-util/android-tools --argv /usr/bin/fastboot |
| |
| The output directory will contain the following: |
| * image.squash: a squashfs image containing the rootfs and config.json |
| for run_oci |
| * table: text file containing verity commandline |
| * imageloader.json: imageloader manifest which contains hashes |
| of image.squash and table |
| * imageloader.sig.2: ECDSA signature of imageloader.json |
| |
| If the --minor_version flag is given a negative number, it is ignored. |
| " |
| |
| FLAGS "$@" || exit 1 |
| eval set -- "${FLAGS_ARGV}" |
| switch_to_strict_mode |
| |
| # Sign the manifest.json file. |
| sign_manifest() { |
| "${VBOOT_SIGNING_DIR}"/sign_official_build.sh \ |
| oci-container "$1" "${VBOOT_DEVKEYS_DIR}" "$1" |
| } |
| |
| # Generate the imageloader.json for this container. |
| generate_manifest() { |
| local pkg_version="$1" |
| local output="$2" |
| local manifest="${output}/imageloader.json" |
| local image="${output}/image.squash" |
| local table="${output}/table" |
| |
| ( |
| gethash() { |
| local file="$1" |
| echo $(sha256sum <"${file}" | awk '{print $1}') |
| } |
| |
| cd "${output}" |
| printf '{\n' |
| printf '"manifest-version": 1,\n' |
| printf '"version": "%s",\n' "${pkg_version}" |
| printf '"image-sha256-hash": "%s",\n' "$(gethash "${image}")" |
| printf '"table-sha256-hash": "%s"\n' "$(gethash "${table}")" |
| printf '}\n' |
| ) >"${manifest}" |
| |
| # Sanity check the generated manifest. |
| python -mjson.tool <"${manifest}" >/dev/null |
| |
| sign_manifest "${output}" |
| } |
| |
| # Sign the specified disk image using verity so we can load it with dm-verity |
| # at runtime. We don't allow algorithm selection -- sha256 should be good |
| # enough for everyone! :) |
| # Note: We write the verity command line to the "verity" variable as an output. |
| sign_disk_image() { |
| local img="$1" |
| local hashtree="${img}.hashtree" |
| # TODO: Add "salt=random" here. |
| verity=$(verity mode=create alg=sha256 payload="${img}" \ |
| hashtree="${hashtree}") |
| cat "${hashtree}" >>"${img}" |
| rm "${hashtree}" |
| } |
| |
| # Produce the container image. |
| build_container_image() { |
| local pkg_name="$1" |
| local container_name="$2" |
| local argv="$3" |
| # For now we hardcode chronos. |
| local container_uid="1000" |
| local container_gid="1000" |
| local output="${PWD}/${container_name}" |
| local IMAGEDIR="${output}/image" |
| local ROOTDIR="${IMAGEDIR}/rootfs" |
| |
| export INSTALL_MASK="${DEFAULT_INSTALL_MASK}" |
| |
| mkdir -p "${output}" |
| mkdir -p "${IMAGEDIR}" |
| |
| info "Fetching package version ... " |
| local version="$(get_package_version "${pkg_name}")" |
| |
| install_with_root_deps "${ROOTDIR}" "${pkg_name}" sys-libs/gcc-libs \ |
| ${FLAGS_extra//,/ } |
| |
| info "Installing C library ... " |
| install_libc "${ROOTDIR}" |
| |
| info "Cleaning excess files ... " |
| sudo rm -rf "${ROOTDIR}"/var "${ROOTDIR}"/usr/lib*/gconv/ \ |
| "${ROOTDIR}"/sbin/ldconfig |
| sudo find "${ROOTDIR}"/ -type d -depth -exec rmdir {} + 2>/dev/null || : |
| |
| info "Creating top level dirs and socket dirs ... " |
| sudo mkdir -p "${ROOTDIR}"/{dev,proc,root,sys,home/user,run,tmp,var} |
| |
| info "Adding config.json ... " |
| sed \ |
| -e "s:@APP_NAME@:${container_name}:g" \ |
| -e "s:@TERMINAL@:true:g" \ |
| -e "s:@USER_UID@:${container_uid}:g" \ |
| -e "s:@USER_GID@:${container_gid}:g" \ |
| -e "s:@ARGV@:${argv// /\", \"}:g" \ |
| generic_container_files/config.json >"${IMAGEDIR}/config.json" |
| |
| info "Generating squashfs file ... " |
| local args=( |
| -all-root |
| -noappend |
| ) |
| local img="${output}/image.squash" |
| # We need to run through sudo because some files might be read-only by root. |
| sudo mksquashfs "${IMAGEDIR}" "${img}" "${args[@]}" |
| sudo chown $(id -u):$(id -g) "${img}" |
| info "Signing squashfs file ... " |
| local verity |
| sign_disk_image "${img}" |
| echo "${verity}" > "${output}/table" |
| |
| info "Generating manifest ..." |
| generate_manifest "${version}" "${output}" |
| |
| info "Cleaning up ... " |
| sudo rm -rf "${IMAGEDIR}" |
| } |
| |
| run_emerge() { |
| emerge-${BOARD} \ |
| --quiet --jobs ${NUM_JOBS} \ |
| --usepkgonly \ |
| "$@" |
| } |
| |
| # Normal emerge. |
| install_with_no_deps() { |
| local root_dir="$1" |
| shift |
| info "Installing '$*' with no deps ... " |
| run_emerge --root="${root_dir}" "$@" --nodeps |
| } |
| |
| # Emerge with root deps. |
| install_with_root_deps() { |
| local root_dir="$1" |
| shift |
| info "Installing '$*' with root deps ... " |
| run_emerge --root="${root_dir}" "$@" --root-deps=rdeps |
| } |
| |
| strip_version() { |
| local full_version="$1" |
| echo "${full_version}" | sed 's/^\([0-9.]*[0-9]\).*/\1/' |
| } |
| |
| get_package_version() { |
| local full_version="$(equery-${BOARD} --quiet list -F '$version' "$@")" |
| local stripped_version="$(strip_version "${full_version}")" |
| if [ "${FLAGS_minor_version}" -ge 0 ]; then |
| echo "${stripped_version}.${FLAGS_minor_version}" |
| else |
| echo "${stripped_version}" |
| fi |
| } |
| |
| main() { |
| . "${BUILD_LIBRARY_DIR}/board_options.sh" || exit 1 |
| . "${BUILD_LIBRARY_DIR}/base_image_util.sh" || exit 1 |
| |
| if [[ -z "${FLAGS_argv}" ]]; then |
| die_notrace "--argv is needed." |
| fi |
| if [[ -z "${FLAGS_package}" ]]; then |
| die_notrace "--package is needed." |
| fi |
| |
| local container_name="${FLAGS_name}" |
| if [[ -d "${container_name}" ]]; then |
| die_notrace "Output dir already exists : ${container_name}/" |
| fi |
| info "Building container '${container_name}' for ${FLAGS_package} ..." |
| build_container_image "${FLAGS_package}" "${container_name}" "${FLAGS_argv}" |
| } |
| |
| main "$@" |