#!/bin/bash #emacs: -*- mode: shell-script; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- #ex: set sts=4 ts=4 sw=4 noet: # # A helper to run/exec singularity images with sanitization: # - fake HOME with minimalistic .bashrc and .gitconfig # - dedicated /tmp /var/tmp # - cleansed environment variables # while bind mounting (and starting from) current directory. # # COPYRIGHT: Yaroslav Halchenko 2019-2023 # # LICENSE: MIT # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # function info() { : # echo -e "I: " "$@" >&2 } # # Generate $BHOME/.gitconfig if not present or missing required minimal user # info based on the current/active one. # Entry will be added, so if already contains a value, we will keep adding. # function pass_git_config() { var="$1" # if variable is not defined at all if ! git config "$var" > /dev/null ; then # and default was not provided - nothing for us to do [ "$#" -ge 2 ] || return 0 # otherwise, use that default VALUES=( "$2" ) else # read all values into an array mapfile -t VALUES < <(git config --get-all "$var") fi for value in "${VALUES[@]}"; do # git config reads without locking, but could lock for writing # so we might need to try multiple times setting the value # # shellcheck disable=SC2034 for attempt in {1..5}; do if git config -f "$BHOME/.gitconfig" --add "$var" "$value" 2>/dev/null then break fi # we failed - sleep a little. Alert: bashism! sleep 0."${RANDOM:0:1}" done done } function test_pass_git_config() { if [ "$(uname -s)" = Darwin ]; then BHOME=$(mktemp -d -t s) else BHOME=$(mktemp -d -t singtmp.XXXXXX) fi # TODO: To make it into a proper test, can create a temp HOME and populate sample # .gitconfig, and then compare with the result echo "TEMP HOME=$BHOME" pass_git_config "user.name" "Must not be used" pass_git_config "user.absent" "Default User" pass_git_config "annex.unset" pass_git_config "annex.autoupgraderepository" pass_git_config "safe.directory" echo "new git config" cat "$BHOME/.gitconfig" } # test_pass_git_config # exit 0 function readlink_f() { readlink -f "$1" 2> /dev/null || python -c 'import sys, os ; print(os.path.realpath(sys.argv[1]))' "$1" } if [ "$#" = 0 ]; then echo "Using as a library. Run with 'exec' or 'run' command and options." exit 0 fi set -eu info "PWD=$PWD" thisfile=$(readlink_f "$0") thisdir=$(dirname "$thisfile") updir=$(dirname "$thisdir") BHOME="$updir/binds/HOME" cmd="${SINGULARITY_CMD:-$1}"; shift if [ -n "${DATALAD_CONTAINER_NAME:-}" ]; then export SINGULARITYENV_DATALAD_CONTAINER_NAME="$DATALAD_CONTAINER_NAME" # for singularity -> apptainer migration and it seems that both are ok # see https://github.com/ReproNim/containers/issues/69 export APPTAINERENV_DATALAD_CONTAINER_NAME="$DATALAD_CONTAINER_NAME" fi # fix non-writable matplotlib cache dir in apptainer https://github.com/ReproNim/containers/issues/97 export SINGULARITYENV_MPLCONFIGDIR=/tmp/mpl-config export APPTAINERENV_MPLCONFIGDIR=/tmp/mpl-config # singularity bind mounts system /tmp, which might result in side-effects # Create a dedicated temporary directory to be removed upon completion # Mac's tmpdir (with default options) causes problems when a singularity # container in a docker container tries to create a socket in the # (double-bound) directory. Shortening the name of the directory is one way # to fix the problem. See https://github.com/ReproNim/containers/issues/57 if [ "$(uname -s)" = Darwin ]; then tmpdir=$(mktemp -d -t s) else tmpdir=$(mktemp -d -t singtmp.XXXXXX) fi # To know what base would need to be mounted within docker tmpbase=$(dirname "$tmpdir") # Create new isolated /tmp to be bound mount mkdir -p "$tmpdir/tmp" info "created temp dir $tmpdir" trap 'rm -fr "$tmpdir" && info "removed temp dir $tmpdir"' exit # We start anew since we use --add to support multi-value entries if [ -e "$BHOME/.gitconfig" ]; then rm -f "$BHOME/.gitconfig" fi pass_git_config "user.name" "ReproNim User" pass_git_config "user.email" "nobody@example.com" # only if set - pass pidlock so we could operate on NFS mounts, like on rolando pass_git_config "annex.pidlock" pass_git_config "safe.directory" # Common arguments for the singularity run SARGS=( -e -B "$PWD" -H "$BHOME" --pwd "$PWD" "$@" ) # Due to https://github.com/sylabs/singularity/issues/3949 # which was fixed only in v3.3.0-rc.1-431-g40331a5b1 # for now we need to avoid using `-c` in such cases and # just create those needed bind points manually and bind mount # /tmp manually (apparently -W is not in effect really without -c). # TODO: make it also check/depend on current version of singularity need_no_c= for d in "$PWD" "$updir"; do d_normalized=$(readlink_f "$d") if [ "${d_normalized##/tmp/}" != "$d_normalized/" ]; then # we should create that one within our $tmpdir info "Creating $d and /var/tmp under $tmpdir" mkdir -p "$tmpdir$d" "$tmpdir/var/tmp" need_no_c=1 fi done if [ -z "$need_no_c" ]; then SARGS=( -c "${SARGS[@]}" ) else SARGS=( -B "$tmpdir/tmp:/tmp" -B "$tmpdir/var/tmp:/var/tmp" "${SARGS[@]}" ) fi SARGS=( -B "$updir/binds/zoneinfo/UTC:/etc/localtime" "${SARGS[@]}" ) # set -x if hash singularity 2>/dev/null && [ -z "${REPRONIM_USE_DOCKER:-}" ]; then singularity "$cmd" -W "$tmpdir" "${SARGS[@]}" else # we need to bind mount all bases without remapping DARGS=( --privileged --rm -e "UID=$(id -u)" -e "GID=$(id -g)" -v "$tmpbase:$tmpbase" -v "$PWD:$PWD" -v "$updir:$updir" -v "$BHOME:$BHOME" -w "$PWD") if [ -n "${REPRONIM_DOCKER_OPTS:-}" ]; then # ATM I do want to split all possible options into separate ones if provided, # shellcheck disable=SC2206 DARGS=( ${REPRONIM_DOCKER_OPTS} "${DARGS[@]}" ) fi # Even though we bind UTC zoneinfo via singularity -B, it does try to bind # it first itself, and if not present -- would fail. So we do bind mount it # also into Docker environment to make sure that some /etc/localtime is available DARGS=( "${DARGS[@]}" -v "$updir/binds/zoneinfo/UTC:/etc/localtime" ) docker run \ "${DARGS[@]}" \ repronim/containers:latest \ "$cmd" "${SARGS[@]}" fi