#!/bin/sh # Copyright (C) 2014-2021 Daniel Baumann # # SPDX-License-Identifier: GPL-3.0+ # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set -e PROJECT="open-infrastructure" SOFTWARE="compute-tools" PROGRAM="container" SCRIPT="${0}" HOOKS="/etc/${PROJECT}/${PROGRAM}/hooks" KEYS="/etc/${PROJECT}/${PROGRAM}/keys" MACHINES="/var/lib/machines" CACHE="/var/cache/${PROJECT}/${PROGRAM}/system" Parameters () { GETOPT_LONGOPTIONS="bind:,bind-ro:,script:,name:,architecture:,clean,password:,server:,setup:,system:," GETOPT_OPTIONS="b:,s:,n:,a:,p:" PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${SCRIPT} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" if [ "${?}" != "0" ] then echo "'${SCRIPT}': getopt exit" >&2 exit 1 fi eval set -- "${PARAMETERS}" while true do case "${1}" in -b|--bind) # ignore shift 2 ;; --bind-ro) # ignore shift 2 ;; --cnt.auto) # ignore shift 2 ;; --cnt.container-server) # ignore shift 2 ;; -s|--script) # ignore shift 2 ;; -n|--name) NAME="${2}" shift 2 ;; -a|--architecture) ARCHITECTURE="${2}" shift 2 ;; --clean) CLEAN="true" shift 1 ;; -p|--password) PASSWORD="${2}" shift 2 ;; --server) SERVER="${2}" shift 2 ;; --setup) SETUP="${2}" shift 2 ;; --system) SYSTEM="${2}" shift 2 ;; --) shift 1 break ;; *) echo "'${SCRIPT}': getopt error" >&2 exit 1 ;; esac done } Usage () { echo "Usage: container create -n|--name NAME -s|--script ${SCRIPT} -- [--clean] [-p|--password PASSWORD] [--server SERVER] [--setup SETUP] [--system SYSTEM]" >&2 exit 1 } Parameters "${@}" if [ -z "${NAME}" ] then Usage fi if [ -e "${MACHINES}/${NAME}" ] then echo "'${NAME}': container already exists" >&2 exit 1 fi if [ ! -x /usr/bin/curl ] then echo "'${NAME}': /usr/bin/curl - no such file." >&2 exit 1 fi if [ "$(id -u)" -ne 0 ] then echo "'${NAME}': need root privileges" >&2 exit 1 fi COMPRESSIONS="" if [ -x /usr/bin/lzip ] then COMPRESSIONS="${COMPRESSIONS} lz" fi if [ -x /usr/bin/xz ] then COMPRESSIONS="${COMPRESSIONS} xz" fi if [ -x /bin/gzip ] then COMPRESSIONS="${COMPRESSIONS} gz" fi if [ -z "${COMPRESSIONS}" ] then echo "'${NAME}': no supported compressor available (lz, xz, gz)." exit 1 fi SERVER="${SERVER:-https://get.open-infrastructure.net/system/container/debian}" PASSWORD="${PASSWORD:-$(dd if=/dev/urandom bs=12 count=1 2> /dev/null | base64)}" VERSION="$(container version)" export SERVER Debconf () { # Configure local debconf mkdir -p "${DEBCONF_TMPDIR}/debconf" cat > "${DEBCONF_TMPDIR}/debconf.systemrc" << EOF Config: configdb Templates: templatedb Name: config Driver: File Mode: 644 Reject-Type: password Filename: ${DEBCONF_TMPDIR}/debconf/config.dat Name: passwords Driver: File Mode: 600 Backup: false Required: false Accept-Type: password Filename: ${DEBCONF_TMPDIR}/debconf/passwords.dat Name: configdb Driver: Stack Stack: config, passwords Name: templatedb Driver: File Mode: 644 Filename: ${DEBCONF_TMPDIR}/debconf/templates.dat EOF DEBCONF_SYSTEMRC="${DEBCONF_TMPDIR}/debconf.systemrc" export DEBCONF_SYSTEMRC } # Pre hooks for FILE in "${HOOKS}/pre-${SCRIPT}".* "${HOOKS}/${NAME}.pre-${SCRIPT}" do if [ -x "${FILE}" ] then "${FILE}" fi done # Run # FIXME: default server via configuration file CURL_OPTIONS="" if curl -V | grep -qs http2 then CURL_OPTIONS="${CURL_OPTIONS} --http2" fi if [ -z "${SYSTEM}" ] then # Downloading container list if curl --fail --head --output /dev/null --silent "${SERVER}/container-list.txt" then mkdir -p "/tmp/${SOFTWARE}" DEBCONF_TMPDIR="$(mktemp -d -p "/tmp/${SOFTWARE}" -t $(basename ${0}).XXXX)" export DEBCONF_TMPDIR if [ -z "${ARCHITECTURE}" ] then case "$(dpkg --print-architecture)" in amd64) GREP_PATTERN="(amd64|i386)" ;; esac fi GREP_PATTERN="${GREP_PATTERN:-${ARCHITECTURE}}" echo "Downloading $(echo ${SERVER} | awk -F/ '{ print $3 }') container list" curl --fail --location --progress-bar --user-agent ${SOFTWARE}/${VERSION} ${CURL_OPTIONS} \ "${SERVER}/container-list.txt" | grep -E "${GREP_PATTERN}" > "${DEBCONF_TMPDIR}/container-list.txt" umask 0022 Debconf # Run debconf parts for DEBCONF_SCRIPT in /usr/share/${PROJECT}/${PROGRAM}/scripts/curl.d/* do if [ -x "${DEBCONF_SCRIPT}" ] then "${DEBCONF_SCRIPT}" fi done # Read-in configuration from debconf . "${DEBCONF_TMPDIR}/debconf.default" # Remove debconf temporary files rm --preserve-root --one-file-system -rf "${DEBCONF_TMPDIR}" rmdir --ignore-fail-on-non-empty "/tmp/${SOFTWARE}" 2>&1 || true fi fi for COMPRESSION in ${COMPRESSIONS} do if curl --fail --head --output /dev/null --silent "${SERVER}/${SYSTEM}.${COMPRESSION}" then SYSTEM="${SYSTEM}.${COMPRESSION}" break fi done # Downloading container files mkdir -p "${CACHE}" SETUP="${SETUP:-$(echo ${SYSTEM} | sed -e 's|.system.tar.|.setup.tar.|')}" for FILE in "${SYSTEM}" "${SYSTEM}.sign" "${SYSTEM}.sha512" \ "${SETUP}" "${SETUP}.sign" "${SETUP}.sha512" do if curl --fail --head --output /dev/null --silent "${SERVER}/${FILE}" then case "${FILE}" in *.sha512) if [ -e "${CACHE}/$(basename ${FILE} .sha512).sign" ] then continue fi ;; esac if [ -e "${CACHE}/${FILE}" ] then CURL_TIME_COND="--time-cond ${CACHE}/${FILE}" else CURL_TIME_COND="" fi echo "Downloading ${FILE}" curl --fail --location --progress-bar --user-agent ${SOFTWARE}/${VERSION} ${CURL_OPTIONS} ${CURL_TIME_COND} \ "${SERVER}/${FILE}" -o "${CACHE}/${FILE}" fi done cd "${CACHE}" for FILE in "${SYSTEM}" "${SETUP}" do if [ ! -e "${FILE}" ] then continue fi if [ -e "${FILE}.sign" ] then echo -n "Verifying ${FILE}:" set +e gpg --homedir "${KEYS}" --verify "${FILE}.sign" "${FILE}" > /dev/null 2>&1 GNUPG="${?}" set -e case "${GNUPG}" in 0) echo " gpg ok." continue ;; *) echo " gpg failed." exit 1 ;; esac elif [ -e "${FILE}.sha512" ] then echo -n "Verifying ${FILE}:" set +e sha512sum --check "${FILE}.sha512" --status SHA512SUM="${?}" set -e case "${SHA512SUM}" in 0) echo " sha512 ok." ;; *) echo " sha512 failed." exit 1 ;; esac fi done cd "${OLDPWD}" case "${SYSTEM}" in *.gz) TAR_OPTIONS="--gzip" if [ ! -e /bin/gzip ] then echo -en "\n" echo "'${NAME}': /bin/lzip - no such file." >&2 exit 1 fi ;; *.lz) TAR_OPTIONS="--lzip" if [ ! -e /usr/bin/lzip ] then echo -en "\n" echo "'${NAME}': /usr/bin/lzip - no such file." >&2 exit 1 fi ;; *.xz) TAR_OPTIONS="--xz" if [ ! -e /usr/bin/xz ] then echo -en "\n" echo "'${NAME}': /usr/bin/xz - no such file." >&2 exit 1 fi ;; *) TAR_OPTIONS="" ;; esac for FILE in "${SYSTEM}" "${SETUP}" do if [ ! -e "${CACHE}/${FILE}" ] then continue fi case "${FILE}" in *.system.tar.*) DIRECTORY="${MACHINES}/${NAME}" ;; *.setup.tar.*) DIRECTORY="${MACHINES}/${NAME}/setup" ;; esac mkdir -p "${DIRECTORY}" if [ -e /usr/bin/pv ] then echo "Unpacking ${FILE}" pv --format '%p' --width 77 "${CACHE}/${FILE}" | tar xf - ${TAR_OPTIONS} -C "${DIRECTORY}" --strip 1 else echo -n "Unpacking ${FILE}:" tar xf "${CACHE}/${FILE}" ${TAR_OPTIONS} -C "${DIRECTORY}" --strip 1 echo " ok." fi done if [ -x "${MACHINES}/${NAME}/setup/container" ] then chroot "${MACHINES}/${NAME}" /usr/bin/env -i \ LC_ALL="C" PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games" TERM="${TERM}" \ DEBIAN_FRONTEND="dialog" DEBIAN_PRIORITY="low" \ DEBCONF_NONINTERACTIVE_SEEN="true" DEBCONF_NOWARNINGS="true" \ NAME="${NAME}" \ /setup/container rm -rf "${MACHINES}/${NAME}/setup" fi # Creating machine-id chroot "${MACHINES}/${NAME}" systemd-machine-id-setup > /dev/null 2>&1 # Setting hostname echo "${NAME}" > "${MACHINES}/${NAME}/etc/hostname" # Copying resolv.conf cp -L /etc/resolv.conf "${MACHINES}/${NAME}/etc/resolv.conf" # Setting root password echo root:${PASSWORD} | chroot "${MACHINES}/${NAME}" chpasswd echo "${NAME}: root password set to '${PASSWORD}'." # Remove cache case "${CLEAN}" in true) rm -f "${CACHE}/${SYSTEM}" "${CACHE}/${SYSTEM}.sha512" rm -f "${CACHE}/${SETUP}" "${CACHE}/${SETUP}.sha512" ;; esac # Post hooks for FILE in "${HOOKS}/post-${SCRIPT}".* "${HOOKS}/${NAME}.post-${SCRIPT}" do if [ -x "${FILE}" ] then "${FILE}" fi done