#!/usr/bin/bash

USAGE_HEADER=$(cat <<EOF
Generate QM sysusers configuration for the root partition based on users and groups
inside of QM with an given offset. This maps processes run by users inside of QM to
the respective qm_<user> on the root partition.
It also generates the necessary user namespace entries in /etc/subuid and /etc/subgid
as well as the drop-in file for QM to use the user namespace.

EOF
)

# Default values for user namespace settings
ROOT_DIR="/"
QM_ROOT_DIR="/usr/lib/qm/rootfs/"

ID_OFFSET=1000000000
ID_RANGE=1500000000

SYSUSERS_OUTPUT_DIR="/etc/sysusers.d/"
SYSUSERS_CONF_FILE="qm-sysusers.conf"
QM_DROPIN_DIR="/etc/containers/systemd/qm.container.d"
QM_DROPIN_FILE="qm-user-namespaces.conf"


function generate_sysusers_conf() {
    local id_offset=$1

    # shellcheck disable=SC2155
    local qm_etc_passwd_file="$(realpath -m "${QM_ROOT_DIR}/etc/passwd")"
    # shellcheck disable=SC2155
    local qm_etc_group_file="$(realpath -m "${QM_ROOT_DIR}/etc/group")"
    # shellcheck disable=SC2155
    local sysusers_conf_file="$(realpath -m "${ROOT_DIR}/${SYSUSERS_OUTPUT_DIR}/${SYSUSERS_CONF_FILE}")"

    echo "# Type        Name        ID      GECOS       Home directory      Shell" > "${sysusers_conf_file}"
    # shellcheck disable=SC2034
    while IFS=: read -r username password uid gid gecos home shell; do
        new_uid=$((id_offset+uid))
        new_gid=$((id_offset+gid))
        echo "u     qm_${username}      ${new_uid}:${new_gid}       \"QM - ${gecos}\""
    done < "${qm_etc_passwd_file}" >> "${sysusers_conf_file}"

    # shellcheck disable=SC2034
    while IFS=: read -r group password gid members; do
        new_gid=$((id_offset+gid))
        echo "g     qm_${group}     ${new_gid}"
    done < "${qm_etc_group_file}" >> "${sysusers_conf_file}"

    echo "Created '${sysusers_conf_file}'"
}

function generate_qm_dropin() {
    # shellcheck disable=SC2155
    local file_path="$(realpath -m "${ROOT_DIR}/${QM_DROPIN_DIR}/${QM_DROPIN_FILE}")"
    local user=$1

    cat > "${file_path}" <<EOF
[Container]
# Run the QM container in the user namespace qm using the map with name in the /etc/subgid file.
# It also maps to the QM user created via systemd sysusers.
SubUIDMap=${user}
SubGIDMap=${user}
EOF

    echo "Created '${file_path}'"
}

function add_subid_entries() {
    local id_range_start=$1
    local id_range=$2
    local id_range_end=$((id_range_start+id_range))
    local user=$3

    # shellcheck disable=SC2155
    local etc_subuid_file="$(realpath -m "${ROOT_DIR}/etc/subuid")"
    # shellcheck disable=SC2155
    local etc_subgid_file="$(realpath -m "${ROOT_DIR}/etc/subgid")"

    if ! grep -q "^${user}:" "${etc_subuid_file}" 2>/dev/null; then
        usermod --root="${ROOT_DIR}" --add-subuids "${id_range_start}-${id_range_end}" "${user}"
    fi
    if ! grep -q "^${user}:" "${etc_subgid_file}" 2>/dev/null; then
        usermod --root="${ROOT_DIR}" --add-subgids "${id_range_start}-${id_range_end}" "${user}"
    fi
}

