#!/bin/sh # container-tools - Manage systemd-nspawn containers # Copyright (C) 2014-2017 Daniel Baumann # # 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 SCRIPT="${0}" export SCRIPT CONFIG="/etc/container-tools/config" HOOKS="/etc/container-tools/hooks" MACHINES="/var/lib/machines" Parameters () { GETOPT_LONGOPTIONS="bind:,bind-ro:,script:,name:,preseed-file:" GETOPT_OPTIONS="b:,s:,n:,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 ;; --preseed-file) PRESEED_FILE="${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} -- [-p|--preseed-file FILE]" >&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/sbin/debootstrap ] then echo "'${NAME}': /usr/sbin/debootstrap - no such file." >&2 exit 1 fi if [ "$(id -u)" -ne 0 ] then echo "'${NAME}': need root privileges" >&2 exit 1 fi Chroot () { CHROOT="${1}" shift chroot "${CHROOT}" /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="${DEBCONF_FRONTEND}" DEBIAN_PRIORITY="${DEBCONF_PRIORITY}" \ DEBCONF_NONINTERACTIVE_SEEN="true" DEBCONF_NOWARNINGS="true" \ ftp_proxy="${ftp_proxy}" http_proxy="${http_proxy}" \ ${@} } CIDR () { x=${1##*255.} set -- 0^^^128^192^224^240^248^252^254^ $(( (${#1} - ${#x})*2 )) ${x%%.*} x=${1%%$3*} echo $(( $2 + (${#x}/4) )) } Upgrade_system () { DIRECTORY="${1}" # Mount pseudo-filesystems mount -o bind /dev/pts "${DIRECTORY}/dev/pts" mount -o bind /proc "${DIRECTORY}/proc" mount -o bind /sys "${DIRECTORY}/sys" # Bind mount systems apt cache mount -o bind /var/cache/apt/archives "${DIRECTORY}/var/cache/apt/archives" # Disable dpkg syncing cat > "${DIRECTORY}/etc/dpkg/dpkg.cfg.d/container-tools" << EOF force-unsafe-io EOF # Create policy-rc.d file cat > "${DIRECTORY}/usr/sbin/policy-rc.d" << EOF #!/bin/sh echo "All runlevel operations denied by policy" >&2 exit 101 EOF chmod 0755 "${DIRECTORY}/usr/sbin/policy-rc.d" # Upgrade system Chroot "${DIRECTORY}" "apt-get update" Chroot "${DIRECTORY}" "apt-get --yes --option APT::Force-LoopBreak=true --option Dpkg::Options::=--force-confnew upgrade" Chroot "${DIRECTORY}" "apt-get --yes --option APT::Force-LoopBreak=true --option Dpkg::Options::=--force-confnew dist-upgrade" # Install systemd support packages Chroot "${DIRECTORY}" "apt-get --yes install dbus libpam-systemd systemd-sysv" # Unmount systems apt cache umount "${DIRECTORY}/var/cache/apt/archives" Chroot "${DIRECTORY}" "apt-get clean" } Cleanup_system () { DIRECTORY="${1}" Chroot "${DIRECTORY}" "apt-get --yes --purge autoremove" Chroot "${DIRECTORY}" "apt-get clean" # Cleanup rm -f "${DIRECTORY}/etc/dpkg/dpkg.cfg.d/container-tools" rm -f "${DIRECTORY}/usr/sbin/policy-rc.d" # Unmount pseudo-filesystems umount "${DIRECTORY}/dev/pts" umount "${DIRECTORY}/proc" umount "${DIRECTORY}/sys" } Debconf () { # Configure local debconf mkdir -p /tmp/container-tools DEBCONF_TMPDIR="$(mktemp -d -p /tmp/container-tools -t $(basename ${0}).XXXX)" export DEBCONF_TMPDIR 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 } Debootstrap () { DIRECTORY="${1}" INCLUDE="dbus" if echo "${MIRROR}" | grep -qs '^https' || \ echo "{PARENT_MIRROR}" | grep -qs '^https' then INCLUDE="${INCLUDE},apt-transport-https,ca-certificates" fi mkdir -p "$(dirname ${DIRECTORY})" debootstrap --verbose --arch=${ARCHITECTURE} --components=${PARENT_ARCHIVE_AREAS} --include=${INCLUDE} \ ${PARENT_DISTRIBUTION} "${DIRECTORY}" ${PARENT_MIRROR} } Configure_apt () { DIRECTORY="${1}" # Configure apt rm -f "${DIRECTORY}/etc/apt/sources.list" PARENT_AREA="$(echo ${PARENT_ARCHIVE_AREAS} | sed -e 's|,| |g')" PARENT_DIST="$(echo ${PARENT_DISTRIBUTION} | sed -e 's|-backports||')" echo "deb ${PARENT_MIRROR} ${PARENT_DIST} ${PARENT_AREA}" > "${DIRECTORY}/etc/apt/sources.list.d/debian.list" case "${MODE}" in progress-linux) AREA="$(echo ${ARCHIVE_AREAS} | sed -e 's|,| |g')" DIST="$(echo ${DISTRIBUTION} | sed -e 's|-backports||')" echo "deb ${MIRROR} ${DIST} ${AREA}" > "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" case "${DISTRIBUTION}" in *-backports) echo "deb ${MIRROR} ${DIST}-backports ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" ;; esac cat > "${DIRECTORY}/etc/apt/preferences.d/progress-linux.pref" << EOF Package: * Pin: release n=${DIST} Pin-Priority: 999 Package: * Pin: release n=${DIST}-security Pin-Priority: 999 Package: * Pin: release n=${DIST}-updates Pin-Priority: 999 Package: * Pin: release n=${DIST}-extras Pin-Priority: 999 EOF case "${DISTRIBUTION}" in *-backports) cat >> "${DIRECTORY}/etc/apt/preferences.d/progress-linux.pref" << EOF Package: * Pin: release n=${DIST}-backports Pin-Priority: 999 Package: * Pin: release n=${DIST}-backports-extras Pin-Priority: 999 EOF ;; *) cat >> "${DIRECTORY}/etc/apt/preferences.d/progress-linux.pref" << EOF #Package: * #Pin: release n=${DIST}-backports #Pin-Priority: 999 #Package: * #Pin: release n=${DIST}-backports-extras #Pin-Priority: 999 EOF ;; esac # Import archive keys KEYS="project/pgp/archive-key-${DIST}.asc" for KEY in ${KEYS} do KEY_NAME="$(basename ${KEY})" echo "P: Fetching archive-key ${KEY_NAME}..." wget -q "${MIRROR}/${KEY}" -O "${DIRECTORY}/key.asc" wget -q "${MIRROR}/${KEY}.gpg" -O "${DIRECTORY}/key.asc.gpg" if [ -e /usr/bin/gpgv ] then if [ -e /usr/share/keyrings/debian-keyring.gpg ] || [ -e /usr/share/keyrings/debian-maintainers.gpg ] then KEY_VALID="" for KEYRING in /usr/share/keyrings/debian-keyring.gpg /usr/share/keyrings/debian-maintainers.gpg do if [ -e "${KEYRING}" ] then echo -n "I: Verifying archive-key ${KEY_NAME} against $(basename ${KEYRING} .gpg | sed -e 's|-keyring||') keyring..." # FIXME: doesn't work anymore when the template eventually be run with 'set -e' /usr/bin/gpgv --quiet --keyring ${KEYRING} "${DIRECTORY}/key.asc.gpg" "${DIRECTORY}/key.asc" > /dev/null 2>&1 && KEY_VALID="true" && break fi done case "${KEY_VALID}" in true) echo " successful." ;; *) echo " failed." return 1 ;; esac else echo "W: Skipping archive-key ${KEY_NAME} verification, debian-keyring not available..." fi else echo "W: Skipping archive-key ${KEY_NAME} verification, gpgv not available..." fi echo "P: Importing archive-key ${KEY_NAME}..." Chroot "${DIRECTORY}" "apt-key add key.asc" rm -f "${DIRECTORY}/key.asc" rm -f "${DIRECTORY}/key.asc.gpg" done Chroot "${DIRECTORY}" "apt-get update" ;; esac } Deconfigure_system () { DIRECTORY="${1}" # Configure fstab cat > "${DIRECTORY}/etc/fstab" << EOF # /etc/fstab: static file system information. # # Use 'blkid' to print the universally unique identifier for a # device; this may be used with UUID= as a more robust way to name devices # that works even if disks are added and removed. See fstab(5). # # EOF # Fix /etc/mtab rm -f "${DIRECTORY}/etc/mtab" ln -s /proc/self/mounts "${DIRECTORY}/etc/mtab" # Temporary hack for dpkg if [ -e "${DIRECTORY}/etc/dpkg/origins/default" ] then rm -f "${DIRECTORY}/etc/dpkg/origins/default" Chroot "${DIRECTORY}" "dpkg-reconfigure base-files" fi # Temporary hack for base-files for FILE in motd.tail motd profile do if [ -e "${DIRECTORY}/usr/share/base-files/${FILE}" ] then rm -f "${DIRECTORY}/etc/${FILE}" cp "${DIRECTORY}/usr/share/base-files/${FILE}" "${DIRECTORY}/etc" fi done # Temporary hack for readline if [ -e "${DIRECTORY}/etc/inputrc" ] then rm -f "${DIRECTORY}/etc/inputrc" cp "${DIRECTORY}/usr/share/readline/inputrc" "${DIRECTORY}/etc" fi # Temporary hack for sysvinit if [ -e "${DIRECTORY}/etc/inittab" ] then rm -f "${DIRECTORY}/etc/inittab" cp "${DIRECTORY}/usr/share/sysvinit/inittab" "${DIRECTORY}/etc/inittab" fi # Removing resolv.conf #rm -f "${DIRECTORY}/etc/resolv.conf" # FIXME: needs to stay for the moment rm -f "${DIRECTORY}/etc/resolv.conf" cp /etc/resolv.conf "${DIRECTORY}/etc" # Removing hosts/hostname rm -f "${DIRECTORY}"/etc/hosts rm -f "${DIRECTORY}"/etc/hostname # Removing openssh-server host keys rm -f "${DIRECTORY}"/etc/ssh/ssh_host_*_key rm -f "${DIRECTORY}"/etc/ssh/ssh_host_*_key.pub } Configure_system () { DIRECTORY="${1}" # Overwrite resolv.conf from cache with hosts resolv.conf rm -f "${DIRECTORY}/etc/resolv.conf" cp /etc/resolv.conf "${DIRECTORY}/etc" echo "${NAME}" > "${DIRECTORY}/etc/hostname" # Configure apt rm -f "${DIRECTORY}/etc/apt/sources.list" PARENT_AREA="$(echo ${PARENT_ARCHIVE_AREAS} | sed -e 's|,| |g')" PARENT_DIST="$(echo ${PARENT_DISTRIBUTION} | sed -e 's|-backports||')" echo "deb ${PARENT_MIRROR} ${PARENT_DIST} ${PARENT_AREA}" > "${DIRECTORY}/etc/apt/sources.list.d/debian.list" for PARENT_REPO in ${PARENT_ARCHIVES} do case "${PARENT_REPO}" in ${PARENT_DIST}-security) echo "deb ${PARENT_MIRROR_SECURITY} ${PARENT_DIST}/updates ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list" ;; ${PARENT_DIST}-updates) echo "deb ${PARENT_MIRROR} ${PARENT_DIST}-updates ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list" ;; ${PARENT_DIST}-backports) echo "deb ${PARENT_MIRROR} ${PARENT_DIST}-backports ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list" ;; ${PARENT_DIST}-proposed-updates) echo "deb ${PARENT_MIRROR} ${PARENT_DIST}-proposed-updates ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list" ;; experimental) echo "deb ${PARENT_MIRROR} experimental ${PARENT_AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/debian.list" ;; esac done case "${MODE}" in progress-linux) AREA="$(echo ${ARCHIVE_AREAS} | sed -e 's|,| |g')" DIST="$(echo ${DISTRIBUTION} | sed -e 's|-backports||')" echo "deb ${MIRROR} ${DIST} ${AREA}" > "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" for REPO in ${ARCHIVES} do case "${REPO}" in ${DIST}-staging) echo "deb ${MIRROR} ${DIST}-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" ;; ${DIST}-security) echo "deb ${MIRROR_SECURITY} ${DIST}-security ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" ;; ${DIST}-security-staging) echo "deb ${MIRROR_SECURITY} ${DIST}-security-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" ;; ${DIST}-updates) echo "deb ${MIRROR} ${DIST}-updates ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" ;; ${DIST}-updates-staging) echo "deb ${MIRROR} ${DIST}-updates-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" ;; ${DIST}-extras) if echo "${AREA}" | grep -qs non-free then echo "deb ${MIRROR} ${DIST}-extras ${AREA} restricted" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" else echo "deb ${MIRROR} ${DIST}-extras ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" fi ;; ${DIST}-extras-staging) if echo "${AREA}" | grep -qs non-free then echo "deb ${MIRROR} ${DIST}-extras-staging ${AREA} restricted" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" else echo "deb ${MIRROR} ${DIST}-extras-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" fi ;; ${DIST}-backports) echo "deb ${MIRROR} ${DIST}-backports ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" ;; ${DIST}-backports-staging) echo "deb ${MIRROR} ${DIST}-backports-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" ;; ${DIST}-backports-extras) if echo "${AREA}" | grep -qs non-free then echo "deb ${MIRROR} ${DIST}-backports-extras ${AREA} restricted" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" else echo "deb ${MIRROR} ${DIST}-backports-extras ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" fi ;; ${DIST}-backports-extras-staging) if echo "${AREA}" | grep -qs non-free then echo "deb ${MIRROR} ${DIST}-backports-extras-staging ${AREA} restricted" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" else echo "deb ${MIRROR} ${DIST}-backports-extras-staging ${AREA}" >> "${DIRECTORY}/etc/apt/sources.list.d/progress-linux.list" fi ;; esac done ;; esac if [ "${APT_RECOMMENDS}" = "false" ] then cat > "${DIRECTORY}/etc/apt/apt.conf.d/recommends.conf" << EOF APT::Install-Recommends "false"; EOF fi # Add local archives configured from preseed file if ls "${DEBCONF_TMPDIR}/apt"/*.list > /dev/null 2>&1 then cp "${DEBCONF_TMPDIR}/apt"/*.list "${DIRECTORY}/etc/apt/sources.list.d" if ls "${DEBCONF_TMPDIR}/apt"/*.key > /dev/null 2>&1 then for KEY in "${DEBCONF_TMPDIR}/apt"/*.key do cp "${KEY}" "${DIRECTORY}" Chroot "${DIRECTORY}" "apt-key add $(basename ${KEY})" rm -f "${DIRECTORY}/$(basename ${KEY})" done fi if ls "${DEBCONF_TMPDIR}/apt"/*.pref > /dev/null 2>&1 then cp "${DEBCONF_TMPDIR}/apt"/*.pref "${DIRECTORY}/etc/apt/preferences.d" fi fi Upgrade_system "${DIRECTORY}" # Preseed system if [ -n "${PRESEED_FILE}" ] then for FILE in ${PRESEED_FILE} do sed -e "s|@NAME@|${NAME}|g" "${FILE}" >> "${DIRECTORY}/preseed.cfg" done Chroot "${DIRECTORY}" "debconf-set-selections preseed.cfg" rm -f "${DIRECTORY}/preseed.cfg" fi # FIXME: All packages of priority of essential need to be reconfigured to reflect choices from preseeding # -> fix: use two-stage bootstrap (foreign) and inject preseeds in between # Manual hack to workaround broken preseeding in locales package if [ -n "${PRESEED_FILE}" ] then for FILE in ${PRESEED_FILE} do if grep -qs locales "${FILE}" then if [ -e "${DIRECTORY}/var/lib/dpkg/info/locales.list" ] then rm -f "${DIRECTORY}/etc/default/locale" "${DIRECTORY}/etc/locale.gen" Chroot "${DIRECTORY}" "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=criticial dpkg-reconfigure locales" break fi fi done fi # Manual hack to create conffiles when using locales-all instead of locales if [ ! -e "${DIRECTORY}/etc/environment" ] then echo "LANG=C.UTF-8" >> "${DIRECTORY}/etc/environment" fi if [ ! -e "${DIRECTORY}/etc/default/locale" ] then echo "LANG=C.UTF-8" >> "${DIRECTORY}/etc/default/locale" fi # Manual hack to workaround broken preseeding in tzdata package if [ -n "${PRESEED_FILE}" ] then for FILE in ${PRESEED_FILE} do if grep -qs tzdata "${FILE}" then rm -f "${DIRECTORY}/etc/localtime" "${DIRECTORY}/etc/timezone" Chroot "${DIRECTORY}" "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=criticial dpkg-reconfigure tzdata" break fi done fi # Temporary hack for base-files (base-files gets upgraded, so run it second time) for FILE in motd.tail motd profile do if [ -e "${DIRECTORY}/usr/share/base-files/${FILE}" ] then rm -f "${DIRECTORY}/etc/${FILE}" cp "${DIRECTORY}/usr/share/base-files/${FILE}" "${DIRECTORY}/etc" fi done # FIXME: Install additional packages after lxc-support has been run. # This is suboptimal, ideally we should install all packages but not run the maintainer scripts, # then run lxc-support, and run the maintainer scripts. This way, lxc-support would see # all the installed packages and could skip those scripts entirely when a certain package is not # installed. Unfortunately, that is not possible in any reasonable way with apt-get. # FTR: The only known workaround for now would be to first apt-get install --download-only all # packages, then unpack them with dpkg, run lxc-support, and dpkg --configure them. # For the time being, it's better to have lxc-support see no packages at all and be run before # packages are installed, than the other way around. # Workaround: We're running lxc-support at the end of the template again. if [ -n "${PACKAGES}" ] then Chroot "${DIRECTORY}" "apt-get --option APT::Force-LoopBreak=true --option Dpkg::Options::=--force-confnew --yes install ${PACKAGES}" fi # Manual hack to regenerate ssh keys if [ -e "${DIRECTORY}/var/lib/dpkg/info/openssh-server.postinst" ] && \ ! ls "${DIRECTORY}"/etc/ssh/ssh_host_*_key > /dev/null 2>&1 then Chroot "${DIRECTORY}" "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=criticial dpkg-reconfigure openssh-server" fi # container command if [ -n "${CONTAINER_COMMAND}" ] then echo "${CONTAINER_COMMAND}" > "${DIRECTORY}/.container-command" chmod 0755 "${DIRECTORY}/.container-command" Chroot "${DIRECTORY}" "sh /.container-command" rm -f "${DIRECTORY}/.container-command" fi } Configure_network () { DIRECTORY="${1}" # Create /etc/resolv.conf rm -f "${DIRECTORY}/etc/resolv.conf.tmp" if [ -n "${NAMESERVER_DOMAIN}" ] then echo "domain ${NAMESERVER_DOMAIN}" >> "${DIRECTORY}/etc/resolv.conf.tmp" fi if [ -n "${NAMESERVER_SEARCH}" ] then echo "search ${NAMESERVER_SEARCH}" >> "${DIRECTORY}/etc/resolv.conf.tmp" fi if [ -n "${NAMESERVER_SERVER}" ] then if [ -e "${DIRECTORY}/etc/resolv.conf.tmp" ] then echo "" >> "${DIRECTORY}/etc/resolv.conf.tmp" fi for NAMESERVER in $(echo ${NAMESERVER_SERVER} | sed -e 's|,| |g') do echo "nameserver ${NAMESERVER}" >> "${DIRECTORY}/etc/resolv.conf.tmp" done fi if [ -n "${NAMESERVER_OPTIONS}" ] then if [ -e "${DIRECTORY}/etc/resolv.conf.tmp" ] then echo "" >> "${DIRECTORY}/etc/resolv.conf.tmp" fi echo "options ${NAMESERVER_OPTIONS}" >> "${DIRECTORY}/etc/resolv.conf.tmp" fi if [ -e "${DIRECTORY}/etc/resolv.conf.tmp" ] then mv "${DIRECTORY}/etc/resolv.conf.tmp" "${DIRECTORY}/etc/resolv.conf" fi # Create /etc/hosts case "${NETWORK1_IPV4_METHOD}" in none|dhcp) cat > "${DIRECTORY}/etc/hosts.tmp" << EOF 127.0.0.1 localhost ${NAME} # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters EOF ;; static) cat > "${DIRECTORY}/etc/hosts.tmp" << EOF 127.0.0.1 localhost ${NETWORK1_IPV4_ADDRESS} ${NAME} # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters EOF ;; esac mv "${DIRECTORY}/etc/hosts.tmp" "${DIRECTORY}/etc/hosts" } Configure_systemd_networkd () { DIRECTORY="${1}" # Enable systemd-networkd chroot "${DIRECTORY}" apt purge --yes ifupdown || true rm -f "${DIRECTORY}/etc/network/interfaces" rmdir --ignore-fail-on-non-empty --parents "${DIRECTORY}"/etc/network/* > /dev/null 2>&1 || true chroot "${DIRECTORY}" systemctl enable systemd-networkd for NUMBER in $(seq 1 ${NETWORK_NUMBER}) do eval IPV4_COMMENT="$`echo NETWORK${NUMBER}_IPV4_COMMENT`" eval IPV4_METHOD="$`echo NETWORK${NUMBER}_IPV4_METHOD`" eval IPV4_ADDRESS="$`echo NETWORK${NUMBER}_IPV4_ADDRESS`" eval IPV4_GATEWAY="$`echo NETWORK${NUMBER}_IPV4_GATEWAY`" eval IPV4_NETMASK="$`echo NETWORK${NUMBER}_IPV4_NETMASK`" eval IPV4_POST_UP="$`echo NETWORK${NUMBER}_IPV4_POST_UP`" eval IPV4_POST_DOWN="$`echo NETWORK${NUMBER}_IPV4_POST_DOWN`" if [ -z "${IPV4_METHOD}" ] then continue fi IPV4_SUFFIX="$(CIDR ${IPV4_NETMASK})" IPV4_CIDR="${IPV4_ADDRESS}/${IPV4_SUFFIX}" cat > "${DIRECTORY}/etc/systemd/network/eno${NUMBER}.network" << EOF [Match] Name=eno${NUMBER} [Network] EOF if [ -n "${IPV4_COMMENT}" ] then echo "Description=${IPV4_COMMENT}" >> "${DIRECTORY}/etc/systemd/network/eno${NUMBER}.network" fi case "${IPV4_METHOD}" in dhcp) cat >> "${DIRECTORY}/etc/systemd/network/eno${NUMBER}.network" << EOF DHCP=ipv4 EOF ;; static) cat >> "${DIRECTORY}/etc/systemd/network/eno${NUMBER}.network" << EOF DHCP=no Address=${IPV4_CIDR} EOF if [ -n "${IPV4_GATEWAY}" ] then cat >> "${DIRECTORY}/etc/systemd/network/eno${NUMBER}.network" << EOF Gateway=${IPV4_GATEWAY} EOF fi esac if [ -n "${IPV4_POST_UP}" ] then cat > "${DIRECTORY}/etc/systemd/system/cnt-ipv4-post-up-eno${NUMBER}.service" << EOF [Unit] Description=container-tools IPV4_POST_UP After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/bin/sh -c "${IPV4_POST_UP}" [Install] WantedBy=multi-user.target EOF chroot "${DIRECTORY}" systemctl enable cnt-ipv4-post-up-eno${NUMBER}.service fi if [ -n "${IPV4_POST_DOWN}" ] then cat > "${DIRECTORY}/etc/systemd/system/cnt-ipv4-post-down-eno${NUMBER}.service" << EOF [Unit] Description=container-tools IPV4_POST_DOWN After=network.target Wants=network.target [Service] Type=oneshot ExecStart=/bin/sh -c "${IPV4_POST_DOWN}" [Install] WantedBy=multi-user.target EOF chroot "${DIRECTORY}" systemctl enable cnt-ipv4-post-down-eno${NUMBER}.service fi NUMBER="$((${NUMBER} + 1))" done } Commands () { DIRECTORY="${1}" if [ -n "${HOST_COMMAND}" ] then echo "${HOST_COMMAND}" > "${DIRECTORY}/.host-command" cd "${DIRECTORY}" sh "${DIRECTORY}/.host-command" cd "${OLDPWD}" rm -f "${DIRECTORY}/.host-command" fi # config (FIXME) # maximum of 15 characters, prefix is 'veth-' HOSTNAME_SHORT="$(echo ${NAME} | cut -c-8)" HOST_INTERFACE_NAME="$(echo ${NETWORK1_VETH:-veth-${HOSTNAME_SHORT}-0})" sed -i -e "s|^cnt.auto=.*|cnt.auto=${CNT_AUTO}|g" \ -e "s|^cnt.container-server=.*|cnt.container-server=${CNT_CONTAINER_SERVER}|g" \ -e "s|^cnt.network-bridge=.*|cnt.network-bridge=${HOST_INTERFACE_NAME}:${NETWORK1_BRIDGE:-bridge0}|g" \ -e "s|^cnt.overlay=.*|cnt.overlay=${CNT_OVERLAY}|g" \ -e "s|^bind=.*|bind=${BIND}|g" \ -e "s|^bind-ro=.*|bind-ro=${BIND_RO}|g" \ -e "s|^network-veth-extra=.*|network-veth-extra=${HOST_INTERFACE_NAME}:eno1|g" \ "${CONFIG}/${NAME}.conf" if [ "${NETWORK_NUMBER}" -ge 2 ] then for NUMBER in $(seq 2 ${NETWORK_NUMBER}) do eval IPV4_METHOD="$`echo NETWORK${NUMBER}_IPV4_METHOD`" if [ -z "${IPV4_METHOD}" ] then continue fi eval HOST_INTERFACE_NAME="$`echo NETWORK${NUMBER}_VETH`" HOST_INTERFACE_NAME="$(echo ${HOST_INTERFACE_NAME:-veth-${HOSTNAME_SHORT}-${NUMBER}})" CONTAINER_INTERFACE_NAME="eno${NUMBER}" sed -i -e "/^register=.*/ a network-veth-extra=${HOST_INTERFACE_NAME}:${CONTAINER_INTERFACE_NAME}" "${CONFIG}/${NAME}.conf" eval BRIDGE="$`echo NETWORK${NUMBER}_BRIDGE`" sed -i -e "/^register=.*/ a cnt.network-bridge=${HOST_INTERFACE_NAME}:${BRIDGE:-bridge${NUMBER}}" "${CONFIG}/${NAME}.conf" done fi # Setting root password echo root:${ROOT_PASSWORD} | chroot "${DIRECTORY}" chpasswd case "${ROOT_RANDOM_PASSWORD}" in true) echo "${NAME}: root password set to '${ROOT_PASSWORD}'." ;; esac } umask 0022 export NAME Debconf # Pre hooks for FILE in "${HOOKS}/pre-${SCRIPT}".* "${HOOKS}/${NAME}.pre-${SCRIPT}" do if [ -x "${FILE}" ] then "${FILE}" fi done # Run debconf parts for DEBCONF_SCRIPT in /usr/share/container-tools/scripts/debconf.d/* do if [ -x "${DEBCONF_SCRIPT}" ] then # FIXME # debconf -ocontainer-tools "${DEBCONF_SCRIPT}" "${DEBCONF_SCRIPT}" fi done # Read-in configuration from debconf . "${DEBCONF_TMPDIR}/debconf.default" CACHE="/var/cache/container-tools/${MODE}" SYSTEM="${MACHINES}/${NAME}" ## Generic parts if [ ! -e "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}" ] then Debootstrap "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}.tmp" Configure_apt "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}.tmp" Deconfigure_system "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}.tmp" mv "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}.tmp" "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}" fi Upgrade_system "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}" || echo "W: If upgrading the system failed, try removing the cache for your distribution in /var/cache/container-tools" Cleanup_system "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}" ## Specific parts mkdir -p "${MACHINES}" cp -a "${CACHE}/${DISTRIBUTION}_${ARCHITECTURE}" "${MACHINES}/${NAME}" # Mounting rw bind mounts if [ -n "${BIND}" ] then BINDS="$(echo ${BIND} | sed -e 's|;| |g')" for ENTRY in ${BINDS} do SOURCE="$(echo ${ENTRY} | awk -F: '{ print $1 }')" TARGET="$(echo ${ENTRY} | awk -F: '{ print $2 }')" mkdir -p "${SOURCE}" mkdir -p "${MACHINES}/${NAME}/${TARGET}" mount -o bind "${SOURCE}" "${MACHINES}/${NAME}/${TARGET}" done fi # Mounting ro bind mounts if [ -n "${BIND_RO}" ] then BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')" for ENTRY in ${BINDS_RO} do SOURCE="$(echo ${ENTRY} | awk -F: '{ print $1 }')" TARGET="$(echo ${ENTRY} | awk -F: '{ print $2 }')" mkdir -p "${SOURCE}" mkdir -p "${MACHINES}/${NAME}/${TARGET}" mount -o rbind "${SOURCE}" "${MACHINES}/${NAME}/${TARGET}" done fi # Mounting overlay mounts if [ -n "${CNT_OVERLAY}" ] then CNT_OVERLAYS="$(echo ${CNT_OVERLAY} | sed -e 's|;| |g')" for CNT_OVERLAY in ${CNT_OVERLAYS} do DIRECTORY_LOWER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $1 }')" DIRECTORY_UPPER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $2 }')" DIRECTORY_WORK="$(echo ${CNT_OVERLAY} | awk -F: '{ print $3 }')" DIRECTORY_MERGED="$(echo ${CNT_OVERLAY} | awk -F: '{ print $4 }')" for DIRECTORY in "${DIRECTORY_LOWER}" "${DIRECTORY_UPPER}" "${DIRECTORY_WORK}" "${DIRECTORY_MERGED}" do mkdir -p "${DIRECTORY}" done mount -t overlay overlay-${NAME} -olowerdir="${DIRECTORY_LOWER}",upperdir="${DIRECTORY_UPPER}",workdir="${DIRECTORY_WORK}" "${DIRECTORY_MERGED}" done fi Configure_system "${MACHINES}/${NAME}" Configure_network "${MACHINES}/${NAME}" Configure_systemd_networkd "${MACHINES}/${NAME}" # FIXME Cleanup_system "${MACHINES}/${NAME}" Commands "${MACHINES}/${NAME}" # Unmounting overlay mounts if [ -n "${CNT_OVERLAY}" ] then CNT_OVERLAYS="$(echo ${CNT_OVERLAY} | sed -e 's|;| |g')" for CNT_OVERLAY in ${CNT_OVERLAYS} do DIRECTORY_LOWER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $1 }')" DIRECTORY_UPPER="$(echo ${CNT_OVERLAY} | awk -F: '{ print $2 }')" DIRECTORY_WORK="$(echo ${CNT_OVERLAY} | awk -F: '{ print $3 }')" DIRECTORY_MERGED="$(echo ${CNT_OVERLAY} | awk -F: '{ print $4 }')" umount -f "${DIRECTORY_MERGED}" for DIRECTORY in "${DIRECTORY_LOWER}" "${DIRECTORY_UPPER}" "${DIRECTORY_WORK}" "${DIRECTORY_MERGED}" do rmdir --ignore-fail-on-non-empty --parents ${DIRECTORY} > /dev/null 2>&1 || true done done fi # Unmounting ro bind mounts if [ -n "${BIND_RO}" ] then BINDS_RO="$(echo ${BIND_RO} | sed -e 's|;| |g')" for ENTRY in ${BINDS_RO} do TARGET="$(echo ${ENTRY} | awk -F: '{ print $2 }')" umount "${MACHINES}/${NAME}/${TARGET}" done fi # Unmounting rw bind mounts if [ -n "${BIND}" ] then BINDS="$(echo ${BIND} | sed -e 's|;| |g')" for ENTRY in ${BINDS} do TARGET="$(echo ${ENTRY} | awk -F: '{ print $2 }')" umount "${MACHINES}/${NAME}/${TARGET}" done fi # remove debconf temporary files rm --preserve-root --one-file-system -rf "${DEBCONF_TMPDIR}" rmdir --ignore-fail-on-non-empty /tmp/container-tools 2>&1 || true # Post hooks for FILE in "${HOOKS}/post-${SCRIPT}".* "${HOOKS}/${NAME}.post-${SCRIPT}" do if [ -x "${FILE}" ] then "${FILE}" fi done