diff --git a/config/templates/Makefile.am b/config/templates/Makefile.am index adc3faf160..1a50f62940 100644 --- a/config/templates/Makefile.am +++ b/config/templates/Makefile.am @@ -5,6 +5,8 @@ EXTRA_DIST = common.seccomp SUBDIRS = common.conf.d templatesconfig_DATA = \ + alpine.common.conf \ + alpine.userns.conf \ archlinux.common.conf \ archlinux.userns.conf \ centos.common.conf \ diff --git a/config/templates/alpine.common.conf.in b/config/templates/alpine.common.conf.in new file mode 100644 index 0000000000..034a33b13f --- /dev/null +++ b/config/templates/alpine.common.conf.in @@ -0,0 +1,20 @@ +# This derives from the global common config. +lxc.include = @LXCTEMPLATECONFIG@/common.conf + +# Doesn't support consoles in /dev/lxc/. +lxc.devttydir = + +# Drop another (potentially) harmful capabilities. +lxc.cap.drop = audit_write +lxc.cap.drop = ipc_owner +lxc.cap.drop = mknod +lxc.cap.drop = setfcap +lxc.cap.drop = setpcap +lxc.cap.drop = sys_nice +lxc.cap.drop = sys_pacct +lxc.cap.drop = sys_ptrace +lxc.cap.drop = sys_rawio +lxc.cap.drop = sys_resource +lxc.cap.drop = sys_tty_config +lxc.cap.drop = syslog +lxc.cap.drop = wake_alarm diff --git a/config/templates/alpine.userns.conf.in b/config/templates/alpine.userns.conf.in new file mode 100644 index 0000000000..4336b448ce --- /dev/null +++ b/config/templates/alpine.userns.conf.in @@ -0,0 +1,2 @@ +# This derives from the global userns config. +lxc.include = @LXCTEMPLATECONFIG@/userns.conf diff --git a/configure.ac b/configure.ac index db764898f9..c47c32afd2 100644 --- a/configure.ac +++ b/configure.ac @@ -647,6 +647,8 @@ AC_CONFIG_FILES([ config/init/upstart/Makefile config/etc/Makefile config/templates/Makefile + config/templates/alpine.common.conf + config/templates/alpine.userns.conf config/templates/archlinux.common.conf config/templates/archlinux.userns.conf config/templates/centos.common.conf diff --git a/templates/lxc-alpine.in b/templates/lxc-alpine.in index 29c7b7c60f..e6f5fc567c 100644 --- a/templates/lxc-alpine.in +++ b/templates/lxc-alpine.in @@ -1,367 +1,514 @@ -#!/bin/sh +#!/bin/bash +# vim: set ts=4: +set -o errexit -o pipefail -o nounset + +# +# LXC template for Alpine Linux 3+ +# + +# Note: Do not replace tabs with spaces, it would break heredocs! + +# Authors: +# Jakub Jirutka + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -# Detect use under userns (unsupported) -for arg in "$@"; do - [ "$arg" = "--" ] && break - if [ "$arg" = "--mapped-uid" -o "$arg" = "--mapped-gid" ]; then - echo "This template can't be used for unprivileged containers." 1>&2 - echo "You may want to try the \"download\" template instead." 1>&2 - exit 1 - fi -done + +#=========================== Constants ============================# # Make sure the usual locations are in PATH -PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin -export PATH +export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin + +readonly LOCAL_STATE_DIR='@LOCALSTATEDIR@' +readonly LXC_TEMPLATE_CONFIG='@LXCTEMPLATECONFIG@' +readonly LXC_CACHE_DIR="${LXC_CACHE_PATH:-"$LOCAL_STATE_DIR/cache/lxc"}/alpine" -key_sha256sums="9c102bcc376af1498d549b77bdbfa815ae86faa1d2d82f040e616b18ef2df2d4 alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub +# SHA256 checksums of GPG keys for APK. +readonly APK_KEYS_SHA256="\ +9c102bcc376af1498d549b77bdbfa815ae86faa1d2d82f040e616b18ef2df2d4 alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub 2adcf7ce224f476330b5360ca5edb92fd0bf91c92d83292ed028d7c4e26333ab alpine-devel@lists.alpinelinux.org-4d07755e.rsa.pub ebf31683b56410ecc4c00acd9f6e2839e237a3b62b5ae7ef686705c7ba0396a9 alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub 1bb2a846c0ea4ca9d0e7862f970863857fc33c32f5506098c636a62a726a847b alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub 12f899e55a7691225603d6fb3324940fc51cd7f133e7ead788663c2b7eecb00c alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub" +readonly APK_KEYS_URI='https://alpinelinux.org/keys' +readonly MIRROR_CHOOSER_URL='http://wiki.alpinelinux.org/cgi-bin/dl.cgi' + +: ${APK_KEYS_DIR:=/etc/apk/keys} +if ! ls "$APK_KEYS_DIR"/alpine* &>/dev/null; then + APK_KEYS_DIR="$LXC_CACHE_DIR/bootstrap/keys" +fi +readonly APK_KEYS_DIR + +: ${APK:=$(command -v apk || true)} +if [ ! -x "$APK" ]; then + APK="$LXC_CACHE_DIR/bootstrap/sbin/apk.static" +fi +readonly APK + + +#======================== Helper Functions ========================# -get_static_apk () { - wget="wget -q -O -" - pkglist=alpine-keys:apk-tools-static - auto_repo_dir= - - if [ -z "$repository" ]; then - url=http://wiki.alpinelinux.org/cgi-bin/dl.cgi - yaml_path="latest-stable/releases/$apk_arch/latest-releases.yaml" - if [ -z "$release" ]; then - echo -n "Determining the latest release... " - release=$($wget $url/$yaml_path | \ - awk '$1 == "branch:" {print $2; exit 0}') - if [ -z "$release" ]; then - release=$($wget $url/.latest.$apk_arch.txt | \ - cut -d " " -f 3 | cut -d / -f 1 | uniq) - fi - if [ -z "$release" ]; then - echo failed - return 1 - fi - echo $release - fi - auto_repo_dir=$release/main - repository=$url/$auto_repo_dir - pkglist=$pkglist:alpine-mirrors - fi - - rootfs="$1" - echo "Using static apk from $repository/$apk_arch" - wget="$wget $repository/$apk_arch" - - # parse APKINDEX to find the current versions - static_pkgs=$($wget/APKINDEX.tar.gz | \ - tar -Oxz APKINDEX | \ - awk -F: -v pkglist=$pkglist ' - BEGIN { split(pkglist,pkg) } - $0 != "" { f[$1] = $2 } - $0 == "" { for (i in pkg) - if (pkg[i] == f["P"]) - print(f["P"] "-" f["V"] ".apk") }') - [ "$static_pkgs" ] || return 1 - - mkdir -p "$rootfs" || return 1 - for pkg in $static_pkgs; do - echo "Downloading $pkg" - $wget/$pkg | tar -xz -C "$rootfs" - done - - # clean up .apk meta files - rm -f "$rootfs"/.[A-Z]* - - # verify checksum of the key - keyname=$(echo $rootfs/sbin/apk.static.*.pub | sed 's/.*\.SIGN\.RSA\.//') - checksum=$(echo "$key_sha256sums" | grep -w "$keyname") - if [ -z "$checksum" ]; then - echo "ERROR: checksum is missing for $keyname" - return 1 - fi - (cd $rootfs/etc/apk/keys && echo "$checksum" | sha256sum -c -) || return 1 - - # verify the static apk binary signature - APK=$rootfs/sbin/apk.static - openssl dgst -sha1 -verify $rootfs/etc/apk/keys/$keyname \ - -signature "$APK.SIGN.RSA.$keyname" "$APK" || return 1 - - if [ "$auto_repo_dir" ]; then - mirror_list=$rootfs/usr/share/alpine-mirrors/MIRRORS.txt - mirror_count=$(wc -l $mirror_list | cut -d " " -f 1) - random=$(hexdump -n 2 -e '/2 "%u"' /dev/urandom) - repository=$(sed $(expr $random % $mirror_count + 1)\!d \ - $mirror_list)$auto_repo_dir - echo "Selecting mirror $repository" - fi +usage() { + cat <<-EOF + Template specific options can be passed to lxc-create after a '--' like this: + + lxc-create --name=NAME [lxc-create-options] -- [template-options] + + Template options: + -a ARCH, --arch=ARCH The container architecture (e.g. x86, x86_64); defaults + to the host arch. + -d, --debug Run this script in a debug mode (set -x and curl -v). + -F, --flush-cache Remove cached files before build. + -m URL --mirror=URL The Alpine mirror to use; defaults to random mirror. + -r VER, --release=VER The Alpine release branch to install; default is the + latest stable. + + Environment variables: + APK The apk-tools binary to use when building rootfs. If not set + or not executable and apk is not on PATH, then the script + will download the latest apk-tools-static. + APK_KEYS_DIR Path to directory with GPG keys for APK. If not set and + /etc/apk/keys does not contain alpine keys, then the script + will download the keys from ${APK_KEYS_URI}. + LXC_CACHE_PATH Path to the cache directory where to store cached rootfs. + EOF } -install_alpine() { - rootfs="$1" - shift - mkdir -p "$rootfs"/etc/apk || return 1 - : ${keys_dir:=/etc/apk/keys} - if ! [ -d "$rootfs"/etc/apk/keys ] && [ -d "$keys_dir" ]; then - cp -r "$keys_dir" "$rootfs"/etc/apk/keys - fi - if [ -n "$repository" ]; then - echo "$repository" > "$rootfs"/etc/apk/repositories - else - cp /etc/apk/repositories "$rootfs"/etc/apk/repositories || return 1 - if [ -n "$release" ]; then - sed -E -i "s:/[^/]+/([^/]+)$:/$release/\\1:" \ - "$rootfs"/etc/apk/repositories - fi - fi - opt_arch= - if [ -n "$apk_arch" ]; then - opt_arch="--arch $apk_arch" - fi - $APK add -U --initdb --root $rootfs $opt_arch "$@" alpine-base +die() { + local retval=$1; shift + + echo -e "ERROR: $@" >&2 + exit $retval } -configure_alpine() { - rootfs="$1" - echo "Setting up /etc/inittab" - cat >"$rootfs"/etc/inittab< $@" +} - # set up nameserver - grep nameserver /etc/resolv.conf > "$rootfs/etc/resolv.conf" +fetch() { + if [ "$DEBUG" = 'yes' ]; then + curl -q --verbose $@ + else + curl -q --silent --show-error $@ + fi +} - # configure the network using the dhcp - cat < $rootfs/etc/network/interfaces -auto lo -iface lo inet loopback +latest_release_branch() { + local arch="$1" + local branch=$(fetch -L "$MIRROR_URL/latest-stable/releases/$arch/latest-releases.yaml" \ + | sed -En 's/^[ \t]*branch: (.*)$/\1/p' \ + | head -n 1) + [ -n "$branch" ] && echo "$branch" +} -auto eth0 -iface eth0 inet dhcp -EOF +parse_arch() { + case "$1" in + x86 | i[3-6]86) echo 'x86';; + x86_64 | amd64) echo 'x86_64';; + arm*) echo 'armhf';; + *) return 1;; + esac +} - # set the hostname - echo $hostname > $rootfs/etc/hostname - - # missing device nodes - echo "Setting up device nodes" - mkdir -p -m 755 "$rootfs/dev/pts" - mkdir -p -m 1777 "$rootfs/dev/shm" - mknod -m 666 "$rootfs/dev/zero" c 1 5 - mknod -m 666 "$rootfs/dev/full" c 1 7 - mknod -m 666 "$rootfs/dev/random" c 1 8 - mknod -m 666 "$rootfs/dev/urandom" c 1 9 - mknod -m 666 "$rootfs/dev/tty0" c 4 0 - mknod -m 666 "$rootfs/dev/tty1" c 4 1 - mknod -m 666 "$rootfs/dev/tty2" c 4 2 - mknod -m 666 "$rootfs/dev/tty3" c 4 3 - mknod -m 666 "$rootfs/dev/tty4" c 4 4 -# mknod -m 600 "$rootfs/dev/initctl" p - mknod -m 666 "$rootfs/dev/tty" c 5 0 - mknod -m 666 "$rootfs/dev/console" c 5 1 - mknod -m 666 "$rootfs/dev/ptmx" c 5 2 - - # start services - ln -s /etc/init.d/bootmisc "$rootfs"/etc/runlevels/boot/bootmisc - ln -s /etc/init.d/syslog "$rootfs"/etc/runlevels/boot/syslog - - return 0 +random_mirror_url() { + local url="$(fetch --head "$MIRROR_CHOOSER_URL" | tr -d '\r' \ + | sed -En 's/^Location: (.*)$/\1/p')" + [ -n "$url" ] && echo "$url" } -copy_configuration() { - path=$1 - rootfs=$2 - hostname=$3 - - grep -q "^lxc.rootfs" $path/config 2>/dev/null \ - || echo "lxc.rootfs = $rootfs" >> $path/config - if [ -n "$lxc_arch" ]; then - echo "lxc.arch = $lxc_arch" >> $path/config - fi - - lxc_network_link_line="# lxc.network.link = br0" - for br in lxcbr0 virbr0 br0; do - if [ -d /sys/class/net/$br/bridge ]; then - lxc_network_link_line="lxc.network.link = $br" - break - fi - done - - if ! grep -q "^lxc.network.type" $path/config 2>/dev/null; then - cat <> $path/config -lxc.network.type = veth -$lxc_network_link_line -lxc.network.flags = up -EOF - fi - - # if there is exactly one veth or macvlan network entry, make sure - # it has an associated mac address. - nics=$(awk -F '[ \t]*=[ \t]*' \ - '$1=="lxc.network.type" && ($2=="veth" || $2=="macvlan") {print $2}' \ - $path/config | wc -l) - if [ "$nics" -eq 1 ] && ! grep -q "^lxc.network.hwaddr" $path/config; then - # see http://sourceforge.net/tracker/?func=detail&aid=3411497&group_id=163076&atid=826303 - hwaddr="fe:$(dd if=/dev/urandom bs=8 count=1 2>/dev/null |od -t x8 | \ - head -n 1 |awk '{print $2}' | cut -c1-10 |\ - sed 's/\(..\)/\1:/g; s/.$//')" - echo "lxc.network.hwaddr = $hwaddr" >> $path/config - fi - - cat <> $path/config - -lxc.tty = 4 -lxc.pts = 1024 -lxc.utsname = $hostname -lxc.cap.drop = sys_module mac_admin mac_override sys_time sys_admin - -# When using LXC with apparmor, uncomment the next line to run unconfined: -#lxc.aa_profile = unconfined - -# devices -lxc.cgroup.devices.deny = a -# /dev/null, zero and full -lxc.cgroup.devices.allow = c 1:3 rwm -lxc.cgroup.devices.allow = c 1:5 rwm -lxc.cgroup.devices.allow = c 1:7 rwm -# consoles -lxc.cgroup.devices.allow = c 5:1 rwm -lxc.cgroup.devices.allow = c 5:0 rwm -lxc.cgroup.devices.allow = c 4:0 rwm -lxc.cgroup.devices.allow = c 4:1 rwm -# /dev/{,u}random -lxc.cgroup.devices.allow = c 1:9 rwm -lxc.cgroup.devices.allow = c 1:8 rwm -lxc.cgroup.devices.allow = c 136:* rwm -lxc.cgroup.devices.allow = c 5:2 rwm -# rtc -lxc.cgroup.devices.allow = c 254:0 rm - -# mounts point -lxc.mount.auto=cgroup:mixed proc:mixed sys:mixed -lxc.mount.entry=run run tmpfs nodev,noexec,nosuid,relatime,size=1m,mode=0755 0 0 -lxc.mount.entry=shm dev/shm tmpfs nodev,nosuid,noexec,mode=1777,create=dir 0 0 +run_exclusively() { + local lock_name="$1" + local timeout=$2 + shift 2 -EOF + mkdir -p "$LOCAL_STATE_DIR/lock/subsys" - return 0 + local retval + { + echo -n "Obtaining an exclusive lock (timeout: $timeout sec)..." + if ! flock --exclusive --wait=$timeout 9; then + echo ' failed.' + return 1 + fi + echo ' done' + + "$@"; retval=$? + } 9> "$LOCAL_STATE_DIR/lock/subsys/lxc-alpine-$lock_name" + + return $retval } -die() { - echo "$@" >&2 - exit 1 + +#============================ Bootstrap ===========================# + +bootstrap() { + if [[ "$FLUSH_CACHE" = 'yes' && -d "$LXC_CACHE_DIR/bootstrap" ]]; then + einfo 'Cleaning cached bootstrap files' + rm -Rf "$LXC_CACHE_DIR/bootstrap" + fi + + einfo 'Fetching and/or verifying APK keys' + fetch_apk_keys "$APK_KEYS_DIR" + + if [ ! -x "$APK" ]; then + einfo 'Fetching apk-tools static binary' + + local host_arch=$(parse_arch $(uname -m)) + fetch_apk_static "$LXC_CACHE_DIR/bootstrap" "$host_arch" + fi } -usage() { - cat >&2 <] - [-R|--release ] [-a|--arch ] - [--rootfs ] -p|--path -n|--name - [PKG...] +fetch_apk_keys() { + local dest="$1" + local line keyname + + mkdir -p "$dest" + pushd "$dest" 1>/dev/null + + echo "$APK_KEYS_SHA256" | while read -r line; do + keyname="${line##* }" + if [ ! -f "$keyname" ]; then + fetch --ssl-reqd "$APK_KEYS_URI/$keyname" > "$keyname" + fi + echo "$line" | sha256sum --check - + done || exit 2 + + popd 1>/dev/null +} + +fetch_apk_static() { + local dest="$1" + local arch="$2" + local pkg_name='apk-tools-static' + + mkdir -p "$dest" + + local pkg_ver=$(fetch -L "$MIRROR_URL/latest-stable/main/$arch/APKINDEX.tar.gz" \ + | tar x --gzip --to-stdout APKINDEX \ + | sed -n "/P:${pkg_name}/,/^$/ s/V:\(.*\)$/\1/p") + + [ -n "$pkg_ver" ] || die 2 "Cannot find a version of $pkg_name in APKINDEX" + + fetch -L "$MIRROR_URL/latest-stable/main/$arch/${pkg_name}-${pkg_ver}.apk" \ + | tar x --gzip --warning=no-unknown-keyword --directory="$dest" sbin/ + + [ -f "$dest/sbin/apk.static" ] || die 2 'apk.static not found' + + local keyname=$(echo "$dest"/sbin/apk.static.*.pub | sed 's/.*\.SIGN\.RSA\.//') + openssl dgst -sha1 \ + -verify "$APK_KEYS_DIR/$keyname" \ + -signature "$dest/sbin/apk.static.SIGN.RSA.$keyname" \ + "$dest/sbin/apk.static" \ + || die 2 'Signature verification for apk.static failed' + + # Note: apk doesn't return 0 for --version + local out="$("$dest"/sbin/apk.static --version)" + echo "$out" + + [[ "$out" == apk-tools* ]] || die 3 'apk.static --version failed' +} + + +#============================ Install ============================# + +install() { + local rootfs="$1" + local arch="$2" + local branch="$3" + local cache_dir="$LXC_CACHE_DIR/rootfs-$branch-$arch" + + if [[ "$FLUSH_CACHE" == 'yes' && -d "$cache_dir" ]]; then + einfo "Cleaning cached rootfs of Alpine $branch $arch" + rm -Rf "$cache_dir" + fi + + if [ ! -d "$cache_dir/rootfs" ]; then + einfo "Building Alpine rootfs in $cache_dir/rootfs" + build_alpine "$cache_dir/rootfs" "$arch" "$branch" + fi + + einfo "Copying cached rootfs to $rootfs" + rsync --archive --hard-links --xattrs "$cache_dir"/rootfs/ "$rootfs"/ +} + +build_alpine() { + local dest="$1" + local arch="$2" + local branch="$3" + local repo_url="$MIRROR_URL/$branch/main" + + rm -Rf "$dest.part" 2>/dev/null + mkdir -p "$dest.part" + + pushd "$dest.part" 1>/dev/null + + $APK --update-cache --initdb --arch="$arch" \ + --root=. --keys-dir="$APK_KEYS_DIR" --repository="$repo_url" \ + add alpine-base + + echo "$repo_url" > etc/apk/repositories + + make_dev_nodes + setup_inittab + setup_hosts + setup_network + setup_services + + chroot . /bin/true \ + || die 3 'Failed to execute /bin/true in chroot, the builded rootfs is broken!' + + popd 1>/dev/null + + rm -Rf "$dest" + mv "$dest.part" "$dest" +} + +make_dev_nodes() { + mkdir -p -m 755 dev/pts + mkdir -p -m 1777 dev/shm + + mknod -m 666 dev/zero c 1 5 + mknod -m 666 dev/full c 1 7 + mknod -m 666 dev/random c 1 8 + mknod -m 666 dev/urandom c 1 9 + + local i; for i in $(seq 0 4); do + mknod -m 620 dev/tty$i c 4 $i + chown 0:5 dev/tty$i # root:tty + done + + mknod -m 666 dev/tty c 5 0 + chown 0:5 dev/tty # root:tty + mknod -m 620 dev/console c 5 1 + mknod -m 666 dev/ptmx c 5 2 + chown 0:5 dev/ptmx # root:tty +} + +setup_inittab() { + # Remove unwanted ttys. + sed -i '/^tty[5-9]\:\:.*$/d' etc/inittab + + cat <<-EOF >> etc/inittab + # Main LXC console console + ::respawn:/sbin/getty 38400 console + EOF +} + +setup_hosts() { + # This runscript injects localhost entries with the current hostname + # into /etc/hosts. + cat <<'EOF' > etc/init.d/hosts +#!/sbin/openrc-run + +start() { + local start_tag='# begin generated' + local end_tag='# end generated' + + local content=$( + cat <<-EOF + $start_tag by /etc/init.d/hosts + 127.0.0.1 $(hostname).local $(hostname) localhost + ::1 $(hostname).local $(hostname) localhost + $end_tag + EOF + ) + + if grep -q "^${start_tag}" /etc/hosts; then + # escape \n, busybox sed doesn't like them + content=${content//$'\n'/\\$'\n'} + + sed -ni "/^${start_tag}/ { + a\\${content} + # read and discard next line and repeat until $end_tag or EOF + :a; n; /^${end_tag}/!ba; n + }; p" /etc/hosts + else + echo -e "$content" >> /etc/hosts + fi +} EOF + chmod +x etc/init.d/hosts + + # Wipe it, will be generated by the above runscript. + echo -n > etc/hosts } -usage_err() { - usage - exit 1 +setup_network() { + # Note: loopback is automatically started by LXC. + cat <<-EOF > etc/network/interfaces + auto eth0 + iface eth0 inet dhcp + EOF } -default_path=@LXCPATH@ -release= -arch=$(uname -m) +setup_services() { + local svc_name + + # Specify the LXC subsystem. + sed -i 's/^#*rc_sys=.*/rc_sys="lxc"/' etc/rc.conf + + # boot runlevel + for svc_name in bootmisc hosts syslog; do + ln -s /etc/init.d/$svc_name etc/runlevels/boot/$svc_name + done -# template mknods, requires root -if [ $(id -u) -ne 0 ]; then - echo "$(basename $0): must be run as root" >&2 - exit 1 + # default runlevel + for svc_name in networking cron; do + ln -s /etc/init.d/$svc_name etc/runlevels/default/$svc_name + done +} + + +#=========================== Configure ===========================# + +configure_container() { + local config="$1" + local hostname="$2" + local arch="$3" + + cat <<-EOF >> "$config" + + # Specify container architecture. + lxc.arch = $arch + + # Set hostname. + lxc.utsname = $hostname + + # If something doesn't work, try to comment this out. + # Dropping sys_admin disables container root from doing a lot of things + # that could be bad like re-mounting lxc fstab entries rw for example, + # but also disables some useful things like being able to nfs mount, and + # things that are already namespaced with ns_capable() kernel checks, like + # hostname(1). + lxc.cap.drop = sys_admin + + # Include common configuration. + lxc.include = $LXC_TEMPLATE_CONFIG/alpine.common.conf + EOF +} + + +#============================= Main ==============================# + +# Detect use under userns (unsupported) +for arg in "$@"; do + [ "$arg" = '--' ] && break + if [[ "$arg" = --mapped-[ug]id ]]; then + echo "This template can't be used for unprivileged containers." >&2 + echo 'You may want to try the "download" template instead.' >&2 + exit 1 + fi +done + +if [ "$(id -u)" != "0" ]; then + die 1 "This script must be run as 'root'" fi -options=$(getopt -o hn:p:r:R:a: -l help,name:,rootfs:,path:,repository:,release:,arch: -- "$@") -[ $? -eq 0 ] || usage_err +# Parse command options. +options=$(getopt -o a:dFm:n:p:r:h -l arch:,debug,flush-cache,mirror:,name:,\ +path:,release:,rootfs:,help -- "$@") eval set -- "$options" +# Clean variables and set defaults. +arch="$(uname -m)" +debug='no' +flush_cache='no' +mirror_url= +name= +path= +release= +rootfs= + +# Process command options. while [ $# -gt 0 ]; do - case "$1" in - -h|--help) - usage - exit 0 - ;; - -n|--name) - name=$2 - ;; - --rootfs) - rootfs=$2 - ;; - -p|--path) - path=$2 - ;; - -r|--repository) - repository=$2 - ;; - -R|--release) - release=$2 - ;; - -a|--arch) - arch=$2 - ;; - --) - shift - break;; - esac - shift 2 + case $1 in + -a | --arch) + arch=$2; shift 2 + ;; + -d | --debug) + debug='yes'; shift 1 + ;; + -F | --flush-cache) + flush_cache='yes'; shift 1 + ;; + -m | --mirror) + mirror_url=$2; shift 2 + ;; + -n | --name) + name=$2; shift 2 + ;; + -p | --path) + path=$2; shift 2 + ;; + -r | --release) + release=$2; shift 2 + ;; + --rootfs) + rootfs=$2; shift 2 + ;; + -h | --help) + usage; exit 0 + ;; + --) + shift; break + ;; + *) + echo "Unknown option: $1" >&2 + usage; exit 1 + ;; + esac done +[ "$debug" = 'yes' ] && set -x -[ -z "$name" ] && usage_err +# Set global variables. +readonly DEBUG="$debug" +readonly FLUSH_CACHE="$flush_cache" +readonly MIRROR_URL="${mirror_url:-$(random_mirror_url)}" -if [ -z "${path}" ]; then - path="${default_path}/${name}" -fi +# Validate options. +[ -n "$name" ] || die 1 'Missing required option --name' +[ -n "$path" ] || die 1 'Missing required option --path' +if [[ -z "$rootfs" && -f "$path/config" ]]; then + rootfs="$(sed -nE 's/^lxc.rootfs\s*=\s*(.*)$/\1/p' "$path/config")" +fi if [ -z "$rootfs" ]; then - rootfs=`awk -F= '$1 ~ /^lxc.rootfs/ { print $2 }' "$path/config" 2>/dev/null` - if [ -z "$rootfs" ]; then - rootfs="${path}/rootfs" - fi + rootfs="$path/rootfs" fi -lxc_arch=$arch -apk_arch=$arch - -case "$arch" in - i[3-6]86) - apk_arch=x86 - lxc_arch=x86 - ;; - x86) - lxc_arch=i686 - ;; - x86_64|"") - ;; - arm*) - apk_arch=armhf - ;; - *) - die "unsupported architecture: $arch" - ;; -esac - -: ${APK:=apk} -if ! which $APK >/dev/null; then - get_static_apk "$rootfs" || die "Failed to download a valid static apk" +arch=$(parse_arch "$arch") \ + || die 1 "Unsupported architecture: $arch" + +if [ -z "$release" ]; then + release=$(latest_release_branch "$arch") \ + || die 2 'Failed to resolve Alpine last release branch' fi -install_alpine "$rootfs" "$@" || die "Failed to install rootfs for $name" -configure_alpine "$rootfs" "$name" || die "Failed to configure $name" -copy_configuration "$path" "$rootfs" "$name" +# Here we go! +run_exclusively 'bootstrap' 10 bootstrap +run_exclusively "$release-$arch" 30 install "$rootfs" "$arch" "$release" +configure_container "$path/config" "$name" "$arch" + +einfo "Container's rootfs and config have been created" +cat <<-EOF + Edit the config file $path/config to check/enable networking setup. + The installed system is preconfigured for a loopback and single network + interface configured via DHCP. + + To start the container, run "lxc-start -n $name". + The root password is not set; to enter the container run "lxc-attach -n $name". +EOF