function enable() {
    # shellcheck disable=SC2155
    local SETUP_USAGE=$(cat <<EOF
${USAGE_HEADER}

Usage:
    $(basename "${0}") enable [options]

Available options:
    --root-dir      The root filesystem of the host. Default: '${ROOT_DIR}'.
    --qm-root-dir   The root filesystem of QM. Default: '${QM_ROOT_DIR}'.
    --sysusers-dir  Directory inside the ${ROOT_DIR} where the '${SYSUSERS_CONF_FILE}' is being placed. Default: '${SYSUSERS_OUTPUT_DIR}'.
    --id-offset     UID/GID offset to be applied. Default: '${ID_OFFSET}'.
    --id-range      Number of UIDs/GIDs reserved for the QM user namespace. Default: '${ID_RANGE}'.

Flags:
    -h, --help  Print help
EOF
)

    while [[ $# -gt 0 ]]; do
        case $1 in
            --root-dir)
                ROOT_DIR="${2}"
                shift 2
                ;;
            --qm-root-dir)
                QM_ROOT_DIR="${2}"
                shift 2
                ;;
            --sysusers-dir)
                SYSUSERS_OUTPUT_DIR="${2}"
                shift 2
                ;;
            --id-offset)
                ID_OFFSET="${2}"
                shift 2
                ;;
            --id-range)
                ID_RANGE="${2}"
                shift 2
                ;;
            -h|--help)
                echo "$SETUP_USAGE"
                exit 0
                ;;
            --*)
                echo "Unknown option $1"
                echo
                echo "$SETUP_USAGE"
                exit 1
                ;;
            *)
                echo "Unknown command: $1"
                echo
                echo "$SETUP_USAGE"
                exit 1
                ;;
        esac
    done

    # Generate sysusers configuration and trigger systemd-sysusers so that
    # users are created (required by adding subids)
    generate_sysusers_conf "${ID_OFFSET}"
    systemd-sysusers --root="${ROOT_DIR}"

    # Add subuids/subgids
    # A root user is always expected, so the corresponding qm user is qm_root
    add_subid_entries "${ID_OFFSET}" "${ID_RANGE}" "qm_root"

    # Add SubUIDMap= and SubGIDMap= to QM via drop-in
    generate_qm_dropin "qm_root"
}


function remove_subid_entries() {
    local user=$1

    # shellcheck disable=SC2155
    local etc_subuid_file="$(realpath -m "${ROOT_DIR}/etc/subuid")"
    # shellcheck disable=SC2155
    local etc_subgid_file="$(realpath -m "${ROOT_DIR}/etc/subgid")"

    sed -i "/^${user}:/d" "${etc_subuid_file}"
    sed -i "/^${user}:/d" "${etc_subgid_file}"
}


function disable() {
    # shellcheck disable=SC2155
    local SETUP_USAGE=$(cat <<EOF
${USAGE_HEADER}

Usage:
    $(basename "${0}") disable [options]

Available options:
    --root-dir      The root filesystem of the host. Default: '${ROOT_DIR}'.
    --qm-root-dir   The root filesystem of QM. Default: '${QM_ROOT_DIR}'.
    --sysusers-dir  Directory inside the ${ROOT_DIR} where the '${SYSUSERS_CONF_FILE}' is located. Default: '${SYSUSERS_OUTPUT_DIR}'.

Flags:
    -h, --help  Print help
EOF
)

    while [[ $# -gt 0 ]]; do
        case $1 in
            --root-dir)
                ROOT_DIR="${2}"
                shift 2
                ;;
            --qm-root-dir)
                QM_ROOT_DIR="${2}"
                shift 2
                ;;
            --sysusers-dir)
                SYSUSERS_OUTPUT_DIR="${2}"
                shift 2
                ;;
            -h|--help)
                echo "$SETUP_USAGE"
                exit 0
                ;;
            --*)
                echo "Unknown option $1"
                echo
                echo "$SETUP_USAGE"
                exit 1
                ;;
            *)
                echo "Unknown command: $1"
                echo
                echo "$SETUP_USAGE"
                exit 1
                ;;
        esac
    done

    remove_subid_entries "qm_root"
    rm -f "$(realpath -m "${ROOT_DIR}/${QM_DROPIN_DIR}/${QM_DROPIN_FILE}")"
    rm -f "$(realpath -m "${ROOT_DIR}/${SYSUSERS_OUTPUT_DIR}/${SYSUSERS_CONF_FILE}")"
}


# Only parse CLI options etc. when invoked directly
# Do nothing when sourced
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then

    BASIC_USAGE=$(cat <<EOF
${USAGE_HEADER}

Usage:
    $(basename "${0}") [enable|disable] [options]

Flags:
    -h, --help  Print help
EOF
)
    case $1 in
        enable)
            shift
            enable "$@"
            exit 0
            ;;
        disable)
            shift
            disable "$@"
            exit 0
            ;;
        -h|--help)
            echo "${BASIC_USAGE}"
            exit 0
            ;;
        *)
            echo "No or unknown positional argument"
            echo
            echo "${BASIC_USAGE}"
            exit 1
            ;;
    esac

fi
