#!/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="--volume /etc/localtime:/etc/localtime " image="$DEFAULT_IMAGE" tag="$DEFAULT_TAG" caps="" show_help() { cat << EOF Usage: ${0##*/} [-xwod] [-n NAME] [-p PORT]... [-v LIST]... [-i IMAGE] [-t TAG] [-c CAP]... [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 -d desktop, forward wayland and xwayland -n NAME give container a NAME -p PORT forward host PORT to container PORT -v LIST mount volume LIST. specify as LOCAL:MOUNT -c CAP add capability CAP (example: SYS_PTRACE) -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 "xwodn:p:v:c: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 " ;; d) desktop="" if $selinux_enabled; then desktop="$desktop--security-opt label=disable " fi desktop="$desktop--env DISPLAY " desktop="$desktop--env WAYLAND_DISPLAY " # overwrites desktop="$desktop--env QT_QPA_PLATFORM=wayland " # xdg desktop="$desktop--env XDG_CURRENT_DESKTOP " desktop="$desktop--env XDG_SESSION_TYPE " desktop="$desktop--env XDG_SESSION_DESKTOP " desktop="$desktop--env XDG_RUNTIME_DIR=/run/container " # video and audio desktop="$desktop--volume \"$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY:/run/container/$WAYLAND_DISPLAY\" " desktop="$desktop--volume \"$XDG_RUNTIME_DIR/pipewire-0:/run/container/pipewire-0\" " desktop="$desktop--volume \"$XDG_RUNTIME_DIR/pulse:/run/container/pulse\" " desktop="$desktop--volume \"/tmp/.X11-unix:/tmp/.X11-unix\" " desktop="$desktop--device /dev/dri " desktop="$desktop--device /dev/snd " ;; 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 " ;; c) caps="$caps--cap-add $OPTARG " ;; 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\ $caps\ $desktop\ $image:$tag\ $@ \ " echo Running: $cmd exec sh -c "exec $cmd"