06588fd84e6f0e1093ed0ebfc1f8e00022b60af9
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 " run: Run the given command with the config in the context of the last bootstrapped image"
14 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
15 echo " cleanup: Remove all containers that have stopped for > 24 hours"
18 echo " --skip-prereqs Don't check launcher prerequisites"
19 echo " --docker-args Extra arguments to pass when running docker"
20 echo " --skip-mac-address Don't assign a mac address"
21 echo " --run-image Override the image used for running the container"
30 if [[ $command == "run" ]]; then
34 while [ ${#} -gt 0 ]; do
58 # Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
59 re
='[[:upper:]/ !@#$%^&*()+~`=]'
60 if [[ $config =~
$re ]];
63 echo "ERROR: Config name '$config' must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
70 docker_min_version
='17.03.1'
71 docker_rec_version
='17.06.2'
72 git_min_version
='1.8.0'
73 git_rec_version
='1.8.0'
75 config_file
=containers
/"$config".yml
76 cidbootstrap
=cids
/"$config"_bootstrap.cid
77 local_discourse
=local_discourse
78 image
=discourse
/base
:2.0.20181031
79 docker_path
=`which docker.io || which docker`
82 if [ "${SUPERVISED}" = "true" ]; then
83 restart_policy
="--restart=no"
85 attach_on_run
="-a stdout -a stderr"
90 if [ -n "$DOCKER_HOST" ]; then
91 docker_ip
=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
92 elif [ -x "$(which ip 2>/dev/null)" ]; then
93 docker_ip
=`ip addr show docker0 | \
95 awk '{ split($2,a,"/"); print a[1] }';`
97 docker_ip
=`ifconfig | \
98 grep -B1 "inet addr" | \
99 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
101 awk -F: '{ print $3 }';`
104 # From https://stackoverflow.com/a/44660519/702738
106 if [[ $1 == $2 ]]; then
110 local i a
=(${1%%[^0-9.]*}) b
=(${2%%[^0-9.]*})
111 local arem
=${1#${1%%[^0-9.]*}} brem=${2#${2%%[^0-9.]*}}
112 for ((i
=0; i
<${#a[@]} || i
<${#b[@]}; i
++)); do
113 if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
115 elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
119 if [ "$arem" '<' "$brem" ]; then
121 elif [ "$arem" '>' "$brem" ]; then
129 echo "Docker is not installed, you will need to install Docker in order to run Launcher"
130 echo "See https://docs.docker.com/installation/"
136 if [ -z $docker_path ]; then
140 # 1. docker daemon running?
141 # we send stderr to /dev/null cause we don't care about warnings,
142 # it usually complains about swap which does not matter
143 test=`$docker_path info 2> /dev/null`
144 if [[ $?
-ne 0 ]] ; then
145 echo "Cannot connect to the docker daemon - verify it is running and you have access"
149 # 2. running an approved storage driver?
150 if ! $docker_path info
2> /dev
/null |
egrep -q '^Storage Driver: (aufs|btrfs|zfs|overlay|overlay2)$'; then
151 echo "Your Docker installation is not using a supported storage driver. If we were to proceed you may have a broken install."
152 echo "aufs is the recommended storage driver, although zfs/btrfs/overlay and overlay2 may work as well."
153 echo "Other storage drivers are known to be problematic."
154 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the 'Storage Driver' line."
156 echo "If you wish to continue anyway using your existing unsupported storage driver,"
157 echo "read the source code of launcher and figure out how to bypass this check."
161 # 3. running recommended docker version
162 test=($
($docker_path --version)) # Get docker version string
163 test=${test[2]//,/} # Get version alone and strip comma if exists
165 # At least minimum docker version
166 if compare_version
"${docker_min_version}" "${test}"; then
167 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
171 # Recommend newer docker version
172 if compare_version
"${docker_rec_version}" "${test}"; then
173 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
176 # 4. discourse docker image is downloaded
177 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
179 if [ -z "$test" ]; then
181 echo "WARNING: We are about to start downloading the Discourse base image"
182 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
184 echo "Please be patient"
188 # 5. running recommended git version
189 test=($
($git_path --version)) # Get git version string
190 test=${test[2]//,/} # Get version alone and strip comma if exists
192 # At least minimum version
193 if compare_version
"${git_min_version}" "${test}"; then
194 echo "ERROR: Git version ${test} not supported, please upgrade to at least ${git_min_version}, or recommended ${git_rec_version}"
198 # Recommend best version
199 if compare_version
"${git_rec_version}" "${test}"; then
200 echo "WARNING: Git version ${test} deprecated, recommend upgrade to ${git_rec_version} or newer."
203 # 6. able to attach stderr / out / tty
204 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
205 if [[ "$test" =~
"working" ]] ; then : ; else
206 echo "Your Docker installation is not working correctly"
208 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
212 # 7. enough space for the bootstrap on docker folder
213 folder
=`$docker_path info --format '{{.DockerRootDir}}'`
214 safe_folder
=${folder:-/var/lib/docker}
215 test=$
(($
(stat
-f --format="%a*%S" $safe_folder)/1024**3 < 5))
216 if [[ $test -ne 0 ]] ; then
217 echo "You have less than 5GB of free space on the disk where $safe_folder is located. You will need more space to continue"
220 read -p "Would you like to attempt to recover space by cleaning docker images and containers in the system?(y/N)" -n 1 -r
222 if [[ $REPLY =~ ^
[Yy
]$
]]
225 echo "If the cleanup was successful, you may try again now"
232 if [ -z "$SKIP_PREREQS" ] && [ "$command" != "cleanup" ]; then
237 read -r -d '' env_ruby
<< 'RUBY'
240 input
= STDIN.readlines.
join
241 yaml
= YAML.load
(input
)
243 if host_run
= yaml
['host_run']
244 params
= yaml
['params'] ||
{}
245 host_run.each
do |run|
247 run
= run.gsub
("$#{k}", v
)
249 STDOUT.
write "#{run}--SEP--"
254 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
256 while [ "$host_run" ] ; do
257 iter
=${host_run%%--SEP--*}
259 echo "Host run: $iter"
262 host_run
="${host_run#*--SEP--}"
268 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
269 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
273 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
274 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
278 local templates
=`cat $1 | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
279 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
281 local arrTemplates
=${templates// / }
283 if [ ! -z "$templates" ]; then
284 for template
in "${arrTemplates[@]}"
286 local nested_templates
=$
(find_templates
$template)
288 if [ ! -z "$nested_templates" ]; then
289 templates
="$templates $nested_templates"
299 set_template_info
() {
300 templates
=$
(find_templates
$config_file)
302 arrTemplates
=(${templates// / })
303 config_data
=$
(cat $config_file)
307 for template
in "${arrTemplates[@]}"
309 [ ! -z $template ] && {
310 input
="$input _FILE_SEPERATOR_ $(cat $template)"
314 # we always want our config file last so it takes priority
315 input
="$input _FILE_SEPERATOR_ $config_data"
317 read -r -d '' env_ruby
<< 'RUBY'
320 input
=STDIN.readlines.
join
321 # default to UTF-8 for the dbs sake
322 env
= {'LANG' => 'en_US.UTF-8'}
323 input.
split('_FILE_SEPERATOR_').each
do |yml|
326 env.merge
!(YAML.load
(yml
)['env'] ||
{})
327 rescue Psych
::SyntaxError
=> e
335 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
338 tmp_input_file
=$
(mktemp
)
340 echo "$input" > "$tmp_input_file"
341 raw
=`exec cat "$tmp_input_file" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
343 rm -f "$tmp_input_file"
348 if [ "$i" == "*ERROR." ]; then
350 elif [ -n "$i" ]; then
355 if [ "$ok" -ne 1 ]; then
357 echo "YAML syntax error. Please check your containers/*.yml config files."
361 read -r -d '' labels_ruby
<< 'RUBY'
364 input
=STDIN.readlines.
join
365 # default to UTF-8 for the dbs sake
367 input.
split('_FILE_SEPERATOR_').each
do |yml|
370 labels.merge
!(YAML.load
(yml
)['labels'] ||
{})
371 rescue Psych
::SyntaxError
=> e
379 puts labels.map
{|k
,v|
"-l\n#{k}=#{v}" }.
join("\n")
382 tmp_input_file
=$
(mktemp
)
384 echo "$input" > "$tmp_input_file"
385 raw
=`exec cat "$tmp_input_file" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$labels_ruby"`
387 rm -f "$tmp_input_file"
392 if [ "$i" == "*ERROR." ]; then
394 elif [ -n "$i" ]; then
395 labels
[${#labels[@]}]=$
(echo $i |
sed s
/{{config
}}/${config}/g
)
399 if [ "$ok" -ne 1 ]; then
401 echo "YAML syntax error. Please check your containers/*.yml config files."
406 if [ -z $docker_path ]; then
410 [ "$command" == "cleanup" ] && {
412 echo "The following command will"
413 echo "- Delete all docker images for old containers"
414 echo "- Delete all stopped and orphan containers"
416 read -p "Are you sure (Y/n): " -n 1 -r && echo
417 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
419 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
420 echo "Starting Cleanup (bytes free $space)"
422 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
424 rm -f shared
/standalone
/log
/var-log
/*.txt
426 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
427 echo "Finished Cleanup (bytes free $space)"
433 if [ -d /var
/discourse
/shared
/standalone
/postgres_data_old
]; then
435 echo "Old PostgreSQL backup data cluster detected taking up $(du -hs /var/discourse/shared/standalone/postgres_data_old | awk '{print $1}') detected"
436 read -p "Would you like to remove it? (Y/n): " -n 1 -r && echo
438 if [[ $REPLY =~ ^
[Yy
]$
]]; then
439 echo "removing old PostgreSQL data cluster at /var/discourse/shared/standalone/postgres_data_old..."
440 rm -rf /var
/discourse
/shared
/standalone
/postgres_data_old
449 if [ -z "$command" -a -z "$config" ]; then
453 if [ ! "$command" == "setup" ]; then
454 if [[ ! -e $config_file ]]; then
455 echo "Config file was not found, ensure $config_file exists"
457 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
462 docker_version
=($
($docker_path --version))
463 docker_version
=${test[2]//,/}
464 restart_policy
=${restart_policy:---restart=always}
466 set_existing_container
(){
467 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
472 set_existing_container
474 if [ ! -z $existing ]
478 $docker_path stop
-t 10 $config
481 echo "$config was not started !"
487 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
488 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
490 if [ -n "$user_run_image" ]; then
491 run_image
=$user_run_image
492 elif [ -z "$run_image" ]; then
493 run_image
="$local_discourse/$config"
498 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
499 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
501 if [ -z "$boot_command" ]; then
503 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
504 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
506 if [ -z "$no_boot_command" ]; then
507 boot_command
="/sbin/boot"
514 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
516 if [ ! -z $existing ]
518 echo "Nothing to do, your container has already started!"
522 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
523 if [ ! -z $existing ]
525 echo "starting up existing container"
528 $docker_path start
$config
535 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
536 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
538 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
539 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
547 # get hostname and settings from container configuration
548 for envar
in "${env[@]}"
550 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
552 # use as environment variable
558 hostname
=`hostname -s`
560 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
562 hostname
=$DISCOURSE_HOSTNAME
564 hostname
=$hostname-$config
567 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
568 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
569 # docker added more hostname rules
570 hostname
=${hostname//_/-}
573 if [ -z "$SKIP_MAC_ADDRESS" ] ; then
574 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/'")"
578 $docker_path run
--shm-size=512m
$links $attach_on_run $restart_policy "${env[@]}" "${labels[@]}" -h "$hostname" \
579 -e DOCKER_HOST_IP
="$docker_ip" --name $config -t $ports $volumes $mac_address $docker_args $user_args \
580 $run_image $boot_command
594 (exec $docker_path run
--rm --shm-size=512m
$user_args $links "${env[@]}" -e DOCKER_HOST_IP
="$docker_ip" -i -a stdin
-a stdout
-a stderr
$volumes $run_image \
595 /bin
/bash
-c "$run_command") || ERR
=$?
597 if [[ $ERR > 0 ]]; then
605 # Is the image available?
606 # If not, pull it here so the user is aware what's happening.
607 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
611 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
612 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
614 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
615 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
617 if [[ ! X
"" = X
"$base_image" ]]; then
626 run_command
="cd /pups &&"
627 if [[ ! "false" = $update_pups ]]; then
628 run_command
="$run_command git pull &&"
630 run_command
="$run_command /pups/bin/pups --stdin"
636 tmp_input_file
=$
(mktemp
)
638 echo "$input" > "$tmp_input_file"
639 (exec cat "$tmp_input_file" |
$docker_path run
--shm-size=512m
$user_args $links "${env[@]}" -e DOCKER_HOST_IP
="$docker_ip" --cidfile $cidbootstrap -i -a stdin
-a stdout
-a stderr
$volumes $image \
640 /bin
/bash
-c "$run_command") || ERR
=$?
642 rm -f "$tmp_input_file"
645 # magic exit code that indicates a retry
646 if [[ "$ERR" == 77 ]]; then
647 $docker_path rm `cat $cidbootstrap`
650 elif [[ "$ERR" > 0 ]]; then
654 if [[ $FAILED = "TRUE" ]]; then
655 if [[ ! -z "$DEBUG" ]]; then
656 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config-debug ||
echo 'FAILED TO COMMIT'
657 echo "** DEBUG ** Maintaining image for diagnostics $local_discourse/$config-debug"
660 $docker_path rm `cat $cidbootstrap`
662 echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one"
668 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
669 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
677 echo "Successfully bootstrapped, to startup use ./launcher start $config"
687 exec $docker_path exec -it $config /bin
/bash
--login
697 $docker_path logs
$config
713 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
714 echo "Ensuring launcher is up to date"
718 LOCAL
=$
(git rev-parse @
)
719 REMOTE
=$
(git rev-parse @
{u
})
720 BASE
=$
(git merge-base @ @
{u
})
722 if [ $LOCAL = $REMOTE ]; then
723 echo "Launcher is up-to-date"
725 elif [ $LOCAL = $BASE ]; then
726 echo "Updating Launcher"
727 git pull ||
(echo 'failed to update' && exit 1)
729 for (( i
=${#BASH_ARGV[@]}-1,j
=0; i
>=0,j
<${#BASH_ARGV[@]}; i--
,j
++ ))
731 args
[$j]=${BASH_ARGV[$i]}
733 exec /bin
/bash
$0 "${args[@]}" # $@ is empty, because of shift at the beginning. Use BASH_ARGV instead.
735 elif [ $REMOTE = $BASE ]; then
736 echo "Your version of Launcher is ahead of origin"
739 echo "Launcher has diverged source, this is only expected in Dev mode"
744 set_existing_container
746 if [ ! -z $existing ]
748 echo "Stopping old container"
751 $docker_path stop
-t 10 $config
757 if [ ! -z $existing ]
759 echo "Removing old container"
762 $docker_path rm $config
772 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)