#!/usr/bin/sh # # SPDX-License-Identifier: LGPL-2.1+ # # Client script for LXC container images. set -eu LXC_HOOK_DIR=/usr/share/lxc/hooks LXC_TEMPLATE_CONFIG=/usr/share/lxc/config LXC_NAME= LXC_PATH= LXC_ROOTFS= LXC_METADATA= LXC_FSTREE= MODE="system" COMPAT_LEVEL=5 EXCLUDES="" TEMPLATE_FILES="" # Make sure the usual locations are in PATH export PATH="$PATH:/usr/sbin:/usr/bin:/sbin:/bin" in_userns() { [ -e /proc/self/uid_map ] || { echo no; return; } while read -r line; do fields="$(echo "$line" | awk '{ print $1 " " $2 " " $3 }')" if [ "${fields}" = "0 0 4294967295" ]; then echo no; return; fi if echo "${fields}" | grep -q " 0 1$"; then echo userns-root; return; fi done < /proc/self/uid_map if [ -e /proc/1/uid_map ]; then if [ "$(cat /proc/self/uid_map)" = "$(cat /proc/1/uid_map)" ]; then echo userns-root return fi fi echo yes } usage() { cat < ]: The container name [ --path ]: The path to the container [ --rootfs ]: The path to the container's rootfs [ --mapped-uid ]: A uid map (user namespaces) [ --mapped-gid ]: A gid map (user namespaces) EOF return 0 } # Show usage and exit if invalid arguments are passed if ! options=$(getopt -o hm:f: -l help,metadata:,fstree:,no-dev:,name:,path:,rootfs:,mapped-uid:,mapped-gid: -- "$@"); then usage exit 1 fi eval set -- "$options" while :; do case "$1" in -h|--help) usage && exit 1;; --name) LXC_NAME="$2"; shift 2;; --path) LXC_PATH="$2"; shift 2;; --rootfs) LXC_ROOTFS="$2"; shift 2;; -m|--metadata) LXC_METADATA="$2"; shift 2;; -f|--fstree) LXC_FSTREE="$2"; shift 2;; --no-dev) EXCLUDES="${EXCLUDES} --exclude=./dev/*"; shift 1;; --mapped-uid) LXC_MAPPED_UID="$2"; shift 2;; --mapped-gid) LXC_MAPPED_GID="$2"; shift 2;; *) break;; esac done # Check for required binaries for bin in tar xz; do if ! command -V "${bin}" >/dev/null 2>&1; then echo "ERROR: Missing required tool: ${bin}" 1>&2 exit 1 fi done cleanup() { if [ -d "${LOCAL_TEMP}" ]; then rm -Rf "${LOCAL_TEMP}" fi } # Trap all exit signals trap cleanup EXIT HUP INT TERM USERNS="$(in_userns)" if [ "${USERNS}" != "no" ]; then if [ "${USERNS}" = "yes" ]; then if [ -z "${LXC_MAPPED_UID}" ] || [ "${LXC_MAPPED_UID}" = "-1" ]; then echo "ERROR: In a user namespace without a map" 1>&2 exit 1 fi MODE="user" else MODE="user" fi fi relevant_file() { # Reads the supplied file name from LOCAL_TEMP.. # If the file exists in $LOCAL_TEMP/$FILENAME.$COMPAT_LEVEL, it will be used. # The default COMPAT_LEVEL is 5. # If the file exists in $LOCAL_TEMP/$FILENAME-$MODE, it will be used. # The default MODE is "system". # If a mode or compatibility level specific file does not exist, the # passed file name will be used. FILE_PATH="${LOCAL_TEMP}/$1" if [ -e "${FILE_PATH}-${MODE}" ]; then FILE_PATH="${FILE_PATH}-${MODE}" fi if [ -e "${FILE_PATH}.${COMPAT_LEVEL}" ]; then FILE_PATH="${FILE_PATH}.${COMPAT_LEVEL}" fi echo "${FILE_PATH}" } create_build_dir() { # Create temporary build directory. # If mktemp is not available, use /tmp/lxc-local.$$ if ! command -V mktemp >/dev/null 2>&1; then LOCAL_TEMP=/tmp/lxc-local.$$ mkdir -p "${LOCAL_TEMP}" else LOCAL_TEMP=$(mktemp -d) fi echo "Using local temporary directory ${LOCAL_TEMP}" } process_excludes() { # If the file ${LXC_PATH}/excludes exists, it will be used as a list of # files to exclude from the tarball. excludelist=$(relevant_file excludes) if [ -f "${excludelist}" ]; then while read -r line; do EXCLUDES="${EXCLUDES} --exclude=${line}" done < "${excludelist}" fi } record_template_file() { if [ -z "${TEMPLATE_FILES}" ]; then TEMPLATE_FILES="$1" else TEMPLATE_FILES="${TEMPLATE_FILES};$1" fi } extract_config() { # lxc-create will automatically create a config file at ${LXC_PATH}/config. # This function extracts the network config, and any remaining lxc config # The extracted config is stored in ${LXC_PATH}/config-network and ${LXC_PATH}/config-extra # The config will be merged later. # Extract all the network config entries sed -i -e "/lxc.net.0/{w ${LXC_PATH}/config-network" -e "d}" "${LXC_PATH}/config" if [ -e "${LXC_PATH}/config-network" ]; then echo "Extracted network config to: ${LXC_PATH}/config-network" cat "${LXC_PATH}/config-network" fi # Extract any other config entry sed -i -e "/lxc./{w ${LXC_PATH}/config-extra" -e "d}" "${LXC_PATH}/config" if [ -e "${LXC_PATH}/config-extra" ]; then echo "Extracted additional config to: ${LXC_PATH}/config-extra" cat "${LXC_PATH}/config-extra" fi } add_container_config() { # Adds the contents of the supplied metadata config to the container config { echo "" echo "# Distribution configuration" cat "$configfile" } >> "${LXC_PATH}/config" } add_extra_config() { # Add the container-specific config { echo "" echo "# Container specific configuration" if [ -e "${LXC_PATH}/config-extra" ]; then cat "${LXC_PATH}/config-extra" rm "${LXC_PATH}/config-extra" fi } >> "${LXC_PATH}/config" } add_network_config() { # Re-add the previously removed network config if [ -e "${LXC_PATH}/config-network" ]; then { echo "" echo "# Network configuration" cat "${LXC_PATH}/config-network" rm "${LXC_PATH}/config-network" } >> "${LXC_PATH}/config" fi } process_config() { # Process the supplied config file if it exists. configfile="$(relevant_file config)" if [ ! -e "${configfile}" ]; then echo "ERROR: metadata tarball is missing the configuration file" 1>&2 return fi extract_config add_container_config add_extra_config add_network_config record_template_file "${LXC_PATH}/config" } process_fstab() { # Process the supplied fstab file if it exists. # Add the fstab file to template files so substitions can be made. fstab="$(relevant_file fstab)" if [ -e "${fstab}" ]; then echo "lxc.mount.fstab = ${LXC_PATH}/fstab" >> "${LXC_PATH}/config" cp "${fstab}" "${LXC_PATH}/fstab" record_template_file "${LXC_PATH}/fstab" fi } process_templates() { # Look for extra templates templates="$(relevant_file templates)" if [ -e "${templates}" ]; then while read -r line; do fullpath="${LXC_ROOTFS}/${line}" record_template_file "${fullpath}" done < "${templates}" fi } unpack_metadata() { # Unpack file that contains the container metadata # If the file does not exist, just warn and continue. if [ -z "${LXC_METADATA}" ]; then echo "Metadata file was not passed" 2>&1 return fi if [ ! -f "${LXC_METADATA}" ]; then echo "Metadata file does not exist: ${LXC_METADATA}" 2>&1 return fi echo "Using metadata file: ${LXC_METADATA}" if ! tar -axf "${LXC_METADATA}" -C "${LOCAL_TEMP}"; then echo "Unable to unpack metadata file: ${LXC_METADATA}" 2>&1 exit 1 fi echo "Unpacked metadata file: ${LXC_METADATA}" process_excludes process_config process_fstab process_templates } set_utsname() { # Set the container's hostname echo "lxc.uts.name = ${LXC_NAME}" >> "${LXC_PATH}/config" } prepare_rootfs() { # Additional configuration that must be done on the rootfs. # Always add /dev/pts as /dev may be excluded. mkdir -p "${LXC_ROOTFS}/dev/pts/" } unpack_rootfs() { # Unpack the rootfs if [ -z "${LXC_FSTREE}" ]; then echo "ERROR: Rootfs file was not passed" 2>&1 exit 1 fi if [ ! -f "${LXC_FSTREE}" ]; then echo "ERROR: Rootfs file does not exist: ${LXC_FSTREE}" 2>&1 exit 1 fi echo "Using rootfs file: ${LXC_FSTREE}" # Do not surround ${EXCLUDES} by quotes. This does not work. The solution could # use array but this is not POSIX compliant. The only POSIX compliant solution # is to use a function wrapper, but the latter can't be used here as the args # are dynamic. We thus need to ignore the warning brought by shellcheck. # shellcheck disable=SC2086 if [ -n "${EXCLUDES}" ]; then echo "Excludes: ${EXCLUDES}" fi tar --anchored ${EXCLUDES} --numeric-owner --xattrs-include='*' -xpaf "${LXC_FSTREE}" -C "${LXC_ROOTFS}" prepare_rootfs } replace_template_vars() { # Replace variables in all templates OLD_IFS=${IFS} IFS=";" for file in ${TEMPLATE_FILES}; do [ ! -f "${file}" ] && continue sed -i "s#LXC_NAME#${LXC_NAME}#g" "${file}" sed -i "s#LXC_PATH#${LXC_PATH}#g" "${file}" sed -i "s#LXC_ROOTFS#${LXC_ROOTFS}#g" "${file}" sed -i "s#LXC_TEMPLATE_CONFIG#${LXC_TEMPLATE_CONFIG}#g" "${file}" sed -i "s#LXC_HOOK_DIR#${LXC_HOOK_DIR}#g" "${file}" done IFS=${OLD_IFS} } fix_tty() { # prevent mingetty from calling vhangup(2) since it fails with userns on CentOS / Oracle if [ -f "${LXC_ROOTFS}/etc/init/tty.conf" ]; then echo "Patching ${LXC_ROOTFS}/etc/init/tty.conf to prevent mingetty from calling vhangup." sed -i 's|mingetty|mingetty --nohangup|' "${LXC_ROOTFS}/etc/init/tty.conf" fi } display_creation_message() { if [ -e "$(relevant_file create-message)" ]; then echo "" echo "---" cat "$(relevant_file create-message)" fi } create_build_dir unpack_metadata unpack_rootfs set_utsname replace_template_vars fix_tty display_creation_message exit 0