#!/bin/bash # # lxc: linux Container library # Authors: # Daniel Lezcano # Template for slackware by Matteo Bernardini # some parts are taken from the debian one (used as model) # Modified by Didier Spaier to allow building # unprivileged containers through user namespaces mapping, see # user_namespaces(7). Some parts are taken from the busybox template. # Credit Chris Willing for unprivileged containers' settings. # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # To build an unprivileged Slackware container as regular user # ============================================================ # In the following examples the permissions are set per user. # In case of several users needing to create containers, it may be # preferable that they be set per group, see 'man cgconfig.conf' and # 'man cgrules.conf'. # Replace 'user' with the regular user's login name below. # 1) Feed /etc/{subuid,subgid} with a line of mapping like: # didier:100000:65537 # You can use these commands as root to do that: # touch /etc/{subuid,subgid} # If these files do not exist # /usr/sbin/usermod --add-subuids 100000-165536 user # /usr/sbin/usermod --add-subgids 100000-165536 user # 2) Create the directory ~/.config/lxc, then write the file # ~/.config/lxc/default.conf with this content (three lines): # lxc.id_map = u 0 100000 65536 # lxc.id_map = g 0 100000 65536 # lxc.devttydir = # 3) Create the container as user: # lxc-create -n myslack -t slackware # 4) Make sure /etc/rc.d/{rc.cgconfig,rc.cgred} be executable # 5) Run as root (at boot time in /etc/rc.d/rc.local for instance) # /etc/rc.d/rc.cgconfig start && /etc/rc.d/rc.cgred start # 6) As user run: # lxc-start -n myslack # 7) Then still as user run one of these commands: # lxc-console -n myslack # lxc-attach -n myslack # Other required settings # ======================= # You will need to have in /etc/cgconfig.conf at least what follows # (Of course uncomment the lines below): ## This is the content of /etc/cgconfig.conf used to start unprivileged ## lxc containers on Slackware as regular user ## Substitute your username to 'didier' # group containers { # perm { # task { # uid = didier; # gid = users; # } # admin { # uid = didier; # gid = users; # } # } # cpuset { cgroup.clone_children = 1; # cpuset.mems = 0; # cpuset.cpus = 0-3; } # cpu {} # cpuacct {} # blkio {} # memory { memory.use_hierarchy = 1; } # devices {} # freezer {}con # net_cls {} # perf_event {} # net_prio {} # pids {} # } ## You also need a specific content in /etc/cgrules.conf: ## This is the content of /etc/cgcrules.conf used to start unprivileged ## lxc containers on Slackware as regular user. ## Substitute your username to 'user' ## And make sure the destination (here 'containers/') is the group name ## used in /etc/cgconfig.conf # user * containers/ # The real thing begins here. LXC_MAPPED_UID= LXC_MAPPED_GID= # Make sure the usual locations are in PATH export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin # If we are in an user namespace, am_in_userns returns yes. am_in_userns() { [ -e /proc/self/uid_map ] || { echo no; return; } [ "$(wc -l /proc/self/uid_map | awk '{ print $1 }')" -eq 1 ] || { echo yes; return; } line=$(awk '{ print $1 " " $2 " " $3 }' /proc/self/uid_map) [ "$line" = "0 0 4294967295" ] && { echo no; return; } echo yes } in_userns=0 [ $(am_in_userns) = "yes" ] && in_userns=1 # Use the primary Slackware site by default, but please consider changing # this to a closer mirror site. MIRROR=${MIRROR:-http://ftp.slackware.com/pub/slackware} if [ -z "$arch" ]; then case "$( uname -m )" in i?86) arch=i486 ;; arm*) arch=arm ;; *) arch=$( uname -m ) ;; esac fi configure_slackware() { rootfs=$1 hostname=$2 echo "Configuring..." ; echo # The next part contains excerpts taken from SeTconfig (written by # Patrick Volkerding) from the slackware setup disk. # But before pasting them just set a variable to use them as they are T_PX=$rootfs ( cd $T_PX ; chmod 755 ./ ) ( cd $T_PX ; chmod 755 ./var ) if [ -d $T_PX/usr/src/linux ]; then chmod 755 $T_PX/usr/src/linux fi if [ ! -d $T_PX/proc ]; then mkdir $T_PX/proc chown root.root $T_PX/proc fi if [ ! -d $T_PX/sys ]; then mkdir $T_PX/sys chown root.root $T_PX/sys fi chmod 1777 $T_PX/tmp if [ ! -d $T_PX/var/spool/mail ]; then mkdir -p $T_PX/var/spool/mail chmod 755 $T_PX/var/spool chown root.mail $T_PX/var/spool/mail chmod 1777 $T_PX/var/spool/mail fi echo "#!/bin/sh" > $T_PX/etc/rc.d/rc.keymap echo "# Load the keyboard map. More maps are in /usr/share/kbd/keymaps." \ >> $T_PX/etc/rc.d/rc.keymap echo "if [ -x /usr/bin/loadkeys ]; then" >> $T_PX/etc/rc.d/rc.keymap echo " /usr/bin/loadkeys us" >> $T_PX/etc/rc.d/rc.keymap echo "fi" >> $T_PX/etc/rc.d/rc.keymap chmod 755 $T_PX/etc/rc.d/rc.keymap # Network configuration is left to the user, that have to edit # /etc/rc.d/rc.inet1.conf and /etc/resolv.conf of the container # just set the hostname cat < $rootfs/etc/HOSTNAME $hostname.example.net EOF cp $rootfs/etc/HOSTNAME $rootfs/etc/hostname # make needed devices, from Chris Willing's MAKEDEV.sh # http://www.vislab.uq.edu.au/howto/lxc/MAKEDEV.sh DEV=$rootfs/dev if [ $in_userns -ne 1 ]; then mkdir -p ${DEV} mknod -m 666 ${DEV}/null c 1 3 mknod -m 666 ${DEV}/zero c 1 5 mknod -m 666 ${DEV}/random c 1 8 mknod -m 666 ${DEV}/urandom c 1 9 mkdir -m 755 ${DEV}/pts mkdir -m 1777 ${DEV}/shm mknod -m 666 ${DEV}/tty c 5 0 mknod -m 600 ${DEV}/console c 5 1 mknod -m 666 ${DEV}/tty0 c 4 0 mknod -m 666 ${DEV}/tty1 c 4 1 mknod -m 666 ${DEV}/tty2 c 4 2 mknod -m 666 ${DEV}/tty3 c 4 3 mknod -m 666 ${DEV}/tty4 c 4 4 mknod -m 666 ${DEV}/tty5 c 4 5 mknod -m 666 ${DEV}/full c 1 7 mknod -m 600 ${DEV}/initctl p mknod -m 660 ${DEV}/loop0 b 7 0 mknod -m 660 ${DEV}/loop1 b 7 1 fi ln -s pts/ptmx ${DEV}/ptmx ln -s /proc/self/fd ${DEV}/fd echo "Adding an etc/fstab that must be modified later with the" echo "full path of the container's rootfs if you decide to move it." cat >$rootfs/etc/fstab <> $rootfs/etc/rc.d/rc.local # reduce the number of local consoles: two should be enough sed -i '/^c3\|^c4\|^c5\|^c6/s/^/# /' $rootfs/etc/inittab # In a container, use shutdown for powerfail conditions. LXC sends the SIGPWR # signal to init to shut down the container with lxc-stop and without this the # container will be force stopped after a one minute timeout. sed -i "s,pf::powerfail:/sbin/genpowerfail start,pf::powerfail:/sbin/shutdown -h now,g" $rootfs/etc/inittab sed -i "s,pg::powerokwait:/sbin/genpowerfail stop,pg::powerokwait:/sbin/shutdown -c,g" $rootfs/etc/inittab # set a default combination for the luggage echo "root:root" | chroot $rootfs chpasswd echo "Root default password is 'root', please change it!" # borrow the time configuration from the local machine cp -a /etc/localtime $rootfs/etc/localtime return 0 } copy_slackware() { rootfs=$1 # make a local copy of the installed filesystem echo -n "Copying rootfs to $rootfs..." mkdir -p $rootfs cp -a $cache/rootfs-$release-$arch/* $rootfs/ || exit 1 # fix fstab with the actual path sed -i "s|$cache/rootfs-$release-$arch|$rootfs|" $rootfs/etc/fstab return 0 } install_slackware() { rootfs=$1 mkdir -p /var/lock/subsys-$LOGNAME/ ( flock -n -x 200 if [ $? -ne 0 ]; then echo "Cache repository is busy." return 1 fi if [ "$arch" == "x86_64" ]; then PKGMAIN=slackware64 elif [ "$arch" == "arm" ]; then PKGMAIN=slackwarearm else PKGMAIN=slackware fi export CONF=$cache/slackpkg-conf export ROOT=$cache/rootfs-$release-$arch mkdir -p $cache/cache-$release-$arch $cache/rootfs-$release-$arch \ $cache/slackpkg-$release-$arch $CONF/templates echo "$MIRROR/$PKGMAIN-$release/" > $CONF/mirrors touch $CONF/blacklist cat < $CONF/slackpkg.conf # v2.8 ARCH=$arch TEMP=$cache/cache-$release-$arch WORKDIR=$cache/slackpkg-$release-$arch DELALL=off CHECKMD5=on CHECKGPG=on CHECKSIZE=off PRIORITY=( patches %PKGMAIN extra pasture testing ) POSTINST=on ONLY_NEW_DOTNEW=off ONOFF=on DOWNLOAD_ALL=on DIALOG=off BATCH=on DEFAULT_ANSWER=y USE_INCLUDES=on SPINNING=off EOF # thanks to Vincent Batts for this list of packages # (that I modified a little :P) # http://connie.slackware.com/~vbatts/minimal/ # Added kmod and less and nano and file (dependency of nano) - Didier cat < $CONF/templates/minimal-lxc.template aaa_base aaa_elflibs aaa_terminfo bash bin bzip2 coreutils cyrus-sasl db48 dcron dhcpcd dialog diffutils e2fsprogs elvis etc eudev file findutils gawk glibc-solibs gnupg gnutls grep gzip iproute2 iputils kmod libcap-ng libffi libmnl libtasn1 libunistring less logrotate mpfr nano net-tools nettle network-scripts ncurses openssh openssl-solibs p11-kit pkgtools procps-ng sed shadow sharutils slackpkg sysklogd sysvinit sysvinit-functions sysvinit-scripts tar util-linux wget which xz EOF TEMPLATE=${TEMPLATE:-minimal-lxc} if [ ! "$TEMPLATE" = "minimal-lxc" ]; then if [ -f /etc/slackpkg/templates/$TEMPLATE.template ]; then cat /etc/slackpkg/templates/$TEMPLATE.template \ > $CONF/templates/$TEMPLATE.template else TEMPLATE="minimal-lxc" fi fi # clean previous installs rm -fR $ROOT/* mkdir -p $cache/.gnupg export GNUPGHOME="$cache/.gnupg" slackpkg -default_answer=n update slackpkg install-template $TEMPLATE # add a slackpkg default mirror echo "$MIRROR/$PKGMAIN-$release/" >> $ROOT/etc/slackpkg/mirrors # blacklist the devs package (we have to use our premade devices). # do the same with the kernel packages (we use the host's one), # but leave available headers and sources echo "devs" >> $ROOT/etc/slackpkg/blacklist sed -i \ -e "s|^#kernel-|kernel-|" \ -e "s|^kernel-headers|#kernel-headers|" \ -e "s|^kernel-source|#kernel-source|" \ $ROOT/etc/slackpkg/blacklist return 0 ) 200>/var/lock/subsys-$LOGNAME/lxc return $? } copy_configuration() { path=$1 rootfs=$2 name=$3 cat <> $path/config lxc.utsname = $name lxc.mount = $rootfs/etc/fstab lxc.tty = 4 lxc.pts = 1024 lxc.rootfs = $rootfs lxc.cgroup.devices.deny = a # /dev/null and zero lxc.cgroup.devices.allow = c 1:3 rwm lxc.cgroup.devices.allow = c 1:5 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 rwm # we don't trust even the root user in the container, better safe than sorry. # comment out only if you know what you're doing. lxc.cap.drop = sys_module mknod mac_override mac_admin sys_time setfcap setpcap # you can try also this alternative to the line above, whatever suits you better. # lxc.cap.drop=sys_admin EOF if [ $? -ne 0 ]; then echo "Failed to add configuration." return 1 fi return 0 } copy_configuration_in_userns() { path=$1 rootfs=$2 name=$3 grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config cat <> $path/config lxc.autodev = 1 lxc.utsname = $name lxc.tty = 4 lxc.pts = 1024 lxc.mount.auto = cgroup:mixed proc:mixed sys:mixed lxc.devttydir = EOF } clean() { if [ ! -e $cache ]; then exit 0 fi # lock, so we won't purge while someone is creating a repository ( flock -n -x 200 if [ $? != 0 ]; then echo "Cache repository is busy." exit 1 fi echo -n "Purging the download cache..." rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 exit 0 ) 200>/var/lock/subsys-$LOGNAME/lxc } remap_userns() { path=$1 if [ -n "$LXC_MAPPED_UID" ] && [ "$LXC_MAPPED_UID" != "-1" ]; then chown $LXC_MAPPED_UID $path/config >/dev/null 2>&1 chown -R root $path/rootfs >/dev/null 2>&1 fi if [ -n "$LXC_MAPPED_GID" ] && [ "$LXC_MAPPED_GID" != "-1" ]; then chgrp $LXC_MAPPED_GID $path/config >/dev/null 2>&1 chgrp -R root $path/rootfs >/dev/null 2>&1 fi } usage() { cat <] lxc-create -n -t slackware [OPTIONS] -- [options] can be a local or remote mirror, like in /etc/slackpkg/mirror Default: http://ftp.slackware.com/pub/slackware is the name you give to the container. Mandatory. OPTIONS: general options of lxc-create, see lxc-create -h options (of this Slackware template): -p|--path: path to the container. Default: ~/.local/share/lxc/ (unprivileged container) /var/lib/lxc/ (privileged container) --rootfs: root file system of the container. Default: ~/.local/share/lxc//rootfs (unprivileged container) /var/lib/lxc//rootfs (privileged container) -a|--arch: kernel architecture. Default: arch of the running kernel -r|--release:Slackware release default: current -n|--name: the container's internal hostname. Default: the container's given as argument of lxc-create -c|--clean: clean the cache. EOF return 0 } options=$(getopt -o hp:n:a:r:c -l help,rootfs:,path:,name:,arch:,release:,clean:,mapped-uid:,mapped-gid: -- "$@") if [ $? -ne 0 ]; then usage $(basename $0) exit 1 fi eval set -- "$options" while true do case "$1" in -h|--help) usage $0 && exit 0;; -p|--path) path=$2; shift 2;; --rootfs) rootfs=$2; shift 2;; -a|--arch) arch=$2; shift 2;; -r|--release) release=$2; shift 2;; -n|--name) name=$2; shift 2;; -c|--clean) clean=$2; shift 2;; --mapped-uid) LXC_MAPPED_UID=$2; shift 2;; --mapped-gid) LXC_MAPPED_GID=$2; shift 2;; --) shift 1; break ;; *) break ;; esac done if [ ! -z "$clean" -a -z "$path" ]; then clean || exit 1 exit 0 fi type installpkg if [ $? -ne 0 ]; then echo "'installpkg' command is missing." exit 1 fi type slackpkg if [ $? -ne 0 ]; then echo "'slackpkg' command is missing." exit 1 fi if [ -z "$path" ]; then echo "'path' parameter is required." exit 1 fi if [ "$(id -u)" != "0" ]; then echo "This script should be run as 'root'." exit 1 fi # If no release version was specified, use current release=${release:-current} # detect rootfs config="$path/config" if [ -z "$rootfs" ]; then if grep -q '^lxc.rootfs' $config 2>/dev/null ; then rootfs=$(awk -F= '/^lxc.rootfs =/{ print $2 }' $config) else rootfs=$path/rootfs fi fi echo set -e if [ $in_userns -eq 1 ]; then COPY_CONFIGURATION=copy_configuration_in_userns cache=${cache:-~/.cache/lxc/slackware} else COPY_CONFIGURATION=copy_configuration cache=${cache:-/var/cache/lxc/slackware} fi install_slackware $rootfs if [ $? -ne 0 ]; then echo "Failed to install slackware." exit 1 fi echo configure_slackware $cache/rootfs-$release-$arch $name if [ $? -ne 0 ]; then echo "Failed to configure slackware for a container." exit 1 fi echo rootfs=$path/rootfs copy_slackware $rootfs if [ $? -ne 0 ]; then echo "Failed to copy rootfs." exit 1 fi $COPY_CONFIGURATION $path $rootfs $name if [ $? -ne 0 ]; then echo "Failed to write configuration file." exit 1 fi remap_userns $path if [ $? -ne 0 ]; then echo "failed to remap files to user" exit 1 fi if [ ! -z $clean ]; then clean || exit 1 exit 0 fi