#!/bin/bash
# A launcher/builder/publisher for isystemtap containers
# Copyright (C) 2023 Red Hat Inc.
#
# This file is part of systemtap, and is free software.  You can
# redistribute it and/or modify it under the terms of the GNU General
# Public License (GPL); either version 2, or (at your option) any
# later version.
set -e

usage() { echo "Usage: stap-jupyter-container [--repo REPOSITORY] [--image IMAGE] [--tag TAG] [--keyname KEYNAME] --{run, pull, build, publish, remove}" 1>&2; exit 1; }

NOTEBOOK_DIR="/usr/share/systemtap/interactive-notebook"
declare -A options=( ["--repo"]="quay.io" ["--image"]="systemtap/isystemtap" ["--tag"]="latest" ["--keyname"]="id_rsa" )

while (( "$#" )); do
    # Parse the mode
    if [[ $1 == --*(run|pull|build|publish|remove) ]]; then
        if [ -z "$MODE" ]; then
            MODE=$1;
        else
            echo The mode may only be defined once; usage
        fi
    # Parse options
    elif [[ $1 == --*(repo|image|tag|keyname)* ]]; then
        flag=$1
        if [[ $flag == --*=* ]]; then
            arg=`echo $flag | awk -F "=" '{print $2}'`
            flag=`echo $flag | awk -F "=" '{print $1}'`
            if [ -z "$arg" ]; then echo "$flag cannot have an empty argument"; usage; fi
        else
            if [ $# -lt 2 ]; then echo "$flag cannot have an empty argument"; usage; fi
            arg="$2"
            shift
        fi
        options[$flag]=$arg
    else
        echo $1 is not a valid argument; usage
    fi
    shift
done


if [ "$MODE" = '--run' ]; then
    HOST_USER=`whoami`
    # The container user
    NB_USER="jovyan"

    HOST_SSH_DIR=$HOME/.ssh
    PRV_KEY_FILE=$HOST_SSH_DIR/${options[--keyname]}
    PUB_KEY_FILE=$HOST_SSH_DIR/${options[--keyname]}.pub
    if [ ! -f $PUB_KEY_FILE -o ! -f $PRV_KEY_FILE ]; then
        echo "A ssh public-private key pair is required! Create one with ssh-keygen";
        echo "If using --keyname ${options[--keyname]} they must be called ${options[--keyname]} and ${options[--keyname]}.pub"
        exit 1
    fi

    # Each run of the container will generate a tmpdir in /tmp
    CONTAINER_TMP_DIR=`mktemp -d "/tmp/systemtap_jupyter_container_XXXXXXXXXX"`
    chmod 777 $CONTAINER_TMP_DIR
    echo "Using tempdir $CONTAINER_TMP_DIR"
    CONTAINER_SSH_DIR=$CONTAINER_TMP_DIR/.ssh
    LOCALHOST=127.0.0.1

    # We store the private key & a config file in this .ssh dir which will be mounted
    echo "Setting up local .ssh directory $CONTAINER_SSH_DIR for ssh from container to localhost. Your ssh keys will never be saved in the container"
    mkdir -m 700 $CONTAINER_SSH_DIR/
    echo -e "Host $LOCALHOST\n\tStrictHostKeyChecking no\n\tUser $HOST_USER\n" > $CONTAINER_SSH_DIR/config
    chmod 600 $CONTAINER_SSH_DIR/config
    cp -pv $PRV_KEY_FILE $CONTAINER_SSH_DIR

    # If the public key is not in authorized_keys (on the host), add it
    if ! grep -Fxq "`cat $PUB_KEY_FILE`" $HOST_SSH_DIR/authorized_keys 2> /dev/null
    then
        # Since the keys exist in HOST_SSH_DIR its safe to assume the directory exists and is well formed
        cat $PUB_KEY_FILE >> $HOST_SSH_DIR/authorized_keys
        chmod 600 $HOST_SSH_DIR/authorized_keys
    fi

    # Extract the constants.py values, since the locally installed version of stap
    # is what the container needs (tr removes whitespace)
    STAP_VERSION_DEF=`grep VERSION $NOTEBOOK_DIR/isystemtap/constants.py | tr -d " \t\n\r"`
    STAP_PREFIX_DEF=`grep PREFIX $NOTEBOOK_DIR/isystemtap/constants.py | tr -d " \t\n\r"`
    STAP_PKGDATADIR_DEF=`grep PKGDATADIR $NOTEBOOK_DIR/isystemtap/constants.py | tr -d " \t\n\r"`
    # We also unpack the static dir containing the examples since
    # we need to mount that 
    STAP_PKGDATADIR=`echo "$STAP_PKGDATADIR_DEF" | tr -d "' " | awk -F "=" '{print $2}'`

    run_args=(
        # We need privileged network access to freely ssh/run websockets, etc.
        --privileged  \
        --net=host    \
        # MOUNTED VOLUMES ----------------------
        # Monitor mode interactions occur in /proc/systemtap/MODULE_NAME
        -v /proc:/proc:rw \
        # The directory where we will find the examples, tapsets, etc.
        # If installed correctly it is the parent of NOTEBOOK_DIR
        -v $STAP_PKGDATADIR:$STAP_PKGDATADIR \
        -v $CONTAINER_SSH_DIR:/home/$NB_USER/.ssh \
        # ENVIRONMENT VARIABLES ------------------
        -e $STAP_VERSION_DEF \
        -e $STAP_PREFIX_DEF \
        -e $STAP_PKGDATADIR_DEF
    )

    if [ $EUID -ne 0 ]; then
        # When running as a regular user we remap so that the container id 0 (which is the podman caller) maps
        # to the u/gid of jovyan the container user
        NB_UID=1000
        NB_GID=100
        SUBUID_SIZE=$(( $(podman info --format "{{ range .Host.IDMappings.UIDMap }}+{{.Size }}{{end }}" ) - 1 ))
        SUBGID_SIZE=$(( $(podman info --format "{{ range .Host.IDMappings.GIDMap }}+{{.Size }}{{end }}" ) - 1 ))

        run_args+=(
            --user $NB_UID:$NB_GID \
            --uidmap $NB_UID:0:1 --uidmap 0:1:$NB_UID --uidmap $(($NB_UID+1)):$(($NB_UID+1)):$(($SUBUID_SIZE-$NB_UID)) \
            --gidmap $NB_GID:0:1 --gidmap 0:1:$NB_GID --gidmap $(($NB_GID+1)):$(($NB_GID+1)):$(($SUBGID_SIZE-$NB_GID)) \
            # It's convinient to be able to access the host's home directory and working directory
            -v $PWD:/home/$NB_USER/working_dir \
            -v $HOME:/home/$NB_USER/${HOST_USER}_home_dir
        )
    else
        # When running as root we create a workdir so we can share data with the user (in the temp dir)
        echo Creating a shared host-container working directory
        WORKING_DIR=$CONTAINER_TMP_DIR/workdir
        mkdir -v $WORKING_DIR

        run_args+=(
            -v $WORKING_DIR:/home/$NB_USER/working_dir \
            --user root \
            -e CHOWN_EXTRA="/home/$NB_USER/.ssh",/home/$NB_USER/working_dir \
            -e CHOWN_EXTRA_OPTS="-R"
        )
    fi

    podman run -it --rm --name isystemtap "${run_args[@]}" "${options[--repo]}/${options[--image]}:${options[--tag]}"
elif [ "$MODE" = '--pull' ]; then
    podman pull "${options[--repo]}/${options[--image]}:${options[--tag]}"
elif [ "$MODE" = '--build' ]; then
    # Build the image 
    podman build \
        --file "$NOTEBOOK_DIR/container/Dockerfile" \
        --format="docker" \
        --tag="${options[--image]}:${options[--tag]}" \
        "$NOTEBOOK_DIR/.."
elif [ "$MODE" = '--publish' ]; then
    podman login $REPO
    podman push "${options[--image]}" "${options[--repo]}/${options[--image]}:${options[--tag]}"
elif [ "$MODE" = '--remove' ]; then
    podman image rm "${options[--repo]}/${options[--image]}:${options[--tag]}"
else
    usage;
fi
