#!/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 # # 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. # set -eu function info() { : # echo -e "I: " "$@" >&2 } # # Generate $BHOME/.gitconfig if not present or missing required minimal user # info based on the current one # function pass_git_config() { var="$1" default="$2" # git config reads without locking, but could lock for writing # so we might need to try multiple times setting the value value=$(git config "$var" || echo "$default") # shellcheck disable=SC2034 for attempt in {1..5}; do git config -f "$BHOME/.gitconfig" "$var" >/dev/null \ || { git config -f "$BHOME/.gitconfig" "$var" "$value" 2>/dev/null && break; } \ && break # we failed - sleep a little. Alert: bashism! sleep 0."${RANDOM:0:1}" done } function readlink_f() { readlink -f "$1" 2> /dev/null || python -c 'import sys, os ; print(os.path.realpath(sys.argv[1]))' "$1" } 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 pass_git_config "user.name" "ReproNim User" pass_git_config "user.email" "nobody@example.com" # 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