#!/bin/sh set -eo pipefail # snippet from https://github.com/mkropat/sh-realpath realpath() { canonicalize_path "$(resolve_symlinks "$1")" } resolve_symlinks() { _resolve_symlinks "$1" } _resolve_symlinks() { _assert_no_path_cycles "$@" || return local dir_context path path=$(readlink -- "$1") if [ $? -eq 0 ]; then dir_context=$(dirname -- "$1") _resolve_symlinks "$(_prepend_dir_context_if_necessary "$dir_context" "$path")" "$@" else printf '%s\n' "$1" fi } _prepend_dir_context_if_necessary() { if [ "$1" = . ]; then printf '%s\n' "$2" else _prepend_path_if_relative "$1" "$2" fi } _prepend_path_if_relative() { case "$2" in /* ) printf '%s\n' "$2" ;; * ) printf '%s\n' "$1/$2" ;; esac } _assert_no_path_cycles() { local target path target=$1 shift for path in "$@"; do if [ "$path" = "$target" ]; then return 1 fi done } canonicalize_path() { if [ -d "$1" ]; then _canonicalize_dir_path "$1" else _canonicalize_file_path "$1" fi } _canonicalize_dir_path() { (cd "$1" 2>/dev/null && pwd -P) } _canonicalize_file_path() { local dir file dir=$(dirname -- "$1") file=$(basename -- "$1") (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file") } . ~/.config/contain if [[ -f "$(which docker 2>/dev/null)" ]]; then runtime=docker elif [[ -f "$(which podman 2>/dev/null)" ]]; then runtime=podman runtime_extra_opts="$runtime_extra_opts--userns=keep-id " fi if $runtime info | grep -iq selinux; then selinux_enabled=true else selinux_enabled=false fi OPTIND=1 label="${0##*/}=true" labels="--label $label " env="--env TERM --env CONTAIN=true " workdir="" delete="" name="" ports="" host="" volumes="" image="$DEFAULT_IMAGE" tag="$DEFAULT_TAG" show_help() { cat << EOF Usage: ${0##*/} [-xwo] [-n NAME] [-p PORT]... [-v LIST]... [-i IMAGE] [-t TAG] [CMD]... ${0##*/} -l ${0##*/} -a NAME ${0##*/} -e NAME [CMD]... ${0##*/} -h Starts an environment container. If CMD is specified, starts CMD instead of a shell. -x delete Docker container after exit. will loose data -w forward \$PWD into $CONTAIN_HOME/src, and start there -o use host networking instead -n NAME give container a NAME -p PORT forward host PORT to container PORT -v LIST mount volume LIST. specify as LOCAL:MOUNT -i IMAGE use IMAGE as container image instead -t TAG use TAG as container tag instead -l list running containers and exit -u pull image and exit -a NAME attach to a running container -e NAME execute zsh or CMD on a running container -h display this help and exit EOF } list_running() { $runtime ps --all \ --filter label="$label" \ --format "table {{.Names}}\t{{.RunningFor}}\t{{.Ports}}\t{{.Command}}" } pull_image() { $runtime pull \ $image:$tag } while getopts "xwon:p:v:i:t:lua:e:h" opt; do case "$opt" in x) delete="--rm " ;; w) workdir="--workdir $CONTAIN_HOME/src/ " if $selinux_enabled; then volume_opts=":z" fi mount="$(realpath "$PWD")" volumes="$volumes--volume \"$mount:$CONTAIN_HOME/src/$volume_opts\" " ;; o) host="--net host " ;; n) name="--name $OPTARG --hostname $OPTARG " ;; p) ports="$ports--publish $OPTARG:$OPTARG " ;; v) if $selinux_enabled; then volume_opts=":z" fi volumes="$volumes--volume $OPTARG$volume_opts " ;; i) image="$OPTARG" ;; t) tag="$OPTARG" ;; l) list_running exit 0 ;; u) pull_image exit 0 ;; a) mode_attach="true" name="$OPTARG" ;; e) mode_execute="true" name="$OPTARG" ;; h) show_help exit 0 ;; :) echo "Option -$OPTARG requires an argument." >&2 show_help exit 1 ;; \?) echo "Invalid option: -$OPTARG" >&2 show_help exit 1 ;; esac done shift $((OPTIND-1)) [ "$1" = "--" ] && shift if [ "$ports" = "\n\t" ]; then ports="" fi if [ "$mode_attach" = "true" ]; then $runtime attach \ $name exit 0 fi if [ "$mode_execute" = "true" ]; then cmd="$@" if [ -z "$@" ]; then cmd="/bin/zsh" fi $runtime exec --interactive --tty \ $name \ $cmd exit 0 fi cmd="\ $runtime run --interactive --tty \ $runtime_extra_opts\ $labels$env\ $delete\ $name\ $ports$host\ $workdir$volumes\ $image:$tag\ $@ \ " echo Running: $cmd exec sh -c "exec $cmd"