#!/bin/bash # vim: set ts=4 sw=4 expandtab # Exit on error and treat unset variables as an error. set -eu # # LXC template for Sabayon OS, based of Alpine script. # # Authors: # Geaaru # 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 #=========================== Constants ============================# # Make sure the usual locations are in PATH export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin readonly LOCAL_STATE_DIR='@LOCALSTATEDIR@' readonly LXC_TEMPLATE_CONFIG='@LXCTEMPLATECONFIG@' # Temporary static MIRROR LIST. I will get list from online path on the near future. readonly MIRRORS_LIST=" http://mirror.it.sabayon.org/ http://dl.sabayon.org/ ftp://ftp.klid.dk/sabayonlinux/ http://ftp.fsn.hu/pub/linux/distributions/sabayon/ http://ftp.cc.uoc.gr/mirrors/linux/SabayonLinux/ http://ftp.rnl.ist.utl.pt/pub/sabayon/ ftp://ftp.nluug.nl/pub/os/Linux/distr/sabayonlinux/ http://mirror.internode.on.net/pub/sabayon/ http://mirror.yandex.ru/sabayon/ http://sabayon.c3sl.ufpr.br/ http://mirror.clarkson.edu/sabayon/ http://na.mirror.garr.it/mirrors/sabayonlinux/" #======================== Global variables ========================# # Clean variables and set defaults. arch="$(uname -m)" debug='no' flush_cache='no' mirror_url= name= path= release="DAILY" rootfs= unprivileged=false mapped_uid= mapped_gid= flush_owner=false #======================== Helper Functions ========================# 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_64, armv7); defaults to the host arch. -d, --debug Run this script in a debug mode (set -x and wget w/o -q). -m URL --mirror=URL The Sabayon mirror to use; defaults to random mirror. -u, --unprivileged Tuning of rootfs for unprivileged containers. -r, --release Identify release to use. Default is DAILY. --mapped-gid Group Id to use on unprivileged container (based of value present on file /etc/subgid). --mapped-uid User Id to use on unprivileged container (based of value present on file /etc/subuid) --flush-owner Only for directly creation of unprivileged containers through lxc-create command. Execute fuidshift command. Require --mapped-gid,--mapped-uid and --unprivileged options. Environment variables: RELEASE Release version of Sabayon. Default is ${RELEASE}. EOF } random_mirror_url() { local url="" if [ $arch == 'amd64' ] ; then url=$(echo $MIRRORS_LIST | sed -e 's/ /\n/g' | sort -R --random-source=/dev/urandom | head -n 1) else if [ $arch == 'armv7l' ] ; then # Currently armv7l tarball is not on sabayon mirrored tree. url="https://dockerbuilder.sabayon.org/" fi fi [ -n "$url" ] && echo "$url" } die() { local retval=$1; shift echo -e "==> $@\n" exit $retval } einfo() { echo -e "==> $@\n" } fetch() { if [ "$debug" = 'yes' ]; then wget -T 10 -O - $@ else wget -T 10 -O - -q $@ fi } parse_arch() { case "$1" in x86_64 | amd64) echo 'amd64';; armv7 | armv7l) echo 'armv7l';; #arm*) echo 'armhf';; *) return 1;; esac } run_exclusively() { local lock_name="$1" local timeout=$2 local method=$3 shift 3 mkdir -p "$LOCAL_STATE_DIR/lock/subsys" local retval { echo -n "Obtaining an exclusive lock..." if ! flock -x 9; then echo ' failed.' return 1 fi echo ' done' ${method} $@ retval=$? } 9> "$LOCAL_STATE_DIR/lock/subsys/lxc-sabayon-$lock_name" return $retval } create_url () { local url="" # Example of amd64 tarball url # http://mirror.yandex.ru/sabayon/iso/daily/Sabayon_Linux_DAILY_amd64_tarball.tar.gz if [ $arch == 'amd64' ] ; then if [ $release = 'DAILY' ] ; then url="${MIRROR_URL}iso/daily/Sabayon_Linux_DAILY_amd64_tarball.tar.gz" else url="${MIRROR_URL}iso/monthly/Sabayon_Linux_${release}_amd64_tarball.tar.gz" fi else # https://dockerbuilder.sabayon.org/Sabayon_Linux_16_armv7l.tar.bz2 if [ $arch == 'armv7l' ] ; then # Currently $arch tarball is not on sabayon mirrored tree. url="${MIRROR_URL}Sabayon_Linux_16_armv7l.tar.bz2" fi fi echo $url } #=========================== Configure ===========================# unprivileged_rootfs() { pushd ${rootfs}/etc/systemd/system # Disable systemd-journald-audit.socket because it seems that doesn't # start correctly on unprivileged container ln -s /dev/null systemd-journald-audit.socket # Disable systemd-remount-fs.service because on unprivileged container # systemd can't remount filesystem ln -s /dev/null systemd-remount-fs.service # Remove mount of FUSE Control File system ln -s /dev/null sys-fs-fuse-connections.mount # Change execution of service systemd-sysctl to avoid errors. mkdir systemd-sysctl.service.d cat < systemd-sysctl.service.d/00gentoo.conf [Service] ExecStart= ExecStart=/usr/lib/systemd/systemd-sysctl --prefix=/etc/sysctl.d/ EOF # Disable mount of hugepages ln -s /dev/null dev-hugepages.mount popd pushd ${rootfs} # Disable sabayon-anti-fork-bomb limits (already apply to lxc container manager) sed -i -e 's/^*/#*/g' ./etc/security/limits.d/00-sabayon-anti-fork-bomb.conf || return 1 sed -i -e 's/^root/#root/g' ./etc/security/limits.d/00-sabayon-anti-fork-bomb.conf || return 1 popd return 0 } unprivileged_shift_owner () { # I use /usr/bin/fuidshift from LXD project. einfo "Executing: fuidshift ${rootfs} u:0:${mapped_uid}:65536 g:0:${mapped_gid}:65536 ..." fuidshift ${rootfs} u:0:${mapped_uid}:65536 g:0:${mapped_gid}:65536 || die 1 "Error on change owners of ${rootfs} directory" einfo "Done." # Fix permission of container directory chmod a+rx ${path} return 0 } systemd_container_tuning () { # To avoid error on start systemd-tmpfiles-setup service # it is needed clean journal directory rm -rf ${rootfs}/var/log/journal/ # Remove LVM service. Normally not needed on container system. rm -rf ${rootfs}/etc/systemd/system/sysinit.target.wants/lvm2-lvmetad.service # Comment unneeded entry on /etc/fstab sed -e 's/\/dev/#\/dev/g' -i ${rootfs}/etc/fstab # Fix this stupid error until fix is available on sabayon image # /usr/lib/systemd/system-generators/gentoo-local-generator: line 4: cd: /etc/local.d: No such file or directory mkdir -p ${rootfs}/etc/local.d/ mkdir -p ${rootfs}/etc/systemd/system/NetworkManager.service.d/ cat < ${rootfs}/etc/systemd/system/NetworkManager.service.d/override.conf [Service] ExecStartPre=-/bin/ip -4 link set dev eth0 down EOF chmod 644 ${rootfs}/etc/systemd/system/NetworkManager.service.d/override.conf return 0 } configure_container() { local config="$1" local hostname="$2" local arch="$3" local privileged_options="" local unprivileged_options="" if [[ $unprivileged && $unprivileged == true ]] ; then if [[ $flush_owner == true ]] ; then unprivileged_options=" lxc.idmap = u 0 ${mapped_uid} 65536 lxc.idmap = g 0 ${mapped_gid} 65536 " fi unprivileged_options=" $unprivileged_options # Force use of cgroup v1. Currently systemd doesn't support # correctly cgroup v2. See: https://github.com/lxc/lxc/issues/1669 # about discussion of default-hierarchy option. lxc.init.cmd = /sbin/init systemd.legacy_systemd_cgroup_controller=yes # Include common configuration. lxc.include = $LXC_TEMPLATE_CONFIG/sabayon.userns.conf " else privileged_options=" ## Allow any mknod (but not reading/writing the node) lxc.cgroup.devices.allow = b *:* m lxc.cgroup.devices.allow = c *:* m ### /dev/pts/* lxc.cgroup.devices.allow = c 136:* rwm ### /dev/tty lxc.cgroup.devices.allow = c 5:0 rwm ### /dev/console lxc.cgroup.devices.allow = c 5:1 rwm ### /dev/ptmx lxc.cgroup.devices.allow = c 5:2 rwm ### fuse lxc.cgroup.devices.allow = c 10:229 rwm " fi cat <<-EOF >> "$config" # Specify container architecture. lxc.arch = $arch # Set hostname. lxc.uts.name = $hostname # Include common configuration. lxc.include = $LXC_TEMPLATE_CONFIG/sabayon.common.conf $unprivileged_options $privileged_options EOF } #============================= Main ==============================# parse_cmdline() { # Parse command options. local short_options="a:dm:n:p:r:hu" local long_options="arch:,debug,mirror:,name:,path:,release:,rootfs:,mapped-uid:,mapped-gid:,flush-owner,help" options=$(getopt -u -q -a -o "$short_options" -l "$long_options" -- "$@") eval set -- "$options" # Process command options. while [ $# -gt 0 ]; do case $1 in -a | --arch) arch=$2 shift ;; -d | --debug) debug='yes' ;; -m | --mirror) mirror_url=$2 shift ;; -n | --name) name=$2 shift ;; -p | --path) path=$2 shift ;; -r | --release) release=$2 shift ;; --rootfs) rootfs=$2 shift ;; -u | --unprivileged) unprivileged=true ;; -h | --help) usage exit 1 ;; --mapped-uid) mapped_uid=$2 shift ;; --mapped-gid) mapped_gid=$2 shift ;; --flush-owner) flush_owner=true ;; --) break ;; *) einfo "Unknown option: $1" usage exit 1 ;; esac shift done if [ "$(id -u)" != "0" ]; then die 1 "This script must be run as 'root'" 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="$path/rootfs" fi [ -z "$path" ] && die 1 "'path' parameter is required." arch=$(parse_arch "$arch") \ || die 1 "Unsupported architecture: $arch" [[ $unprivileged == true && $flush_owner == true &&-z "$mapped_uid" ]] && \ die 1 'Missing required option --mapped-uid with --unprivileged option' [[ $unprivileged == true && $flush_owner == true && -z "$mapped_gid" ]] && \ die 1 'Missing required option --mapped-gid with --unprivileged option' [[ $flush_owner == true && $unprivileged == false ]] && \ die 1 'flush-owner require --unprivileged option' return 0 } main () { local tarball="" # Set global variables. RELEASE="${RELEASE:-"DAILY"}" ARCH="${ARCH:-`uname -m`}" OS="${OS:-"sabayon"}" einfo "Processing command line arguments: $@" # Parse command line options parse_cmdline "$@" DEBUG="$debug" MIRROR_URL="${mirror_url:-$(random_mirror_url)}" einfo "Use arch = $arch, mirror_url = $MIRROR_URL, path = $path, name = $name, release = $release, unprivileged = $unprivileged, rootfs = $rootfs, mapped_uid = $mapped_uid, mapped_gid = $mapped_gid, flush_owner = $flush_owner" [ "$debug" = 'yes' ] && set -x # Download sabayon tarball tarball=$(create_url) einfo "Fetching tarball $tarball..." # TODO: use only a compression mode if [ $arch == 'amd64' ] ; then fetch "${tarball}" | tar -xpz -C "${rootfs}" else if [ $arch == 'armv7l' ] ; then fetch "${tarball}" | tar -xpj -C "${rootfs}" fi fi einfo "Tarball ${tarball} Extracted." systemd_container_tuning # Fix container for unprivileged mode. if [[ $unprivileged == true ]] ; then unprivileged_rootfs if [[ $flush_owner == true ]] ; then unprivileged_shift_owner fi fi return 0 } einfo "Prepare creation of sabayon container with params: $@ ($#)" # Here we go! run_exclusively 'main' 10 main "$@" 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". Note: From kenel >= 4.6 for use unprivileged containers it is needed this mount on host: mkdir /sys/fs/cgroup/systemd mount -t cgroup -o none,name=systemd systemd /sys/fs/cgroup/systemd EOF