Skip to content
Snippets Groups Projects
openfoam-docker 11 KiB
Newer Older
openfoamVersion="latest"
imageBasename="opencfd/openfoam"
imageFlavour="-run"
scriptVersion="2022-01-10"

#------------------------------------------------------------------------------
# =========                 |
# \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
#  \\    /   O peration     |
#   \\  /    A nd           | www.openfoam.com
#    \\/     M anipulation  |
#------------------------------------------------------------------------------
#    Copyright (C) 2016-2022 OpenCFD Ltd.
#------------------------------------------------------------------------------
# SPDX-License-Identifier: (GPL-3.0-or-later)
#     Run script for openfoam container images
#     (https://hub.docker.com/u/opencfd)
#
#     Copy/link to automatically specify OpenFOAM version and image 'flavour'
#     For example,
#
Mark OLESEN's avatar
Mark OLESEN committed
#        ln -s openfoam-docker openfoam2112-run
#
#------------------------------------------------------------------------------
printHelp()
{
    defaultImage="${imageBasename}${imageFlavour}:${openfoamVersion}"

    cat<<HELP_HEAD

Usage: ${0##*/} [OPTION] [-- application ...]
       ${0##*/} [OPTION] [command_string]

options:
  -c                Shell commands read from the first non-option argument
  -data=DIR         Specify mount dir for container '/data'
  -dir=DIR          Specify mount dir for container '~/' (default: pwd)
Mark OLESEN's avatar
Mark OLESEN committed
  -<digits>         Specify OpenFOAM version (eg, -2112)
  -base | -dev | -run | -tut | -default
                    Image flavour (default: -run).
                    Not all image flavours are necessarily available.
  -X | -x           X11 forwarding: enable (-X) or disable (-x)
  -update           Update (pull) the image, do not run
  -dry-run          Report the start command, without running
  -verbose          Additional verbosity when starting (FOAM_VERBOSE)
HELP_HEAD

if [ -n "$1" ]
then
cat<<'HELP_FULL'
  -entry=PATH       Alternative entry point
  -image=NAME | -i=NAME
                    Specify image to run
  -docker           Use docker (default)
  -podman           Use podman instead of docker
  -sudo             Prefix container calls with 'sudo'
  -xhost            Allow container access to host network (implies -X)
  --shm-size=BYTES  Size of /dev/shm (eg, --shm-size=4G)
HELP_FULL
fi

cat<<TAIL_OPTIONS
  --                The end of option processing.
                    An argument of - or / is equivalent to --.
  -h | -help        Display short help and exit
  -help-full        Display full help and exit

Run OpenFOAM container image (${defaultImage})
- Simulations are stored on the host system (not the container)
- Mounts the current or specified directory (except the HOME directory).

Script Version: ${scriptVersion:-[]}

Note:
  The user name within the container is 'openfoam',
  but matches the user/group id from the host.
  Requires docker or podman.

TAIL_OPTIONS

if [ -n "$1" ]
then
    cat<<HELP_FULL
Equivalent options:
  | -dir=DIR  | -case=DIR | -case DIR | -home=DIR

Example:
    ${0##*/} -dir=/path/to/simulation
or  cd /path/to/simulation && ${0##*/}

Note:
  The '-xhost' option can be useful in combination with 'ssh -X',
  but should be used sparingly since it permits the container full
  access to network communication (potentially insecure).

HELP_FULL
fi

cat<<'FOOTER'
Note:
    Different OpenFOAM components and modules may be present (or missing)
    on any particular container image.
    For example, source code, tutorials, in-situ visualization,
    paraview plugins, external linear-solver interfaces etc.

For more information: www.openfoam.com

FOOTER

    exit 0  # A clean exit
}


# Report error and exit
die()
{
    exec 1>&2
    echo
    echo "Error encountered:"
    while [ "$#" -ge 1 ]; do echo "    $1"; shift; done
    echo
    echo "See '${0##*/} -help' for usage"
    echo
    exit 1
}


#------------------------------------------------------------------------------
# Constants - user name/locations MUST correspond to the image assets

toolChain=docker
container_home='/home/openfoam'         # Home for container user
container_tmphome='/tmp/.home.openfoam' # Fake home for container user


#------------------------------------------------------------------------------

# Select 'podman' toolchain if mentioned in script name:
case "${0##*/}" in (*-podman*) toolChain=podman;; esac

# Get openfoam version and/or image flavour from script name
# "/path/openfoam{VERSION}"
# "/path/openfoam{VERSION}-{FLAVOUR}"

imageTag="$(echo "${0##*/}" | sed -ne 's/openfoam\([1-9][0-9]*\).*/\1/ip')"

# Version:
if [ -n "$imageTag" ]
then
    openfoamVersion="$imageTag"
fi

# Flavour:
imageTag="-${0##*-}"
case "$imageTag" in
(-base)
    unset imageFlavour
    ;;
(-run | -default)
    imageFlavour="$imageTag"
    ;;
(-dev*)
    imageFlavour="-dev"
    ;;
(-tut*)
    imageFlavour="-tutorials"
    ;;
esac


#------------------------------------------------------------------------------
# Parse options

unset image sudo
unset mount1Dir mount2Dir
unset optDryrun optEntrypoint optShellCommand optUpdate optVerbose optShmSize
unset optX11Forwarding

while [ "$#" -gt 0 ]
do
    case "$1" in
    ('') ;;
    (- | -- | /)
        shift
        break   # Stop option parsing
        ;;

Mark OLESEN's avatar
Mark OLESEN committed
    # OpenFOAM versions (eg, -2112, -2106, -2012, etc)
    (-[1-9]*)   openfoamVersion="${1#*-}" ;;
    (-v[1-9]*)  openfoamVersion="${1#*-v}" ;;

    # Image flavours
    (-base)     unset imageFlavour ;;
    (-run)      imageFlavour="-run" ;;
    (-def*)     imageFlavour="-default" ;;
    (-dev*)     imageFlavour="-dev" ;;
    (-tut*)     imageFlavour="-tutorials" ;;

    (-c)  # Shell command
        optShellCommand="-c"
        ;;

    (-help-f*)   # Full help
    (-h | -help* | --help*)  # Short help
    (-docker | -podman)
        toolChain="${1#*-}"
        ;;
    (-sudo)     # Use sudo
        sudo="sudo"
        ;;
    (--shm-size=*)
        optShmSize="${1#*=}"
        ;;

    (-case)
        # OpenFOAM-style '-case' for specfying the HOME mount
        [ "$#" -ge 2 ] || die "'$1' option requires an argument"
        shift
        mount1Dir="$1"
        ;;

    # Various ways to specify the HOME mount
    (-case=* | -dir=* | -home=*)
    (-entry=*)            # Alternative entrypoint
    (-i=* | -image=*)     # Alternative image name
    (-update | -upgrade)  # Also allow 'upgrade' (for Ubunutu people)
        optUpdate=true
        ;;
    (-dry-run | -dryrun)
        optDryrun=true
        ;;
    (-verbose)
        optVerbose=true
        ;;

    (-X)        # Enable X11 forwarding
        : "${optX11Forwarding:=X}"
        ;;
    (-x)        # Disable X11 forwarding
        unset optX11Forwarding
        ;;
    (-xhost)    # Any host X11 forwarding
        optX11Forwarding="host"
        ;;

        if [ -n "$optShellCommand" ]
        then
            break
        else
            die "Unexpected argument '$1'"
        fi
        ;;
    esac
    shift
done

if [ -z "$image" ]
then
    image="${imageBasename}${imageFlavour}:${openfoamVersion}"
fi

if [ -n "$optUpdate" ]
then
    if [ -n "$optDryrun" ]
    then
        runPrefix="echo"
        echo "(dry-run)" 1>&2
        echo 1>&2
    else
        runPrefix="$sudo"
        echo "Update image: $image" 1>&2
    fi
    $runPrefix ${toolChain:?} pull "$image"
    exitCode="$?"
    echo 1>&2
    echo "Done" 1>&2
    exit "$exitCode"
fi

if [ -n "$optShellCommand" ] && [ "$#" -eq 0 ]
then
    die "-c: option requires an argument"
fi


#------------------------------------------------------------------------------

# Sanity and setup

guest_uid="$(id -u 2>/dev/null)"
guest_gid="$(id -g 2>/dev/null)"
[ -n "$guest_uid" ] || die "Cannot determine current user id"
[ -n "$guest_gid" ] || die "Cannot determine current group id"

# Mount directory (mandatory)
if [ -z "$mount1Dir" ]
then
    mount1Dir="$(pwd -P)"  # Default is current directory
elif [ -d "$mount1Dir" ]
then
    mount1Dir="$(cd "$mount1Dir" && pwd -P)"
else
    die "No such mount directory: $mount1Dir"
fi
if [ "$mount1Dir" = "$(cd "$HOME" && pwd -P)" ]
then
    die "Cannot use home directory as the mount-point" \
        "Run from a subdirectory instead"
fi

# Data directory (optional)
if [ -n "$mount2Dir" ]
then
    if [ -d "$mount2Dir" ]
    then
        mount2Dir="$(cd "$mount2Dir" && pwd -P)"
    else
        echo "${0##*/}: ignore invalid -data directory: $mount2Dir" 1>&2
        unset mount2Dir
    fi
fi


# Older (non-nss) user/group handling
# userMapping()
# {
#     echo '--volume=/etc/group:/etc/group:ro'
#     echo '--volume=/etc/passwd:/etc/passwd:ro'
#     echo '--volume=/etc/shadow:/etc/shadow:ro'
#     echo '--volume=/etc/sudoers.d:/etc/sudoers.d:ro'
# }


# Environment and settings for X11 forwarding
#
# See Also
# https://stackoverflow.com/questions/48235040/run-x-application-in-a-docker-container-reliably-on-a-server-connected-via-ssh-w
# https://blog.yadutaf.fr/2017/09/10/running-a-graphical-app-in-a-docker-container-on-a-remote-server/

# Prepare X11 mapping
# - use tmp Xauthority file in local (mounted) directory
unset display_host tmpXauth_host tmpXauth_guest

if [ -n "$DISPLAY" ] \
&& [ -n "$optX11Forwarding" ] \
&& [ -d "$mount1Dir" ] \
&& command -v xauth >/dev/null
then
    # DISPLAY="host:0" vs DISPLAY=":0"
    display_host="${DISPLAY%%:*}"

    tmpXauth_host="$(mktemp --tmpdir="$mount1Dir" .Xauthority.container.XXXX)"
    trap "rm -f \"$tmpXauth_host\"; exit 0" EXIT TERM INT  # Remove on exit

    # X-authority file to allow any hostname. Generally reasonably safe
    xauth nlist "$DISPLAY" | sed -e 's/^..../ffff/' | \
    xauth -f "$tmpXauth_host" nmerge -

    # The mounted name
    tmpXauth_guest="${container_home}/${tmpXauth_host##*/}"
fi


# Define container run options for X11 mapping
x11Mapping()
{
    if [ -n "$tmpXauth_guest" ]
    then
        echo "--env=DISPLAY=$DISPLAY"
        echo "--env=XAUTHORITY=$tmpXauth_guest"

        # Accessing via ssh -X ('localhost:0' etc) or forced with -xhost
        if [ -n "$display_host" ] || [ "$optX11Forwarding" = host ]
        then
            echo "--net=host"
        elif [ -d /tmp/.X11-unix ]  # No display host, bind sockets
        then
            echo "--volume=/tmp/.X11-unix:/tmp/.X11-unix"
        fi
    fi
}


if [ "$optVerbose" = true ]
then
    set -x
fi

if [ -n "$optDryrun" ]
then
    runPrefix="echo"
    echo "(dry-run)" 1>&2
    echo 1>&2
else
    runPrefix="$sudo"
fi

if [ "$optVerbose" = true ]
then
    set -x
fi

exec $runPrefix ${toolChain:?} run \
    --rm -t -i \
    --user="$guest_uid:$guest_gid" \
    ${mount1Dir:+--volume="$mount1Dir:$container_home"} \
    ${mount2Dir:+--volume="$mount2Dir:/data"} \
    $(x11Mapping) \
    ${optShmSize:+--shm-size="$optShmSize"} \
    ${optEntrypoint:+--entrypoint="$optEntrypoint"} \
    "$image" $optShellCommand "$@"

# ---------------------------------------------------------------------------