You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
531 lines
14 KiB
531 lines
14 KiB
#!/bin/bash
|
|
|
|
## Bash wrap script to launch the ``compose`` docker with right options.
|
|
##
|
|
##
|
|
|
|
## Launcher
|
|
## - should need minimum requirement to run
|
|
## - no shell libs
|
|
##
|
|
|
|
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && SOURCED=true
|
|
|
|
ANSI_ESC=$'\e['
|
|
|
|
ansi_color() {
|
|
local choice="$1"
|
|
|
|
if [ "$choice" == "tty" ]; then
|
|
if [ -t 1 ]; then
|
|
choice="yes"
|
|
else
|
|
choice="no"
|
|
fi
|
|
fi
|
|
|
|
if [ "$choice" != "no" ]; then
|
|
|
|
SET_COL_CHAR="${ANSI_ESC}${COL_CHAR}G"
|
|
SET_COL_STATUS="${ANSI_ESC}${COL_STATUS}G"
|
|
SET_COL_INFO="${ANSI_ESC}${COL_INFO}G"
|
|
SET_COL_ELT="${ANSI_ESC}${COL_ELT}G"
|
|
|
|
SET_BEGINCOL="${ANSI_ESC}0G"
|
|
|
|
UP="${ANSI_ESC}1A"
|
|
DOWN="${ANSI_ESC}1B"
|
|
LEFT="${ANSI_ESC}1D"
|
|
RIGHT="${ANSI_ESC}1C"
|
|
|
|
SAVE="${ANSI_ESC}7"
|
|
RESTORE="${ANSI_ESC}8"
|
|
|
|
NORMAL="${ANSI_ESC}0m"
|
|
|
|
GRAY="${ANSI_ESC}1;30m"
|
|
RED="${ANSI_ESC}1;31m"
|
|
GREEN="${ANSI_ESC}1;32m"
|
|
YELLOW="${ANSI_ESC}1;33m"
|
|
BLUE="${ANSI_ESC}1;34m"
|
|
PINK="${ANSI_ESC}1;35m"
|
|
CYAN="${ANSI_ESC}1;36m"
|
|
WHITE="${ANSI_ESC}1;37m"
|
|
|
|
DARKGRAY="${ANSI_ESC}0;30m"
|
|
DARKRED="${ANSI_ESC}0;31m"
|
|
DARKGREEN="${ANSI_ESC}0;32m"
|
|
DARKYELLOW="${ANSI_ESC}0;33m"
|
|
DARKBLUE="${ANSI_ESC}0;34m"
|
|
DARKPINK="${ANSI_ESC}0;35m"
|
|
DARKCYAN="${ANSI_ESC}0;36m"
|
|
DARKWHITE="${ANSI_ESC}0;37m"
|
|
|
|
SUCCESS=$GREEN
|
|
WARNING=$YELLOW
|
|
FAILURE=$RED
|
|
NOOP=$BLUE
|
|
ON=$SUCCESS
|
|
OFF=$FAILURE
|
|
ERROR=$FAILURE
|
|
|
|
else
|
|
|
|
SET_COL_CHAR=
|
|
SET_COL_STATUS=
|
|
SET_COL_INFO=
|
|
SET_COL_ELT=
|
|
|
|
SET_BEGINCOL=
|
|
|
|
NORMAL=
|
|
RED=
|
|
GREEN=
|
|
YELLOW=
|
|
BLUE=
|
|
GRAY=
|
|
WHITE=
|
|
|
|
DARKGRAY=
|
|
DARKRED=
|
|
DARKGREEN=
|
|
DARKYELLOW=
|
|
DARKBLUE=
|
|
DARKPINK=
|
|
DARKCYAN=
|
|
|
|
SUCCESS=
|
|
WARNING=
|
|
FAILURE=
|
|
NOOP=
|
|
ON=
|
|
OFF=
|
|
ERROR=
|
|
|
|
fi
|
|
|
|
ansi_color="$choice"
|
|
|
|
export SET_COL_CHAR SET_COL_STATUS SET_COL_INFO SET_COL_ELT \
|
|
SET_BEGINCOL UP DOWN LEFT RIGHT SAVE RESTORE NORMAL \
|
|
GRAY RED GREEN YELLOW BLUE PINK CYAN WHITE DARKGRAY \
|
|
DARKRED DARKGREEN DARKYELLOW DARKBLUE DARKPINK DARKCYAN \
|
|
SUCCESS WARNING FAILURE NOOP ON OFF ERROR ansi_color
|
|
}
|
|
|
|
|
|
e() { printf "%s" "$*"; }
|
|
|
|
warn() { e "${YELLOW}Warning:$NORMAL" "$*"$'\n' >&2 ; }
|
|
info() { e "${BLUE}II$NORMAL" "$*"$'\n' >&2 ; }
|
|
verb() { [ -z "$VERBOSE" ] || e "$*"$'\n' >&2; }
|
|
debug() { [ -z "$DEBUG" ] || e "$*"$'\n' >&2; }
|
|
err() { e "${RED}Error:$NORMAL $*"$'\n' >&2 ; }
|
|
die() { err "$@" ; exit 1; }
|
|
|
|
ansi_color "${ansi_color:-tty}"
|
|
|
|
|
|
get_path() { (
|
|
IFS=:
|
|
for d in $PATH; do
|
|
filename="$d/$1"
|
|
[ -f "$filename" -a -x "$filename" ] && {
|
|
echo "$d/$1"
|
|
return 0
|
|
}
|
|
done
|
|
return 1
|
|
) }
|
|
|
|
|
|
depends() {
|
|
## Avoid colliding with variables that are created with depends.
|
|
local __i __path __new_name
|
|
|
|
for __i in "$@"; do
|
|
if ! __path=$(get_path "$__i"); then
|
|
__new_name="$(echo "${__i//-/_}")"
|
|
if [ "$__new_name" != "$__i" ]; then
|
|
depends "$__new_name"
|
|
else
|
|
err "dependency check: couldn't find '$__i' required command."
|
|
exit 1
|
|
fi
|
|
else
|
|
if ! test -z "$__path" ; then
|
|
export "$(echo "${__i//- /__}")"="$__path"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
|
|
get_os() {
|
|
local uname_output
|
|
uname_output="$(uname -s)"
|
|
case "${uname_output}" in
|
|
Linux*) e linux;;
|
|
Darwin*) e mac;;
|
|
CYGWIN*) e cygwin;;
|
|
MINGW*) e mingw;;
|
|
*) e "UNKNOWN:${uname_output}";;
|
|
esac
|
|
}
|
|
|
|
|
|
read-0() {
|
|
local eof= IFS=''
|
|
while [ "$1" ]; do
|
|
read -r -d '' -- "$1" || eof=1
|
|
shift
|
|
done
|
|
[ -z "$eof" ]
|
|
}
|
|
|
|
|
|
read-0a() {
|
|
local eof= IFS=''
|
|
while [ "$1" ]; do
|
|
IFS='' read -r -d $'\n' -- "$1" || eof=1
|
|
shift
|
|
done
|
|
[ -z "$eof" ]
|
|
}
|
|
|
|
|
|
p0() {
|
|
printf "%s\0" "$@"
|
|
}
|
|
|
|
|
|
list_compose_vars() {
|
|
while read-0a def; do
|
|
def="${def##* }"
|
|
def="${def%=*}"
|
|
p0 "$def"
|
|
done < <(declare -p | grep "^declare -x COMPOSE_[A-Z_]\+=\"")
|
|
}
|
|
|
|
|
|
get_running_compose_containers() {
|
|
## XXXvlab: docker bug: there will be a final newline anyway
|
|
docker ps --filter label="compose.service" --format='{{.ID}}'
|
|
}
|
|
|
|
|
|
get_volumes_for_container() {
|
|
local container="$1"
|
|
|
|
docker inspect \
|
|
--format '{{range $mount := .Mounts}}{{$mount.Source}}{{"\x00"}}{{$mount.Destination}}{{"\x00"}}{{end}}' \
|
|
"$container"
|
|
}
|
|
|
|
|
|
is_volume_used() {
|
|
local volume="$1"
|
|
|
|
while read container_id; do
|
|
while read-0 src dst; do
|
|
[ "$src" == "$volume" ] && return 0
|
|
done < <(get_volumes_for_container "$container_id")
|
|
done < <(get_running_compose_containers)
|
|
return 1
|
|
}
|
|
|
|
|
|
clean_unused_sessions() {
|
|
for f in "$SESSION_DIR/"*; do
|
|
[ -e "$f" ] || continue
|
|
is_volume_used "$f" && continue
|
|
rm -f "$f"
|
|
done
|
|
}
|
|
|
|
|
|
relink_subdirs() {
|
|
local dir
|
|
for dir in "$@"; do
|
|
[ -L "$dir" ] || continue
|
|
target=$(realpath "$dir")
|
|
[ -d "$target" ] || continue
|
|
docker_run_opts+=("-v" "$target:$dir")
|
|
[ -e "$dir/metadata.yml" ] && continue
|
|
relink_subdirs "$dir"/*
|
|
done
|
|
}
|
|
|
|
|
|
mk_docker_run_options() {
|
|
|
|
## Order matters, files get to override vars
|
|
compose_config_files=(
|
|
## DEFAULT LINUX VARS
|
|
/etc/default/charm
|
|
/etc/default/datastore
|
|
/etc/default/compose
|
|
|
|
## COMPOSE SYSTEM-WIDE FILES
|
|
/etc/compose.conf
|
|
/etc/compose.local.conf
|
|
/etc/compose/local.conf
|
|
|
|
## COMPOSE USER FILES
|
|
~/.compose/etc/local.conf
|
|
~/.compose.conf
|
|
)
|
|
|
|
|
|
docker_run_opts=("-v" "/var/run/docker.sock:/var/run/docker.sock")
|
|
|
|
## current dir
|
|
if parent=$(while true; do
|
|
[ -e "./compose.yml" ] && {
|
|
echo "$PWD"
|
|
exit 0
|
|
}
|
|
[ "$PWD" == "/" ] && exit 1
|
|
cd ..
|
|
done
|
|
); then
|
|
docker_path=$COMPOSE_VAR/root/$(basename "$parent")
|
|
docker_run_opts+=("-v" "$parent:$docker_path:ro" \
|
|
"-w" "$docker_path")
|
|
fi
|
|
|
|
|
|
##
|
|
## Load config files
|
|
##
|
|
|
|
if [ -z "$DISABLE_SYSTEM_CONFIG_FILE" ]; then
|
|
## XXXvlab: should provide YML config opportunities in possible parent dirs ?
|
|
## userdir ? and global /etc/compose.yml ?
|
|
for cfgfile in "${compose_config_files[@]}"; do
|
|
[ -e "$cfgfile" ] || continue
|
|
docker_run_opts+=("-v" "$cfgfile:$cfgfile:ro")
|
|
. "$cfgfile"
|
|
done
|
|
else
|
|
docker_run_opts+=("-e" "DISABLE_SYSTEM_CONFIG_FILE=$DISABLE_SYSTEM_CONFIG_FILE")
|
|
fi
|
|
|
|
|
|
COMPOSE_LOCAL_ROOT=${COMPOSE_LOCAL_ROOT:-"$HOME/.compose"}
|
|
|
|
case "$(get_os)" in
|
|
linux)
|
|
COMPOSE_VAR=${COMPOSE_VAR:-/var/lib/compose}
|
|
COMPOSE_CACHE=${COMPOSE_CACHE:-/var/cache/compose}
|
|
DATASTORE=${DATASTORE:-/srv/datastore/data}
|
|
CONFIGSTORE=${CONFIGSTORE:-/srv/datastore/config}
|
|
if [ "$UID" == 0 ]; then
|
|
SESSION_DIR=${SESSION_DIR:-"$COMPOSE_VAR"/sessions}
|
|
CHARM_STORE=${CHARM_STORE:-/srv/charm-store}
|
|
TZ_PATH=${TZ_PATH:-"$COMPOSE_VAR"/timezones}
|
|
else
|
|
SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
|
|
CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store}
|
|
TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
|
|
fi
|
|
;;
|
|
mac)
|
|
COMPOSE_VAR=${COMPOSE_VAR:-"$COMPOSE_LOCAL_ROOT"/lib}
|
|
COMPOSE_CACHE=${COMPOSE_CACHE:-"$COMPOSE_LOCAL_ROOT"/cache}
|
|
SESSION_DIR=${SESSION_DIR:-"$COMPOSE_LOCAL_ROOT"/sessions}
|
|
DATASTORE=${DATASTORE:-"$COMPOSE_LOCAL_ROOT"/data}
|
|
CONFIGSTORE=${CONFIGSTORE:-"$COMPOSE_LOCAL_ROOT"/config}
|
|
CHARM_STORE=${CHARM_STORE:-"$HOME"/.charm-store}
|
|
TZ_PATH=${TZ_PATH:-"$COMPOSE_LOCAL_ROOT"/timezones}
|
|
;;
|
|
*)
|
|
echo "System '$os' not supported yet." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
|
|
## get TZ value and prepare TZ_PATH
|
|
TZ=$(get_tz) || exit 1
|
|
mkdir -p "${TZ_PATH}"
|
|
TZ_PATH="${TZ_PATH}/$(e "$TZ" | sha256sum | cut -c 1-8)" || exit 1
|
|
[ -e "$TZ_PATH" ] || e "$TZ" > "$TZ_PATH"
|
|
|
|
## CACHE/DATA DIRS
|
|
docker_run_opts+=("-v" "$COMPOSE_VAR:/var/lib/compose")
|
|
docker_run_opts+=("-v" "$COMPOSE_CACHE:/var/cache/compose")
|
|
|
|
docker_run_opts+=("-v" "$TZ_PATH:/etc/timezone:ro")
|
|
|
|
##
|
|
## Checking vars
|
|
##
|
|
|
|
## CHARM_STORE
|
|
[ -e "$CHARM_STORE" ] || mkdir -p "$CHARM_STORE" || exit 1
|
|
[ -L "$CHARM_STORE" ] && {
|
|
CHARM_STORE=$(readlink -f "$CHARM_STORE") || exit 1
|
|
}
|
|
|
|
docker_run_opts+=(
|
|
"-v" "$CHARM_STORE:/srv/charm-store:ro"
|
|
"-e" "CHARM_STORE=/srv/charm-store"
|
|
"-e" "HOST_CHARM_STORE=$CHARM_STORE"
|
|
)
|
|
relink_subdirs "$CHARM_STORE"/*
|
|
|
|
## DEFAULT_COMPOSE_FILE
|
|
if [ "${DEFAULT_COMPOSE_FILE+x}" ]; then
|
|
DEFAULT_COMPOSE_FILE=$(realpath "$DEFAULT_COMPOSE_FILE")
|
|
dirname=$(dirname "$DEFAULT_COMPOSE_FILE")/
|
|
if [ -e "${DEFAULT_COMPOSE_FILE}" ]; then
|
|
docker_run_opts+=("-v" "$dirname:$dirname:ro")
|
|
fi
|
|
fi
|
|
|
|
## COMPOSE_YML_FILE
|
|
if [ "${COMPOSE_YML_FILE+x}" ]; then
|
|
if [ -e "${COMPOSE_YML_FILE}" ]; then
|
|
docker_run_opts+=(
|
|
"-v" "$COMPOSE_YML_FILE:/tmp/compose.yml:ro"
|
|
"-e" "COMPOSE_YML_FILE=/tmp/compose.yml"
|
|
"-e" "HOST_COMPOSE_YML_FILE=/tmp/compose.yml"
|
|
)
|
|
fi
|
|
fi
|
|
|
|
## DATASTORE and CONFIGSTORE
|
|
docker_run_opts+=(
|
|
|
|
"-v" "$DATASTORE:/srv/datastore/data:rw"
|
|
"-e" "DATASTORE=/srv/datastore/data"
|
|
"-e" "HOST_DATASTORE=$DATASTORE"
|
|
|
|
"-v" "$CONFIGSTORE:/srv/datastore/config:rw"
|
|
"-e" "CONFIGSTORE=/srv/datastore/config"
|
|
"-e" "HOST_CONFIGSTORE=$CONFIGSTORE"
|
|
)
|
|
|
|
docker_run_opts+=("-v" "$HOME/.docker:/root/.docker")
|
|
|
|
COMPOSE_DOCKER_IMAGE=${COMPOSE_DOCKER_IMAGE:-docker.0k.io/compose}
|
|
docker_run_opts+=("-e" "COMPOSE_DOCKER_IMAGE=$COMPOSE_DOCKER_IMAGE")
|
|
|
|
## SSH config
|
|
docker_run_opts+=(
|
|
"-v" "$HOME/.ssh:/root/.ssh:ro"
|
|
"-v" "/etc/ssh:/etc/ssh:ro"
|
|
)
|
|
|
|
COMPOSE_LAUNCHER_BIN=$(readlink -f "${BASH_SOURCE[0]}")
|
|
docker_run_opts+=("-v" "$COMPOSE_LAUNCHER_BIN:/usr/local/bin/compose")
|
|
|
|
while read-0 var; do
|
|
case "$var" in
|
|
COMPOSE_YML_FILE|COMPOSE_LAUNCHER_BIN|COMPOSE_DOCKER_IMAGE|\
|
|
COMPOSE_LAUNCHER_OPTS|COMPOSE_VAR|COMPOSE_CACHE)
|
|
:
|
|
;;
|
|
*)
|
|
docker_run_opts+=("-e" "$var=${!var}")
|
|
;;
|
|
esac
|
|
done < <(list_compose_vars)
|
|
|
|
filename=$(mktemp -p /tmp/ -t launch_opts-XXXXXXXXXXXXXXXX)
|
|
{
|
|
p0 "${docker_run_opts[@]}"
|
|
} > "$filename"
|
|
sha=$(sha256sum "$filename")
|
|
sha=${sha:0:64}
|
|
src="$SESSION_DIR/$UID-$sha"
|
|
dest="/var/lib/compose/sessions/$UID-$sha"
|
|
{
|
|
p0 "-v" "$SESSION_DIR/$UID-$sha:$dest:ro"
|
|
p0 "-e" "COMPOSE_LAUNCHER_OPTS=$dest"
|
|
p0 "-e" "COMPOSE_LAUNCHER_BIN=$COMPOSE_LAUNCHER_BIN"
|
|
} >> "$filename"
|
|
mkdir -p "$SESSION_DIR" || return 1
|
|
mv -f "$filename" "$SESSION_DIR/$UID-$sha" || return 1
|
|
echo "$SESSION_DIR/$UID-$sha"
|
|
}
|
|
|
|
|
|
get_tz() {
|
|
if [ -n "$TZ" ]; then
|
|
:
|
|
elif [ -n "$COMPOSE_LOCAL_ROOT" ] && ## previous compose run
|
|
[ -e "$COMPOSE_LOCAL_ROOT/etc/timezone" ]; then
|
|
read -r TZ < "$COMPOSE_LOCAL_ROOT/etc/timezone"
|
|
elif [ -e "/etc/timezone" ]; then ## debian host system timezone
|
|
read -r TZ < /etc/timezone
|
|
elif [ -e "/etc/localtime" ]; then ## redhat and macosx sys timezone
|
|
local fullpath dirname
|
|
fullpath="$(readlink -f /etc/localtime)"
|
|
dirname="${fullpath%/*}"
|
|
TZ=${TZ:-${fullpath##*/}/${dirname##*/}}
|
|
else
|
|
err "Timezone not found nor inferable !"
|
|
echo " compose relies on '/etc/timezone' or '/etc/localtime' to be present " >&2
|
|
echo " so as to ensure same timezone for all containers that need it." >&2
|
|
echo >&2
|
|
echo " You can set a default value for compose by create issuing:" >&2
|
|
echo >&2
|
|
if [ -n "$COMPOSE_LOCAL_ROOT" ] && [ "$UID" != 0 ]; then
|
|
echo " mkdir -p $COMPOSE_LOCAL_ROOT/etc &&" >&2
|
|
echo " echo \"Europe/Paris\" > $COMPOSE_LOCAL_ROOT/etc/timezone" >&2
|
|
else
|
|
echo " echo \"Europe/Paris\" > /etc/timezone" >&2
|
|
fi
|
|
echo >&2
|
|
echo " Of course, you can change 'Europe/Paris' value by any other valid timezone." >&2
|
|
echo " timezone." >&2
|
|
echo >&2
|
|
echo " Notice you can also use \$TZ environment variable, but the value" >&2
|
|
echo " will be necessary each time you launch compose." >&2
|
|
echo >&2
|
|
return 1
|
|
fi
|
|
e "$TZ"
|
|
}
|
|
|
|
|
|
run() {
|
|
local os docker_run_opts
|
|
|
|
docker_run_opts=()
|
|
if [ -z "$COMPOSE_LAUNCHER_OPTS" ]; then
|
|
clean_unused_sessions
|
|
COMPOSE_LAUNCHER_OPTS="$(mk_docker_run_options)"
|
|
fi
|
|
|
|
while read-0 opt; do
|
|
docker_run_opts+=("$opt")
|
|
## catch COMPOSE_DOCKER_IMAGE
|
|
if [[ "$env" == "true" && "$opt" == "COMPOSE_DOCKER_IMAGE="* ]]; then
|
|
COMPOSE_DOCKER_IMAGE=${opt##COMPOSE_DOCKER_IMAGE=}
|
|
elif [ "$opt" == "-e" ]; then
|
|
env=true
|
|
else
|
|
env=
|
|
fi
|
|
done < <(cat "$COMPOSE_LAUNCHER_OPTS")
|
|
|
|
[ -t 0 ] && docker_run_opts+=("-i")
|
|
[ -t 1 ] && docker_run_opts+=("-t")
|
|
exec docker run --rm "${docker_run_opts[@]}" "${COMPOSE_DOCKER_IMAGE}" "$@"
|
|
}
|
|
|
|
|
|
[ "$SOURCED" ] && return 0
|
|
|
|
|
|
##
|
|
## Code
|
|
##
|
|
|
|
|
|
depends readlink
|
|
|
|
run "$@"
|