#!/bin/bash

#
# lxc: linux Container library

# Authors:
# Daniel Lezcano <daniel.lezcano@free.fr>
# Updated to work with LXC 4.0.x by Matthew Chamley <mcgchamley@yahoo.co.uk>

# Template for slackware by Matteo Bernardini <ponce@slackbuilds.org>
# some parts are taken from the debian one (used as model)

# 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

cache=${cache:-/var/cache/lxc/slarm64}

# Use the primary slarm64 site by default, but please consider changing
# this to a closer mirror site.
MIRROR=${MIRROR:-http://dl.slarm64.org/slarm64}

if [ -z "$arch" ]; then
case "$( uname -m )" in
    i?86) arch=i486 ;;
    arm*) arch=arm ;;
       *) arch=$( uname -m ) ;;
esac
fi

configure_slarm64()
{
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 <<EOF > $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
mkdir -p ${DEV}
# Remove any existing "devices" first, as these might be files
# created by package install scripts:
for clear_device in ${DEV}/null ${DEV}/zero ${DEV}/random ${DEV}/urandom \
  ${DEV}/tty ${DEV}/console ${DEV}/tty0 ${DEV}/tty1 ${DEV}/tty2 ${DEV}/tty3 \
  ${DEV}/tty4 ${DEV}/tty5 ${DEV}/full ${DEV}/initctl ${DEV}/loop0 \
  ${DEV}/loop1 ; do
  rm -f $clear_device
done
# Create initial set of devices:
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
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 <<EOF
lxcpts $rootfs/dev/pts devpts defaults,newinstance 0 0
none $rootfs/proc    proc   defaults 0 0
none $rootfs/sys     sysfs  defaults 0 0
none /dev/shm tmpfs defaults 0 0
none /run tmpfs defaults,mode=0755 0 0
EOF

# restart rc.inet1 to have routing for the loop device
echo "/etc/rc.d/rc.inet1 restart" >> $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

# create the library symlinks
chroot $rootfs ldconfig

# set a default combination for the luggage
echo "root:root" | chroot $rootfs chpasswd 2> /dev/null
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_slarm64()
{
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_slarm64()
{
rootfs=$1
mkdir -p /var/lock/subsys/
(
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
elif [ "$arch" == "aarch64" ]; then
        PKGMAIN=slarm64
elif [ "$arch" == "riscv64" ]; then
        PKGMAIN=slarm64-riscv64
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 <<EOF > $CONF/slackpkg.conf
# v15.0
ARCH=$arch
TEMP=$cache/cache-$release-$arch
WORKDIR=$cache/slackpkg-$release-$arch
WGETFLAGS="--passive-ftp"
DELALL=off
CHECKMD5=on
CHECKGPG=on
CHECKSIZE=off
PRIORITY=( patches %PKGMAIN extra pasture testing )
POSTINST=on
ONLY_NEW_DOTNEW=off
ORIG_BACKUPS=on
ONOFF=on
DOWNLOAD_ALL=on
DIALOG=off
BATCH=on
DEFAULT_ANSWER=y
USE_INCLUDES=on
SPINNING=off
DIALOG_MAXARGS=139000
EOF

# thanks to Vincent Batts for this list of packages
# (that I modified a little :P)
# http://connie.slackware.com/~vbatts/minimal/
cat <<EOF > $CONF/templates/minimal-lxc.template
aaa_base
aaa_elflibs
aaa_glibc-solibs
aaa_libraries
aaa_terminfo
bash
bin
bridge-utils
bzip2
coreutils
cracklib
cyrus-sasl
db48
dbus
dcron
dhcpcd
dialog
diffutils
e2fsprogs
elfutils
elogind
elvis
etc
eudev
findutils
gawk
gnupg
gnutls
grep
gzip
hostname
iproute2
iptables
iputils
kmod
less
libcap-ng
libffi
libmnl
libnl3
libpcap
libpwquality
libtasn1
libtirpc
libunistring
logrotate
mpfr
net-tools
nettle
network-scripts
ncurses
pam
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/*

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/lxc

return $?
}

copy_configuration()
{
path=$1
rootfs=$2
name=$3

cat <<EOF >> $path/config

# Adding a . for LXC 4.0.x
lxc.uts.name = $name

# Adding .fstab for LXC 4.0.x
lxc.mount.fstab = $rootfs/etc/fstab

# Added .max and .path for LXC 4.0.x
lxc.tty.max = 4
lxc.pty.max = 1024
lxc.rootfs.path = $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
}

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/lxc
}

usage()
{
cat <<EOF
$1 -h|--help -p|--path=<path> --clean
EOF
return 0
}

options=$(getopt -o hp:n:a:r:c -l help,rootfs:,path:,name:,arch:,release:,clean --  "$@")
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;;
        --)             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}

if [ -z "$name" ]; then
	# no name given? set a default one
	name=slarm64container
fi

# 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

install_slarm64 $rootfs
if [ $? -ne 0 ]; then
	echo "Failed to install slarm64."
	exit 1
fi

echo

configure_slarm64 $cache/rootfs-$release-$arch $name
if [ $? -ne 0 ]; then
	echo "Failed to configure slarm64 for a container."
	exit 1
fi

echo

rootfs=$path/rootfs
copy_slarm64 $rootfs
if [ $? -ne 0 ]; then
	echo "Failed to copy rootfs."
	exit 1
fi

echo

copy_configuration $path $rootfs $name
if [ $? -ne 0 ]; then
	echo "Failed to write configuration file."
	exit 1
fi

if [ ! -z $clean ]; then
	clean || exit 1
	exit 0
fi
