mirror of
https://github.com/flatcar/scripts.git
synced 2025-08-07 13:06:59 +02:00
There is no need to arbitrarily bind mount all of the host system's /run into the chroot. In fact this causes issues when the host system's /run isn't set up in a way this script anticipates. Namely the user runtime directory in /run/usr/$UID is another tmpfs mount on my system, leaving the underlying directory node that is bind-mounted in with the wrong ownership. Behave a little more like a responsible container and use a fresh /run but continue binding /run/shm for whatever versions of Ubuntu that depended on that behavior. Not strictly needed but go ahead and create the user runtime directory with the correct permissions.
463 lines
17 KiB
Bash
Executable File
463 lines
17 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Copyright (c) 2011 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 to enter the chroot environment
|
|
|
|
SCRIPT_ROOT=$(readlink -f $(dirname "$0")/..)
|
|
. "${SCRIPT_ROOT}/common.sh" || exit 1
|
|
|
|
# Script must be run outside the chroot and as root.
|
|
assert_outside_chroot
|
|
assert_root_user
|
|
|
|
# Define command line flags
|
|
# See http://code.google.com/p/shflags/wiki/Documentation10x
|
|
DEFINE_string chroot "$DEFAULT_CHROOT_DIR" \
|
|
"The destination dir for the chroot environment." "d"
|
|
DEFINE_string trunk "$GCLIENT_ROOT" \
|
|
"The source trunk to bind mount within the chroot." "s"
|
|
DEFINE_string build_number "" \
|
|
"The build-bot build number (when called by buildbot only)." "b"
|
|
DEFINE_string chrome_root "" \
|
|
"The root of your chrome browser source. Should contain a 'src' subdir."
|
|
DEFINE_string chrome_root_mount "/home/${SUDO_USER}/chrome_root" \
|
|
"The mount point of the chrome broswer source in the chroot."
|
|
DEFINE_string cache_dir "" "Directory to use for caching."
|
|
|
|
DEFINE_boolean official_build $FLAGS_FALSE \
|
|
"Set COREOS_OFFICIAL=1 for release builds."
|
|
DEFINE_boolean ssh_agent $FLAGS_TRUE "Import ssh agent."
|
|
DEFINE_boolean early_make_chroot $FLAGS_FALSE \
|
|
"Internal flag. If set, the command is run as root without sudo."
|
|
DEFINE_boolean verbose $FLAGS_FALSE "Print out actions taken"
|
|
|
|
# More useful help
|
|
FLAGS_HELP="USAGE: $0 [flags] [VAR=value] [-- command [arg1] [arg2] ...]
|
|
|
|
One or more VAR=value pairs can be specified to export variables into
|
|
the chroot environment. For example:
|
|
|
|
$0 FOO=bar BAZ=bel
|
|
|
|
If [-- command] is present, runs the command inside the chroot,
|
|
after changing directory to /${SUDO_USER}/trunk/src/scripts. Note that neither
|
|
the command nor args should include single quotes. For example:
|
|
|
|
$0 -- ./build_platform_packages.sh
|
|
|
|
Otherwise, provides an interactive shell.
|
|
"
|
|
|
|
CROS_LOG_PREFIX=cros_sdk:enter_chroot
|
|
SUDO_HOME=$(eval echo ~${SUDO_USER})
|
|
|
|
# Version of info from common.sh that only echos if --verbose is set.
|
|
debug() {
|
|
if [ $FLAGS_verbose -eq $FLAGS_TRUE ]; then
|
|
info "$*"
|
|
fi
|
|
}
|
|
|
|
# Parse command line flags
|
|
FLAGS "$@" || exit 1
|
|
eval set -- "${FLAGS_ARGV}"
|
|
|
|
if [ $FLAGS_official_build -eq $FLAGS_TRUE ]; then
|
|
COREOS_OFFICIAL=1
|
|
fi
|
|
|
|
[ -z "${FLAGS_cache_dir}" ] && \
|
|
die "--cache_dir is required"
|
|
|
|
# Only now can we die on error. shflags functions leak non-zero error codes,
|
|
# so will die prematurely if 'switch_to_strict_mode' is specified before now.
|
|
# TODO: replace shflags with something less error-prone, or contribute a fix.
|
|
switch_to_strict_mode
|
|
|
|
# These config files are to be copied into chroot if they exist in home dir.
|
|
FILES_TO_COPY_TO_CHROOT=(
|
|
.gdata_cred.txt # User/password for Google Docs on chromium.org
|
|
.gdata_token # Auth token for Google Docs on chromium.org
|
|
.disable_build_stats_upload # Presence of file disables command stats upload
|
|
.netrc # May contain required source fetching credentials
|
|
.boto # Auth information for gsutil
|
|
.boto-key.p12 # Service account key for gsutil
|
|
.ssh/config # User may need this for fetching git over ssh
|
|
.ssh/known_hosts # Reuse existing known hosts
|
|
)
|
|
|
|
INNER_CHROME_ROOT=$FLAGS_chrome_root_mount # inside chroot
|
|
CHROME_ROOT_CONFIG="/var/cache/chrome_root" # inside chroot
|
|
FUSE_DEVICE="/dev/fuse"
|
|
|
|
# We can't use /var/lock because that might be a symlink to /run/lock outside
|
|
# of the chroot. Or /run on the host system might not exist.
|
|
LOCKFILE="${FLAGS_chroot}/.enter_chroot.lock"
|
|
MOUNTED_PATH=$(readlink -f "$FLAGS_chroot")
|
|
|
|
# Reset the depot tools/internal trunk pathways to what they'll
|
|
# be w/in the chroot.
|
|
set_chroot_trunk_dir "${FLAGS_chroot}"
|
|
|
|
|
|
setup_mount() {
|
|
# If necessary, mount $source in the host FS at $target inside the
|
|
# chroot directory with $mount_args. We don't write to /etc/mtab because
|
|
# these mounts are all contained within an unshare and are therefore
|
|
# inaccessible to other namespaces (e.g. the host desktop system).
|
|
local source="$1"
|
|
local mount_args="-n $2"
|
|
local target="$3"
|
|
|
|
local mounted_path="${MOUNTED_PATH}$target"
|
|
|
|
case " ${MOUNT_CACHE} " in
|
|
*" ${mounted_path} "*)
|
|
# Already mounted!
|
|
;;
|
|
*)
|
|
mkdir -p "${mounted_path}"
|
|
# The args are left unquoted on purpose.
|
|
if [[ -n ${source} ]]; then
|
|
mount ${mount_args} "${source}" "${mounted_path}"
|
|
else
|
|
mount ${mount_args} "${mounted_path}"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
copy_into_chroot_if_exists() {
|
|
# $1 is file path outside of chroot to copy to path $2 inside chroot.
|
|
[ -e "$1" ] && cp -p "$1" "${FLAGS_chroot}/$2"
|
|
}
|
|
|
|
# Usage: promote_api_keys
|
|
# This takes care of getting the developer API keys into the chroot where
|
|
# chrome can build with them. It needs to take it from the places a dev
|
|
# is likely to put them, and recognize that older chroots may or may not
|
|
# have been used since the concept of keys got added, as well as before
|
|
# and after the developer decding to grab his own keys.
|
|
promote_api_keys() {
|
|
local destination="${FLAGS_chroot}/home/${SUDO_USER}/.googleapikeys"
|
|
# Don't disturb existing keys. They could be set differently
|
|
if [[ -s "${destination}" ]]; then
|
|
return 0
|
|
fi
|
|
if [[ -r "${SUDO_HOME}/.googleapikeys" ]]; then
|
|
cp -p "${SUDO_HOME}/.googleapikeys" "${destination}"
|
|
if [[ -s "${destination}" ]] ; then
|
|
info "Copied Google API keys into chroot."
|
|
fi
|
|
elif [[ -r "${SUDO_HOME}/.gyp/include.gypi" ]]; then
|
|
local NAME="('google_(api_key|default_client_(id|secret))')"
|
|
local WS="[[:space:]]*"
|
|
local CONTENTS="('[^\\\\']*')"
|
|
sed -nr -e "/^${WS}${NAME}${WS}[:=]${WS}${CONTENTS}.*/{s//\1: \4,/;p;}" \
|
|
"${SUDO_HOME}/.gyp/include.gypi" | user_clobber "${destination}"
|
|
if [[ -s "${destination}" ]]; then
|
|
info "Put discovered Google API keys into chroot."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
generate_locales() {
|
|
# Make sure user's requested locales are available
|
|
# http://crosbug.com/19139
|
|
# And make sure en_US{,.UTF-8} are always available as
|
|
# that what buildbot forces internally
|
|
local l locales gen_locales=()
|
|
|
|
locales=$(printf '%s\n' en_US en_US.UTF-8 ${LANG} \
|
|
$LC_{ADDRESS,ALL,COLLATE,CTYPE,IDENTIFICATION,MEASUREMENT,MESSAGES} \
|
|
$LC_{MONETARY,NAME,NUMERIC,PAPER,TELEPHONE,TIME} | \
|
|
sort -u | sed '/^C$/d')
|
|
for l in ${locales}; do
|
|
if [[ ${l} == *.* ]]; then
|
|
enc=${l#*.}
|
|
else
|
|
enc="ISO-8859-1"
|
|
fi
|
|
case $(echo ${enc//-} | tr '[:upper:]' '[:lower:]') in
|
|
utf8) enc="UTF-8";;
|
|
esac
|
|
gen_locales+=("${l} ${enc}")
|
|
done
|
|
if [[ ${#gen_locales[@]} -gt 0 ]] ; then
|
|
# Force LC_ALL=C to workaround slow string parsing in bash
|
|
# with long multibyte strings. Newer setups have this fixed,
|
|
# but locale-gen doesn't need to be run in any locale in the
|
|
# first place, so just go with C to keep it fast.
|
|
chroot "${FLAGS_chroot}" env LC_ALL=C locale-gen -q -u \
|
|
-G "$(printf '%s\n' "${gen_locales[@]}")"
|
|
fi
|
|
}
|
|
|
|
setup_env() {
|
|
(
|
|
flock 200
|
|
|
|
# Make the lockfile writable for backwards compatibility.
|
|
chown ${SUDO_UID}:${SUDO_GID} "${LOCKFILE}"
|
|
|
|
# Refresh system config files in the chroot.
|
|
for copy_file in /etc/{hosts,localtime,resolv.conf}; do
|
|
if [ -f "${copy_file}" ] ; then
|
|
rm -f "${FLAGS_chroot}${copy_file}"
|
|
install -C -m644 "${copy_file}" "${FLAGS_chroot}${copy_file}"
|
|
fi
|
|
done
|
|
|
|
debug "Mounting chroot environment."
|
|
MOUNT_CACHE=$(echo $(awk '{print $2}' /proc/mounts))
|
|
|
|
# The cros_sdk script created a new filesystem namespace but the system
|
|
# default (namely on systemd hosts) may be for everything to be shared.
|
|
# Using 'slave' means we see global changes but cannot change global state.
|
|
mount --make-rslave /
|
|
|
|
setup_mount none "-t proc" /proc
|
|
setup_mount none "-t sysfs" /sys
|
|
setup_mount /dev "--bind" /dev
|
|
setup_mount /dev/pts "--bind" /dev/pts
|
|
setup_mount tmpfs "-t tmpfs -o nosuid,nodev,mode=755" /run
|
|
if [[ -d /run/shm && ! -L /run/shm ]]; then
|
|
setup_mount /run/shm "--bind" /run/shm
|
|
fi
|
|
mkdir -p /run/user/${SUDO_UID}
|
|
chown ${SUDO_UID}:${SUDO_GID} /run/user/${SUDO_UID}
|
|
|
|
# Do this early as it's slow and only needs basic mounts (above).
|
|
generate_locales &
|
|
|
|
setup_mount "${FLAGS_trunk}" "--rbind" "${CHROOT_TRUNK_DIR}"
|
|
|
|
debug "Setting up referenced repositories if required."
|
|
REFERENCE_DIR=$(git config --file \
|
|
"${FLAGS_trunk}/.repo/manifests.git/config" \
|
|
repo.reference)
|
|
if [ -n "${REFERENCE_DIR}" ]; then
|
|
|
|
ALTERNATES="${FLAGS_trunk}/.repo/alternates"
|
|
|
|
# Ensure this directory exists ourselves, and has the correct ownership.
|
|
user_mkdir "${ALTERNATES}"
|
|
|
|
unset ALTERNATES
|
|
|
|
IFS=$'\n';
|
|
required=( $( sudo -u "${SUDO_USER}" -- \
|
|
"${FLAGS_trunk}/chromite/lib/rewrite_git_alternates.py" \
|
|
"${FLAGS_trunk}" "${REFERENCE_DIR}" "${CHROOT_TRUNK_DIR}" ) )
|
|
unset IFS
|
|
|
|
setup_mount "${FLAGS_trunk}/.repo/chroot/alternates" --bind \
|
|
"${CHROOT_TRUNK_DIR}/.repo/alternates"
|
|
|
|
# Note that as we're bringing up each referened repo, we also
|
|
# mount bind an empty directory over its alternates. This is
|
|
# required to suppress git from tracing through it- we already
|
|
# specify the required alternates for CHROOT_TRUNK_DIR, no point
|
|
# in having git try recursing through each on their own.
|
|
#
|
|
# Finally note that if you're unfamiliar w/ chroot/vfs semantics,
|
|
# the bind is visible only w/in the chroot.
|
|
user_mkdir ${FLAGS_trunk}/.repo/chroot/empty
|
|
position=1
|
|
for x in "${required[@]}"; do
|
|
base="${CHROOT_TRUNK_DIR}/.repo/chroot/external${position}"
|
|
setup_mount "${x}" "--bind" "${base}"
|
|
if [ -e "${x}/.repo/alternates" ]; then
|
|
setup_mount "${FLAGS_trunk}/.repo/chroot/empty" "--bind" \
|
|
"${base}/.repo/alternates"
|
|
fi
|
|
position=$(( ${position} + 1 ))
|
|
done
|
|
unset required position base
|
|
fi
|
|
unset REFERENCE_DIR
|
|
|
|
chroot_cache='/var/cache/chromeos-cache'
|
|
debug "Setting up shared cache dir directory."
|
|
user_mkdir "${FLAGS_cache_dir}"/distfiles/{target,host}
|
|
user_mkdir "${FLAGS_chroot}/${chroot_cache}"
|
|
setup_mount "${FLAGS_cache_dir}" "--bind" "${chroot_cache}"
|
|
# TODO(build): remove this as of 12/01/12.
|
|
# Because of how distfiles -> cache_dir was deployed, if this isn't
|
|
# a symlink, we *know* the ondisk pathways aren't compatible- thus
|
|
# fix it now.
|
|
distfiles_path="${FLAGS_chroot}/var/cache/distfiles"
|
|
if [ ! -L "${distfiles_path}" ]; then
|
|
# While we're at it, ensure the var is exported w/in the chroot; it
|
|
# won't exist if distfiles isn't a symlink.
|
|
p="${FLAGS_chroot}/etc/profile.d/chromeos-cachedir.sh"
|
|
rm -rf "${distfiles_path}"
|
|
ln -s chromeos-cache/distfiles "${distfiles_path}"
|
|
mkdir -p -m 775 "${p%/*}"
|
|
echo 'export CHROMEOS_CACHEDIR=${chroot_cache}' > "${p}"
|
|
chmod 0644 "${p}"
|
|
fi
|
|
|
|
user_mkdir "${FLAGS_chroot}/home/${SUDO_USER}/.ssh"
|
|
if [ $FLAGS_ssh_agent -eq $FLAGS_TRUE ]; then
|
|
# Clean up previous ssh agents.
|
|
rmdir "${FLAGS_chroot}"/tmp/ssh-* 2>/dev/null
|
|
|
|
if [ -n "${SSH_AUTH_SOCK}" -a -d "${SUDO_HOME}/.ssh" ]; then
|
|
# Don't try to bind mount the ssh agent dir if it has gone stale.
|
|
ASOCK=${SSH_AUTH_SOCK%/*}
|
|
if [ -d "${ASOCK}" ]; then
|
|
setup_mount "${ASOCK}" "--bind" "${ASOCK}"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ -d "$SUDO_HOME/.subversion" ]]; then
|
|
TARGET="/home/${SUDO_USER}/.subversion"
|
|
setup_mount "${SUDO_HOME}/.subversion" "--bind" "${TARGET}"
|
|
# Symbolic-link the .subversion directory so sandboxed subversion.class
|
|
# clients can use it.
|
|
for d in \
|
|
"${FLAGS_cache_dir}"/distfiles/{host,target}/svn-src/"${SUDO_USER}"; do
|
|
if [[ ! -L "${d}/.subversion" ]]; then
|
|
rm -rf "${d}/.subversion"
|
|
user_mkdir "${d}"
|
|
user_symlink /home/${SUDO_USER}/.subversion "${d}/.subversion"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Mount GnuPG's data directory for signing uploads
|
|
if [[ -d "$SUDO_HOME/.gnupg" ]]; then
|
|
debug "Mounting GnuPG"
|
|
setup_mount "${SUDO_HOME}/.gnupg" "--bind" "/home/${SUDO_USER}/.gnupg"
|
|
|
|
# bind mount the gpg agent dir if available
|
|
GPG_AGENT_DIR="${GPG_AGENT_INFO%/*}"
|
|
if [[ -d "$GPG_AGENT_DIR" ]]; then
|
|
setup_mount "$GPG_AGENT_DIR" "--bind" "$GPG_AGENT_DIR"
|
|
fi
|
|
fi
|
|
|
|
# Mount additional directories as specified in .local_mounts file.
|
|
local local_mounts="${FLAGS_trunk}/src/scripts/.local_mounts"
|
|
if [[ -f ${local_mounts} ]]; then
|
|
info "Mounting local folders (read-only for safety concern)"
|
|
# format: mount_source
|
|
# or mount_source mount_point
|
|
# or # comments
|
|
local mount_source mount_point
|
|
while read mount_source mount_point; do
|
|
if [[ -z ${mount_source} ]]; then
|
|
continue
|
|
fi
|
|
# if only source is assigned, use source as mount point.
|
|
: ${mount_point:=${mount_source}}
|
|
debug " mounting ${mount_source} on ${mount_point}"
|
|
setup_mount "${mount_source}" "--bind" "${mount_point}"
|
|
# --bind can't initially be read-only so we have to do it via remount.
|
|
setup_mount "" "-o remount,ro" "${mount_point}"
|
|
done < <(sed -e 's:#.*::' "${local_mounts}")
|
|
fi
|
|
|
|
CHROME_ROOT="$(readlink -f "$FLAGS_chrome_root" || :)"
|
|
if [ -z "$CHROME_ROOT" ]; then
|
|
CHROME_ROOT="$(cat "${FLAGS_chroot}${CHROME_ROOT_CONFIG}" \
|
|
2>/dev/null || :)"
|
|
CHROME_ROOT_AUTO=1
|
|
fi
|
|
if [[ -n "$CHROME_ROOT" ]]; then
|
|
if [[ ! -d "${CHROME_ROOT}/src" ]]; then
|
|
error "Not mounting chrome source"
|
|
rm -f "${FLAGS_chroot}${CHROME_ROOT_CONFIG}"
|
|
if [[ ! "$CHROME_ROOT_AUTO" ]]; then
|
|
exit 1
|
|
fi
|
|
else
|
|
debug "Mounting chrome source at: $INNER_CHROME_ROOT"
|
|
echo $CHROME_ROOT > "${FLAGS_chroot}${CHROME_ROOT_CONFIG}"
|
|
setup_mount "$CHROME_ROOT" --bind "$INNER_CHROME_ROOT"
|
|
fi
|
|
fi
|
|
|
|
# Install fuse module. Skip modprobe when possible for slight
|
|
# speed increase when initializing the env.
|
|
if [ -c "${FUSE_DEVICE}" ] && ! grep -q fuse /proc/filesystems; then
|
|
modprobe fuse 2> /dev/null ||\
|
|
warn "-- Note: modprobe fuse failed. gmergefs will not work"
|
|
fi
|
|
|
|
# Fix permissions on ccache tree. If this is a fresh chroot, then they
|
|
# might not be set up yet. Or if the user manually `rm -rf`-ed things,
|
|
# we need to reset it. Otherwise, gcc itself takes care of fixing things
|
|
# on demand, but only when it updates.
|
|
ccache_dir="${FLAGS_chroot}/var/cache/distfiles/ccache"
|
|
if [[ ! -d ${ccache_dir} ]]; then
|
|
mkdir -p -m 2775 "${ccache_dir}"
|
|
fi
|
|
find -H "${ccache_dir}" -type d -exec chmod 2775 {} + &
|
|
find -H "${ccache_dir}" -gid 0 -exec chgrp 250 {} + &
|
|
|
|
# Certain files get copied into the chroot when entering.
|
|
for fn in "${FILES_TO_COPY_TO_CHROOT[@]}"; do
|
|
copy_into_chroot_if_exists "${SUDO_HOME}/${fn}" "/home/${SUDO_USER}/${fn}"
|
|
done
|
|
promote_api_keys
|
|
|
|
# Fix permissions on shared memory to allow non-root users access to POSIX
|
|
# semaphores.
|
|
chmod -R 777 "${FLAGS_chroot}/dev/shm"
|
|
|
|
# Have found a few chroots where ~/.gsutil is owned by root:root, probably
|
|
# as a result of old gsutil or tools. This causes permission errors when
|
|
# gsutil cp tries to create its cache files, so ensure the user can
|
|
# actually write to their directory.
|
|
gsutil_dir="${FLAGS_chroot}/home/${SUDO_USER}/.gsutil"
|
|
if [ -d "${gsutil_dir}" ]; then
|
|
chown -R ${SUDO_UID}:${SUDO_GID} "${gsutil_dir}"
|
|
fi
|
|
) 200>>"$LOCKFILE" || die "setup_env failed"
|
|
}
|
|
|
|
setup_env
|
|
|
|
CHROOT_PASSTHRU=(
|
|
"BUILDBOT_BUILD=$FLAGS_build_number"
|
|
"CHROMEOS_RELEASE_APPID=${CHROMEOS_RELEASE_APPID:-{DEV-BUILD}}"
|
|
"EXTERNAL_TRUNK_PATH=${FLAGS_trunk}"
|
|
)
|
|
|
|
# Add the whitelisted environment variables to CHROOT_PASSTHRU.
|
|
load_environment_whitelist
|
|
for var in "${ENVIRONMENT_WHITELIST[@]}" ; do
|
|
[ "${!var+set}" = "set" ] && CHROOT_PASSTHRU+=( "${var}=${!var}" )
|
|
done
|
|
|
|
# Set up GIT_PROXY_COMMAND so git:// URLs automatically work behind a proxy.
|
|
if [[ -n "${all_proxy}" || -n "${https_proxy}" || -n "${http_proxy}" ]]; then
|
|
CHROOT_PASSTHRU+=(
|
|
"GIT_PROXY_COMMAND=${CHROOT_TRUNK_DIR}/src/scripts/bin/proxy-gw"
|
|
)
|
|
fi
|
|
|
|
# Run command or interactive shell. Also include the non-chrooted path to
|
|
# the source trunk for scripts that may need to print it (e.g.
|
|
# build_image.sh).
|
|
|
|
if [ $FLAGS_early_make_chroot -eq $FLAGS_TRUE ]; then
|
|
cmd=( /bin/bash -l -c 'env "$@"' -- )
|
|
elif [ ! -x "${FLAGS_chroot}/usr/bin/sudo" ]; then
|
|
# Complain that sudo is missing.
|
|
error "Failing since the chroot lacks sudo."
|
|
error "Requested enter_chroot command was: $@"
|
|
exit 127
|
|
else
|
|
cmd=( sudo -i -u "${SUDO_USER}" )
|
|
fi
|
|
|
|
cmd+=( "${CHROOT_PASSTHRU[@]}" "$@" )
|
|
exec chroot "${FLAGS_chroot}" "${cmd[@]}"
|