687557b81c7aedc64ebbd7869a66f8fceeca4d82
4 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs] [--docker-args STRING]"
6 echo " start: Start/initialize a container"
7 echo " stop: Stop a running container"
8 echo " restart: Restart a container"
9 echo " destroy: Stop and remove a container"
10 echo " enter: Open a shell to run commands inside the container"
11 echo " logs: View the Docker logs for a container"
12 echo " bootstrap: Bootstrap a container for the config based on a template"
13 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
14 echo " cleanup: Remove all containers that have stopped for > 24 hours"
17 echo " --skip-prereqs Don't check launcher prerequisites"
18 echo " --docker-args Extra arguments to pass when running docker"
19 echo " --skip-mac-address Don't assign a mac address"
27 while [ ${#} -gt 0 ]; do
47 # Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
48 re
='[A-Z/ !@#$%^&*()+~`=]'
49 if [[ $config =~
$re ]];
52 echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
59 docker_min_version
='1.8.0'
60 docker_rec_version
='1.8.0'
61 git_min_version
='1.8.0'
62 git_rec_version
='1.8.0'
64 config_file
=containers
/"$config".yml
65 cidbootstrap
=cids
/"$config"_bootstrap.cid
66 local_discourse
=local_discourse
67 image
=discourse
/base
:2.0.20170728
68 docker_path
=`which docker.io || which docker`
71 if [ "${SUPERVISED}" = "true" ]; then
72 restart_policy
="--restart=no"
74 attach_on_run
="-a stdout -a stderr"
79 if [ -n "$DOCKER_HOST" ]; then
80 docker_ip
=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
81 elif [ -x "$(which ip 2>/dev/null)" ]; then
82 docker_ip
=`ip addr show docker0 | \
84 awk '{ split($2,a,"/"); print a[1] }';`
86 docker_ip
=`ifconfig | \
87 grep -B1 "inet addr" | \
88 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
90 awk -F: '{ print $3 }';`
96 IFS
=.
read -a ver_a
<<< "$1"
97 IFS
=.
read -a ver_b
<<< "$2"
99 while [[ -n $ver_a ]]; do
100 if (( ver_a
> ver_b
)); then
102 elif (( ver_b
> ver_a
)); then
106 ver_a
=("${ver_a[@]}")
108 ver_b
=("${ver_b[@]}")
111 return 1 # They are equal
116 echo "Docker is not installed, you will need to install Docker in order to run Launcher"
117 echo "See https://docs.docker.com/installation/"
123 if [ -z $docker_path ]; then
127 # 1. docker daemon running?
128 # we send stderr to /dev/null cause we don't care about warnings,
129 # it usually complains about swap which does not matter
130 test=`$docker_path info 2> /dev/null`
131 if [[ $?
-ne 0 ]] ; then
132 echo "Cannot connect to the docker daemon - verify it is running and you have access"
136 # 2. running an approved storage driver?
137 if ! $docker_path info
2> /dev
/null |
egrep -q '^Storage Driver: (aufs|btrfs|zfs|overlay|overlay2)$'; then
138 echo "Your Docker installation is not using a supported storage driver. If we were to proceed you may have a broken install."
139 echo "aufs is the recommended storage driver, although zfs/btrfs/overlay and overlay2 may work as well."
140 echo "Other storage drivers are known to be problematic."
141 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the 'Storage Driver' line."
143 echo "If you wish to continue anyway using your existing unsupported storage driver,"
144 echo "read the source code of launcher and figure out how to bypass this check."
148 # 3. running recommended docker version
149 test=($
($docker_path --version)) # Get docker version string
150 test=${test[2]//,/} # Get version alone and strip comma if exists
152 # At least minimum docker version
153 if compare_version
"${docker_min_version}" "${test}"; then
154 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
158 # Recommend newer docker version
159 if compare_version
"${docker_rec_version}" "${test}"; then
160 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
163 # 4. discourse docker image is downloaded
164 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
166 if [ -z "$test" ]; then
168 echo "WARNING: We are about to start downloading the Discourse base image"
169 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
171 echo "Please be patient"
175 # 5. running recommended git version
176 test=($
($git_path --version)) # Get git version string
177 test=${test[2]//,/} # Get version alone and strip comma if exists
179 # At least minimum version
180 if compare_version
"${git_min_version}" "${test}"; then
181 echo "ERROR: Git version ${test} not supported, please upgrade to at least ${git_min_version}, or recommended ${git_rec_version}"
185 # Recommend best version
186 if compare_version
"${git_rec_version}" "${test}"; then
187 echo "WARNING: Git version ${test} deprecated, recommend upgrade to ${git_rec_version} or newer."
190 # 6. able to attach stderr / out / tty
191 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
192 if [[ "$test" =~
"working" ]] ; then : ; else
193 echo "Your Docker installation is not working correctly"
195 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
199 # 7. enough space for the bootstrap on docker folder
200 folder
=`$docker_path info --format '{{.DockerRootDir}}'`
201 safe_folder
=${folder:-/var/lib/docker}
202 test=$
(($
(stat
-f --format="%a*%S" $safe_folder)/1024**3 < 5))
203 if [[ $test -ne 0 ]] ; then
204 echo "You have less than 5GB of free space on the disk where $safe_folder is located. You will need more space to continue"
207 read -p "Would you like to attempt to recover space by cleaning docker images and containers in the system?(y/N)" -n 1 -r
209 if [[ $REPLY =~ ^
[Yy
]$
]]
212 echo "If the cleanup was successful, you may try again now"
219 if [ -z "$SKIP_PREREQS" ] && [ "$command" != "cleanup" ]; then
224 read -r -d '' env_ruby
<< 'RUBY'
227 input
= STDIN.readlines.
join
228 yaml
= YAML.load
(input
)
230 if host_run
= yaml
['host_run']
231 params
= yaml
['params'] ||
{}
232 host_run.each
do |run|
234 run
= run.gsub
("$#{k}", v
)
236 STDOUT.
write "#{run}--SEP--"
241 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
243 while [ "$host_run" ] ; do
244 iter
=${host_run%%--SEP--*}
246 echo "Host run: $iter"
249 host_run
="${host_run#*--SEP--}"
256 if selinuxenabled
; then
259 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
260 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << '${volopts} '}.join"`
264 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
265 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
269 local templates
=`cat $1 | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
270 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
272 local arrTemplates
=${templates// / }
274 if [ ! -z "$templates" ]; then
275 for template
in "${arrTemplates[@]}"
277 local nested_templates
=$
(find_templates
$template)
279 if [ ! -z "$nested_templates" ]; then
280 templates
="$templates $nested_templates"
290 set_template_info
() {
291 templates
=$
(find_templates
$config_file)
293 arrTemplates
=(${templates// / })
294 config_data
=$
(cat $config_file)
298 for template
in "${arrTemplates[@]}"
300 [ ! -z $template ] && {
301 input
="$input _FILE_SEPERATOR_ $(cat $template)"
305 # we always want our config file last so it takes priority
306 input
="$input _FILE_SEPERATOR_ $config_data"
308 read -r -d '' env_ruby
<< 'RUBY'
311 input
=STDIN.readlines.
join
312 # default to UTF-8 for the dbs sake
313 env
= {'LANG' => 'en_US.UTF-8'}
314 input.
split('_FILE_SEPERATOR_').each
do |yml|
317 env.merge
!(YAML.load
(yml
)['env'] ||
{})
318 rescue Psych
::SyntaxError
=> e
326 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
329 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
334 if [ "$i" == "*ERROR." ]; then
336 elif [ -n "$i" ]; then
341 if [ "$ok" -ne 1 ]; then
343 echo "YAML syntax error. Please check your containers/*.yml config files."
347 read -r -d '' labels_ruby
<< 'RUBY'
350 input
=STDIN.readlines.
join
351 # default to UTF-8 for the dbs sake
353 input.
split('_FILE_SEPERATOR_').each
do |yml|
356 labels.merge
!(YAML.load
(yml
)['labels'] ||
{})
357 rescue Psych
::SyntaxError
=> e
365 puts labels.map
{|k
,v|
"-l\n#{k}=#{v}" }.
join("\n")
368 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$labels_ruby"`
373 if [ "$i" == "*ERROR." ]; then
375 elif [ -n "$i" ]; then
376 labels
[${#labels[@]}]=$i
380 if [ "$ok" -ne 1 ]; then
382 echo "YAML syntax error. Please check your containers/*.yml config files."
387 if [ -z $docker_path ]; then
391 [ "$command" == "cleanup" ] && {
393 echo "The following command will"
394 echo "- Delete all docker images for old containers"
395 echo "- Delete all stopped and orphan containers"
397 read -p "Are you sure (Y/n): " -n 1 -r && echo
398 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
400 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
401 echo "Starting Cleanup (bytes free $space)"
403 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
405 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
406 echo "Finished Cleanup (bytes free $space)"
414 if [ -z "$command" -a -z "$config" ]; then
418 if [ ! "$command" == "setup" ]; then
419 if [[ ! -e $config_file ]]; then
420 echo "Config file was not found, ensure $config_file exists"
422 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
427 docker_version
=($
($docker_path --version))
428 docker_version
=${test[2]//,/}
429 restart_policy
=${restart_policy:---restart=always}
431 set_existing_container
(){
432 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
437 set_existing_container
439 if [ ! -z $existing ]
443 $docker_path stop
-t 10 $config
446 echo "$config was not started !"
452 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
453 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
455 if [ -z "$run_image" ]; then
456 run_image
="$local_discourse/$config"
461 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
462 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
464 if [ -z "$boot_command" ]; then
466 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
467 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
469 if [ -z "$no_boot_command" ]; then
470 boot_command
="/sbin/boot"
477 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
479 if [ ! -z $existing ]
481 echo "Nothing to do, your container has already started!"
485 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
486 if [ ! -z $existing ]
488 echo "starting up existing container"
491 $docker_path start
$config
498 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
499 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
501 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
502 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
510 # get hostname and settings from container configuration
511 for envar
in "${env[@]}"
513 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
515 # use as environment variable
521 hostname
=`hostname -s`
523 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
525 hostname
=$DISCOURSE_HOSTNAME
527 hostname
=$hostname-$config
530 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
531 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
532 # docker added more hostname rules
533 hostname
=${hostname//_/-}
536 if [ -z "$SKIP_MAC_ADDRESS" ] ; then
537 mac_address
="--mac-address $($docker_path run $user_args -i --rm -a stdout -a stderr $image /bin/sh -c "echo $hostname |
md5sum |
sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'")"
541 $docker_path run
$links $attach_on_run $restart_policy "${env[@]}" "${labels[@]}" -h "$hostname" \
542 -e DOCKER_HOST_IP
="$docker_ip" --name $config -t $ports $volumes $mac_address $docker_args $user_args \
543 $run_image $boot_command
553 # I got no frigging clue what this does, ask Sam Saffron. It RUNS STUFF ON THE HOST I GUESS?
556 # Is the image available?
557 # If not, pull it here so the user is aware what's happening.
558 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
562 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
563 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
565 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
566 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
568 if [[ ! X
"" = X
"$base_image" ]]; then
577 run_command
="cd /pups &&"
578 if [[ ! "false" = $update_pups ]]; then
579 run_command
="$run_command git pull &&"
581 run_command
="$run_command /pups/bin/pups --stdin"
586 (exec echo "$input" |
$docker_path run
$user_args $links "${env[@]}" -e DOCKER_HOST_IP
="$docker_ip" --cidfile $cidbootstrap -i -a stdin
-a stdout
-a stderr
$volumes $image \
587 /bin
/bash
-c "$run_command") || ERR
=$?
590 # magic exit code that indicates a retry
591 if [[ "$ERR" == 77 ]]; then
592 $docker_path rm `cat $cidbootstrap`
595 elif [[ "$ERR" > 0 ]]; then
599 if [[ $FAILED = "TRUE" ]]; then
600 if [[ ! -z "$DEBUG" ]]; then
601 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config-debug ||
echo 'FAILED TO COMMIT'
602 echo "** DEBUG ** Maintaining image for diagnostics $local_discourse/$config-debug"
605 $docker_path rm `cat $cidbootstrap`
607 echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one"
613 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
614 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
622 echo "Successfully bootstrapped, to startup use ./launcher start $config"
627 exec $docker_path exec -it $config /bin
/bash
--login
637 $docker_path logs
$config
653 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
654 echo "Ensuring launcher is up to date"
658 LOCAL
=$
(git rev-parse @
)
659 REMOTE
=$
(git rev-parse @
{u
})
660 BASE
=$
(git merge-base @ @
{u
})
662 if [ $LOCAL = $REMOTE ]; then
663 echo "Launcher is up-to-date"
665 elif [ $LOCAL = $BASE ]; then
666 echo "Updating Launcher"
667 git pull ||
(echo 'failed to update' && exit 1)
669 for (( i
=${#BASH_ARGV[@]}-1,j
=0; i
>=0,j
<${#BASH_ARGV[@]}; i--
,j
++ ))
671 args
[$j]=${BASH_ARGV[$i]}
673 exec /bin
/bash
$0 "${args[@]}" # $@ is empty, because of shift at the beginning. Use BASH_ARGV instead.
675 elif [ $REMOTE = $BASE ]; then
676 echo "Your version of Launcher is ahead of origin"
679 echo "Launcher has diverged source, this is only expected in Dev mode"
684 set_existing_container
686 if [ ! -z $existing ]
688 echo "Stopping old container"
691 $docker_path stop
-t 10 $config
697 if [ ! -z $existing ]
699 echo "Removing old container"
702 $docker_path rm $config
712 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)