#!/bin/sh

# IOPSYS specific template for LXC container images.
#
# Copyright © 2021 IOPSYS Software Solutions AB
# Author: Vivek Dutta <vivek.dutta@iopsys.eu>
#
# This template is based on lxc-busybox template

LXC_MAPPED_UID=
LXC_MAPPED_GID=

BUSYBOX_EXE=$(command -v busybox)

# Make sure the usual locations are in PATH
export PATH=$PATH:/usr/sbin:/usr/bin:/sbin:/bin

in_userns() {
    [ -e /proc/self/uid_map ] || { echo no; return; }
    while read -r line; do
        fields="$(echo "$line" | awk '{ print $1 " " $2 " " $3 }')"
        if [ "${fields}" = "0 0 4294967295" ]; then
            echo no;
            return;
        fi
        if echo "${fields}" | grep -q " 0 1$"; then
            echo userns-root;
            return;
        fi
    done < /proc/self/uid_map

    if [ -e /proc/1/uid_map ]; then
        if [ "$(cat /proc/self/uid_map)" = "$(cat /proc/1/uid_map)" ]; then
            echo userns-root
                return
        fi
    fi
    echo yes
}

USERNS="$(in_userns)"

install_busybox()
{
    rootfs="${1}"
    name="${2}"
    res=0
    fstree="\
        ${rootfs}/dev \
        ${rootfs}/home \
        ${rootfs}/root \
        ${rootfs}/etc \
        ${rootfs}/etc/config \
        ${rootfs}/etc/init.d \
        ${rootfs}/etc/rc.d \
        ${rootfs}/etc/swmod \
        ${rootfs}/etc/hotplug.d \
        ${rootfs}/bin \
        ${rootfs}/sbin \
        ${rootfs}/usr/bin \
        ${rootfs}/sbin \
        ${rootfs}/usr/sbin \
        ${rootfs}/proc \
        ${rootfs}/sys \
        ${rootfs}/mnt \
        ${rootfs}/tmp \
        ${rootfs}/var/log \
        ${rootfs}/var/lock \
        ${rootfs}/var/run \
        ${rootfs}/dev/pts \
        ${rootfs}/lib \
        ${rootfs}/usr/lib \
        ${rootfs}/lib64 \
        ${rootfs}/usr/lib64"

    # shellcheck disable=SC2086
    mkdir -p ${fstree} || return 1
    # shellcheck disable=SC2086
    chmod 755 ${fstree} || return 1

    # minimal devices needed for busybox
    if [ "${USERNS}" = "yes" ]; then
        for dev in tty console tty0 tty1 ram0 null urandom; do
            echo "lxc.mount.entry = /dev/${dev} dev/${dev} none bind,optional,create=file 0 0" >> "${path}/config"
        done
    fi

    # make /tmp accessible to any user (with sticky bit)
    chmod 1777 "${rootfs}/tmp" || return 1

    # root user defined
    cat <<EOF >> "${rootfs}/etc/passwd"
root:x:0:0:root:/root:/bin/ash
EOF

    cat <<EOF >> "${rootfs}/etc/shadow"
root::18844:0:99999:7:::
EOF

    cat <<EOF >> "${rootfs}/etc/group"
root:x:0:
EOF
    # mount everything
    cat <<EOF >> "${rootfs}/etc/rc.local"
#!/bin/sh

echo "Running rc.local"
/bin/mount -a
/bin/udhcpc -i eth0
EOF

    # executable
    chmod 744 "${rootfs}/etc/rc.local" || return 1

    # launch rcS first then make a console available
    # and propose a shell on the tty, the last one is
    # not needed
    cat <<EOF >> "${rootfs}/etc/init.d/boot"
#!/bin/sh /etc/rc.common

# run this script to get ip
START=9

start() {
        # process user commands
        [ -f /etc/rc.local ] && {
                sh /etc/rc.local
        }

        echo "All init.d scripts started."
}
EOF

    chmod +x "${rootfs}/etc/init.d/boot"

    cat <<EOF >> "${rootfs}/bin/boot_lxc"
#!/bin/sh

for f in \`ls -1 /etc/init.d/\`; do
    /etc/init.d/\$f enable
    /etc/init.d/\$f start
done

/bin/login root
EOF
    chmod +x "${rootfs}/bin/boot_lxc"

    cat <<EOF >> "${rootfs}/etc/inittab"
::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
console::respawn:/bin/boot_lxc
EOF
    # writable and readable for other
    chmod 644 "${rootfs}/etc/inittab" || return 1

    # Look for the pathname of "default.script" from the help of udhcpc
    DEF_SCRIPT=$(${BUSYBOX_EXE} udhcpc --help 2>&1 | egrep -- '-s.*Run PROG' | cut -d'/' -f2- | cut -d')' -f1)
    DEF_SCRIPT_DIR=$(dirname /"${DEF_SCRIPT}")
    mkdir -p "${rootfs}/${DEF_SCRIPT_DIR}"
    chmod 644 "${rootfs}/${DEF_SCRIPT_DIR}" || return 1

    cat <<EOF >> "${rootfs}/${DEF_SCRIPT}"
#!/bin/sh
case "\$1" in
  deconfig)
    ip addr flush dev \$interface
    ;;

  renew|bound)
    # flush all the routes
    if [ -n "\$router" ]; then
      ip route del default 2> /dev/null
    fi

    # check broadcast
    if [ -n "\$broadcast" ]; then
      broadcast="broadcast \$broadcast"
    fi

    # add a new ip address
    ip addr add \$ip/\$mask \$broadcast dev \$interface

    if [ -n "\$router" ]; then
      ip route add default via \$router dev \$interface
    fi

    [ -n "\$domain" ] && echo search \$domain > /etc/resolv.conf
    for i in \$dns ; do
      grep "nameserver \$i" /etc/resolv.conf > /dev/null 2>&1
      if [ \$? -ne 0 ]; then
        echo nameserver \$i >> /etc/resolv.conf
      fi
    done
    ;;
esac
exit 0
EOF

    chmod 744 "${rootfs}/${DEF_SCRIPT}"

    return "${res}"
}

configure_busybox()
{
    rootfs="${1}"

    # copy busybox in the rootfs
    if ! cp "${BUSYBOX_EXE}" "${rootfs}/bin"; then
        echo "ERROR: Failed to copy busybox binary" 1>&2
        return 1
    fi

    # symlink busybox for the commands it supports
    # it would be nice to just use "chroot $rootfs busybox --install -s /bin"
    # but that only works right in a chroot with busybox >= 1.19.0
    (
        cd "${rootfs}/bin" || return 1
        ./busybox --list | grep -v busybox | xargs -n1 ln -s busybox
    )

    # /etc/fstab must exist for "mount -a"
    touch "${rootfs}/etc/fstab"

    # passwd exec must be setuid
    chmod +s "${rootfs}/bin/passwd"
    touch "${rootfs}/etc/shadow"

    return 0
}

copy_prereq()
{
    local rootfs pkgs

    rootfs="${1}"
    shift
    pkgs="${@}"
    req_pkg="procd uci opkg ip-full curl libstdcpp6 librt"

    for p in ${req_pkg}; do
        /usr/share/swmodd/opkg_offline "${rootfs}" "${p}"
    done

    for p in ${pkgs}; do
        /usr/share/swmodd/opkg_offline "${rootfs}" "${p}"
    done

    cp /etc/preinit "${rootfs}/etc/preinit"
    cp /etc/diag.sh "${rootfs}/etc/diag.sh"
    cp /sbin/mount_root "${rootfs}/sbin/mount_root"
    cp /sbin/ip "${rootfs}/sbin/ip"
    cp /etc/rc.common "${rootfs}/etc/rc.common"
    if [ -f "${rootfs}/bin/uclient-fetch" ] && [ ! -f "${rootfs}/usr/bin/wget" ]; then
        ln -sf /bin/uclient-fetch "${rootfs}/usr/bin/wget"
    fi

    return 0;
}

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

    grep -q "^lxc.rootfs.path" "${path}/config" 2>/dev/null || echo "lxc.rootfs.path = ${rootfs}" >> "${path}/config"
    cat <<EOF >> "${path}/config"
lxc.uts.name = "${name}"
lxc.cap.drop = mac_admin
lxc.cap.drop = mac_override
lxc.cap.drop = sys_admin
lxc.cap.drop = sys_boot
lxc.cap.drop = sys_module
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_time
lxc.cap.drop = sys_tty_config
lxc.cap.drop = syslog
lxc.cap.drop = wake_alarm

lxc.autodev = 1
lxc.console.buffer.size = auto
lxc.tty.max = 5
lxc.pty.max = 5
lxc.mount.auto = cgroup:mixed proc:mixed sys:mixed

lxc.net.0.type = veth
lxc.net.0.flags = up
lxc.net.0.link = br-lan
lxc.net.0.name = eth0
lxc.net.0.mtu = 1500
EOF

    libdirs="lib"

    #cp -rf /lib/functions.sh ${rootfs}/lib/
    #cp -rf /lib/functions ${rootfs}/lib/
    #cp -rf /lib/preinit ${rootfs}/lib/
    for dir in ${libdirs}; do
        if [ -d "/${dir}" ] && [ -d "${rootfs}/${dir}" ]; then
            echo "lxc.mount.entry = /${dir} ${dir} none ro,bind 0 0" >> "${path}/config"
        fi
    done
    echo "lxc.mount.entry = /sys/kernel/security sys/kernel/security none ro,bind,optional 0 0" >> "${path}/config"
}

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 <<EOF
LXC iopsys image builder

Special arguments:

  [ -h | --help ]: Print this help message and exit.

LXC internal arguments:

  [ --name <name> ]: The container name
  [ --path <path> ]: The path to the container
  [ --rootfs <rootfs> ]: The path to the container's rootfs (default: config or <path>/rootfs)
  [ --mapped-uid <map> ]: A uid map (user namespaces)
  [ --mapped-gid <map> ]: A gid map (user namespaces)

IOPSYS template specific arguments:

  [ --busybox-path <path> ]: busybox pathname (default: ${BUSYBOX_EXE})

EOF
  return 0
}

if ! options=$(getopt -o hp:n: -l help,rootfs:,path:,name:,mapped-uid:,mapped-gid:,busybox-path: -- "$@"); then
    usage
    exit 1
fi
eval set -- "$options"

while true
do
    case "$1" in
        -h|--help)    usage && exit 0;;
        -n|--name)    name=$2; shift 2;;
        -p|--path)    path=$2; shift 2;;
        --rootfs)     rootfs=$2; shift 2;;
        --mapped-uid) LXC_MAPPED_UID=$2; shift 2;;
        --mapped-gid) LXC_MAPPED_GID=$2; shift 2;;
        --busybox-path) BUSYBOX_EXE=$2; shift 2;;
        --)           shift 1; break ;;
        *)            break ;;
    esac
done

PKGS="$@"
# Check that we have all variables we need
if [ -z "${name}" ] || [ -z "${path}" ]; then
    echo "ERROR: Please pass the name and path for the container" 1>&2
    exit 1
fi

# Make sure busybox is present
if [ -z "${BUSYBOX_EXE}" ]; then
    echo "ERROR: Please pass a pathname for busybox binary" 1>&2
    exit 1
fi
if [ ! -x "${BUSYBOX_EXE}" ]; then
    echo "ERROR: Failed to find busybox binary (${BUSYBOX_EXE})" 1>&2
    exit 1
fi

# detect rootfs
config="$path/config"
if [ -z "$rootfs" ]; then
    if grep -q '^lxc.rootfs.path' "${config}" 2> /dev/null ; then
        rootfs=$(awk -F= '/^lxc.rootfs.path =/{ print $2 }' "${config}")
    else
        rootfs="${path}/rootfs"
    fi
fi

if ! install_busybox "${rootfs}" "${name}"; then
    echo "ERROR: Failed to install rootfs" 1>&2
    exit 1
fi

if ! configure_busybox "${rootfs}"; then
    echo "ERROR: Failed to configure busybox" 1>&2
    exit 1
fi

if ! copy_configuration "${path}" "${rootfs}" "${name}"; then
    echo "ERROR: Failed to write config file" 1>&2
    exit 1
fi

if ! remap_userns "${path}"; then
    echo "ERROR: Failed to change idmappings" 1>&2
    exit 1
fi

if ! copy_prereq "${rootfs}" "${PKGS}"; then
    echo "ERROR: Failed to copy required binaries" 1>&2
    exit 1
fi

if command -v crun_create; then
	bundle_path=$(echo ${path%/$name})
	env_name=$(echo ${bundle_path##/*/})
	crun_create -c -e "$env_name" -n "$name" -u "local://lxc_template"
fi
