8a843e6399d7414473db7e2964a075205c152b1b
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
/discourse
:1.3.10
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"
202 if [ -z "$SKIP_PREREQS" ] ; then
207 read -r -d '' env_ruby
<< 'RUBY'
210 input
= STDIN.readlines.
join
211 yaml
= YAML.load
(input
)
213 if host_run
= yaml
['host_run']
214 params
= yaml
['params'] ||
{}
215 host_run.each
do |run|
217 run
= run.gsub
("$#{k}", v
)
219 STDOUT.
write "#{run}--SEP--"
224 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
226 while [ "$host_run" ] ; do
227 iter
=${host_run%%--SEP--*}
229 echo "Host run: $iter"
232 host_run
="${host_run#*--SEP--}"
238 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
239 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
243 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
244 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
248 local templates
=`cat $1 | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
249 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
251 local arrTemplates
=${templates// / }
253 if [ ! -z "$templates" ]; then
254 for template
in "${arrTemplates[@]}"
256 local nested_templates
=$
(find_templates
$template)
258 if [ ! -z "$nested_templates" ]; then
259 templates
="$templates $nested_templates"
269 set_template_info
() {
270 templates
=$
(find_templates
$config_file)
272 arrTemplates
=(${templates// / })
273 config_data
=$
(cat $config_file)
277 for template
in "${arrTemplates[@]}"
279 [ ! -z $template ] && {
280 input
="$input _FILE_SEPERATOR_ $(cat $template)"
284 # we always want our config file last so it takes priority
285 input
="$input _FILE_SEPERATOR_ $config_data"
287 read -r -d '' env_ruby
<< 'RUBY'
290 input
=STDIN.readlines.
join
291 # default to UTF-8 for the dbs sake
292 env
= {'LANG' => 'en_US.UTF-8'}
293 input.
split('_FILE_SEPERATOR_').each
do |yml|
296 env.merge
!(YAML.load
(yml
)['env'] ||
{})
297 rescue Psych
::SyntaxError
=> e
305 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
308 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
313 if [ "$i" == "*ERROR." ]; then
315 elif [ -n "$i" ]; then
320 if [ "$ok" -ne 1 ]; then
322 echo "YAML syntax error. Please check your containers/*.yml config files."
326 read -r -d '' labels_ruby
<< 'RUBY'
329 input
=STDIN.readlines.
join
330 # default to UTF-8 for the dbs sake
332 input.
split('_FILE_SEPERATOR_').each
do |yml|
335 labels.merge
!(YAML.load
(yml
)['labels'] ||
{})
336 rescue Psych
::SyntaxError
=> e
344 puts labels.map
{|k
,v|
"-l\n#{k}=#{v}" }.
join("\n")
347 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$labels_ruby"`
352 if [ "$i" == "*ERROR." ]; then
354 elif [ -n "$i" ]; then
355 labels
[${#labels[@]}]=$i
359 if [ "$ok" -ne 1 ]; then
361 echo "YAML syntax error. Please check your containers/*.yml config files."
366 if [ -z $docker_path ]; then
370 [ "$command" == "cleanup" ] && {
372 echo "The following command will"
373 echo "- Delete all docker images for old containers"
374 echo "- Delete all stopped and orphan containers"
376 read -p "Are you sure (Y/n): " -n 1 -r && echo
377 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
379 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
380 echo "Starting Cleanup (bytes free $space)"
382 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
384 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
385 echo "Finished Cleanup (bytes free $space)"
393 if [ -z "$command" -a -z "$config" ]; then
397 if [ ! "$command" == "setup" ]; then
398 if [[ ! -e $config_file ]]; then
399 echo "Config file was not found, ensure $config_file exists"
401 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
406 docker_version
=($
($docker_path --version))
407 docker_version
=${test[2]//,/}
408 restart_policy
=${restart_policy:---restart=always}
410 set_existing_container
(){
411 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
416 set_existing_container
418 if [ ! -z $existing ]
422 $docker_path stop
-t 10 $config
425 echo "$config was not started !"
431 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
432 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
434 if [ -z "$run_image" ]; then
435 run_image
="$local_discourse/$config"
440 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
441 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
443 if [ -z "$boot_command" ]; then
445 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
446 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
448 if [ -z "$no_boot_command" ]; then
449 boot_command
="/sbin/boot"
456 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
458 if [ ! -z $existing ]
460 echo "Nothing to do, your container has already started!"
464 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
465 if [ ! -z $existing ]
467 echo "starting up existing container"
470 $docker_path start
$config
477 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
478 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
480 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
481 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
489 # get hostname and settings from container configuration
490 for envar
in "${env[@]}"
492 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
494 # use as environment variable
500 hostname
=`hostname -s`
502 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
504 hostname
=$DISCOURSE_HOSTNAME
506 hostname
=$hostname-$config
509 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
510 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
511 # docker added more hostname rules
512 hostname
=${hostname//_/-}
515 if [ -z "$SKIP_MAC_ADDRESS" ] ; then
516 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/'")"
520 $docker_path run
$links $attach_on_run $restart_policy "${env[@]}" "${labels[@]}" -h "$hostname" \
521 -e DOCKER_HOST_IP
="$docker_ip" --name $config -t $ports $volumes $mac_address $docker_args $user_args \
522 $run_image $boot_command
532 # I got no frigging clue what this does, ask Sam Saffron. It RUNS STUFF ON THE HOST I GUESS?
535 # Is the image available?
536 # If not, pull it here so the user is aware what's happening.
537 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
541 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
542 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
544 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
545 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
547 if [[ ! X
"" = X
"$base_image" ]]; then
556 envs
=$
(echo "${env[@]}" |
awk '{gsub("-e ", "");print}')
557 run_command
="cd /pups &&"
558 if [[ ! "false" = $update_pups ]]; then
559 run_command
="$run_command git pull &&"
561 run_command
="$run_command $envs /pups/bin/pups --stdin"
566 (exec echo "$input" |
$docker_path run
$user_args $links -e DOCKER_HOST_IP
="$docker_ip" --cidfile $cidbootstrap -i -a stdin
-a stdout
-a stderr
$volumes $image \
567 /bin
/bash
-c "$run_command") || ERR
=$?
570 # magic exit code that indicates a retry
571 if [[ "$ERR" == 77 ]]; then
572 $docker_path rm `cat $cidbootstrap`
575 elif [[ "$ERR" > 0 ]]; then
579 if [[ $FAILED = "TRUE" ]]; then
580 if [[ ! -z "$DEBUG" ]]; then
581 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config-debug ||
echo 'FAILED TO COMMIT'
582 echo "** DEBUG ** Maintaining image for diagnostics $local_discourse/$config-debug"
585 $docker_path rm `cat $cidbootstrap`
587 echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one"
593 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
594 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
602 echo "Successfully bootstrapped, to startup use ./launcher start $config"
607 exec $docker_path exec -it $config /bin
/bash
--login
617 $docker_path logs
$config
633 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
634 echo "Ensuring launcher is up to date"
638 LOCAL
=$
(git rev-parse @
)
639 REMOTE
=$
(git rev-parse @
{u
})
640 BASE
=$
(git merge-base @ @
{u
})
642 if [ $LOCAL = $REMOTE ]; then
643 echo "Launcher is up-to-date"
645 elif [ $LOCAL = $BASE ]; then
646 echo "Updating Launcher"
647 git pull ||
(echo 'failed to update' && exit 1)
649 for (( i
=${#BASH_ARGV[@]}-1,j
=0; i
>=0,j
<${#BASH_ARGV[@]}; i--
,j
++ ))
651 args
[$j]=${BASH_ARGV[$i]}
653 exec /bin
/bash
$0 "${args[@]}" # $@ is empty, because of shift at the beginning. Use BASH_ARGV instead.
655 elif [ $REMOTE = $BASE ]; then
656 echo "Your version of Launcher is ahead of origin"
659 echo "Launcher has diverged source, this is only expected in Dev mode"
664 set_existing_container
666 if [ ! -z $existing ]
668 echo "Stopping old container"
671 $docker_path stop
-t 10 $config
677 if [ ! -z $existing ]
679 echo "Removing old container"
682 $docker_path rm $config
692 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)