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: Use nsenter to get a shell into a 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"
26 while [ ${#} -gt 0 ]; do
43 # Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
44 re
='[A-Z/ !@#$%^&*()+~`=]'
45 if [[ $config =~
$re ]];
48 echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
55 docker_min_version
='1.8.0'
56 docker_rec_version
='1.8.0'
57 git_min_version
='1.8.0'
58 git_rec_version
='1.8.0'
60 config_file
=containers
/"$config".yml
61 cidbootstrap
=cids
/"$config"_bootstrap.cid
62 local_discourse
=local_discourse
63 image
=discourse
/discourse
:1.0.17
64 docker_path
=`which docker.io || which docker`
67 if [ "${SUPERVISED}" = "true" ]; then
68 restart_policy
="--restart=no"
70 attach_on_run
="-a stdout -a stderr"
75 if [ -n "$DOCKER_HOST" ]; then
76 docker_ip
=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
77 elif [ -x "$(which ip 2>/dev/null)" ]; then
78 docker_ip
=`ip addr show docker0 | \
80 awk '{ split($2,a,"/"); print a[1] }';`
82 docker_ip
=`ifconfig | \
83 grep -B1 "inet addr" | \
84 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
86 awk -F: '{ print $3 }';`
92 IFS
=.
read -a ver_a
<<< "$1"
93 IFS
=.
read -a ver_b
<<< "$2"
95 while [[ -n $ver_a ]]; do
96 if (( ver_a
> ver_b
)); then
98 elif (( ver_b
> ver_a
)); then
102 ver_a
=("${ver_a[@]}")
104 ver_b
=("${ver_b[@]}")
107 return 1 # They are equal
112 echo "Docker is not installed, you will need to install Docker in order to run Launcher"
113 echo "See https://docs.docker.com/installation/"
119 if [ -z $docker_path ]; then
123 # 1. docker daemon running?
124 # we send stderr to /dev/null cause we don't care about warnings,
125 # it usually complains about swap which does not matter
126 test=`$docker_path info 2> /dev/null`
127 if [[ $?
-ne 0 ]] ; then
128 echo "Cannot connect to the docker daemon - verify it is running and you have access"
132 # 2. running aufs or btrfs
133 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
134 if [[ "$test" =~
[aufs|btrfs|zfs|overlay
] ]] ; then : ; else
135 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
136 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
137 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
139 echo "If you wish to continue anyway using your existing unsupported filesystem, "
140 echo "read the source code of launcher and figure out how to bypass this."
144 # 3. running recommended docker version
145 test=($
($docker_path --version)) # Get docker version string
146 test=${test[2]//,/} # Get version alone and strip comma if exists
148 # At least minimum docker version
149 if compare_version
"${docker_min_version}" "${test}"; then
150 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
154 # Recommend newer docker version
155 if compare_version
"${docker_rec_version}" "${test}"; then
156 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
159 # 4. discourse docker image is downloaded
160 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
162 if [ -z "$test" ]; then
164 echo "WARNING: We are about to start downloading the Discourse base image"
165 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
167 echo "Please be patient"
171 # 5. running recommended git version
172 test=($
($git_path --version)) # Get git version string
173 test=${test[2]//,/} # Get version alone and strip comma if exists
175 # At least minimum version
176 if compare_version
"${git_min_version}" "${test}"; then
177 echo "ERROR: Git version ${test} not supported, please upgrade to at least ${git_min_version}, or recommended ${git_rec_version}"
181 # Recommend best version
182 if compare_version
"${git_rec_version}" "${test}"; then
183 echo "WARNING: Git version ${test} deprecated, recommend upgrade to ${git_rec_version} or newer."
186 # 6. able to attach stderr / out / tty
187 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
188 if [[ "$test" =~
"working" ]] ; then : ; else
189 echo "Your Docker installation is not working correctly"
191 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
198 if [ -z "$SKIP_PREREQS" ] ; then
203 read -r -d '' env_ruby
<< 'RUBY'
206 input
= STDIN.readlines.
join
207 yaml
= YAML.load
(input
)
209 if host_run
= yaml
['host_run']
210 params
= yaml
['params'] ||
{}
211 host_run.each
do |run|
213 run
= run.gsub
("$#{k}", v
)
215 STDOUT.
write "#{run}--SEP--"
220 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
222 while [ "$host_run" ] ; do
223 iter
=${host_run%%--SEP--*}
225 echo "Host run: $iter"
228 host_run
="${host_run#*--SEP--}"
234 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
235 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
239 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
240 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
243 set_template_info
() {
245 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
246 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
248 arrTemplates
=(${templates// / })
249 config_data
=$
(cat $config_file)
253 for template
in "${arrTemplates[@]}"
255 [ ! -z $template ] && {
256 input
="$input _FILE_SEPERATOR_ $(cat $template)"
260 # we always want our config file last so it takes priority
261 input
="$input _FILE_SEPERATOR_ $config_data"
263 read -r -d '' env_ruby
<< 'RUBY'
266 input
=STDIN.readlines.
join
267 # default to UTF-8 for the dbs sake
268 env
= {'LANG' => 'en_US.UTF-8'}
269 input.
split('_FILE_SEPERATOR_').each
do |yml|
272 env.merge
!(YAML.load
(yml
)['env'] ||
{})
273 rescue Psych
::SyntaxError
=> e
281 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
284 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
289 if [ "$i" == "*ERROR." ]; then
291 elif [ -n "$i" ]; then
296 if [ "$ok" -ne 1 ]; then
298 echo "YAML syntax error. Please check your containers/*.yml config files."
303 if [ -z $docker_path ]; then
307 [ "$command" == "cleanup" ] && {
309 echo "The following command will"
310 echo "- Delete all docker images for old containers"
311 echo "- Delete all stopped and orphan containers"
313 read -p "Are you sure (Y/n): " -n 1 -r && echo
314 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
316 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
317 echo "Starting Cleanup (bytes free $space)"
319 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
321 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
322 echo "Finished Cleanup (bytes free $space)"
330 if [ -z "$command" -a -z "$config" ]; then
334 if [ ! "$command" == "setup" ]; then
335 if [[ ! -e $config_file ]]; then
336 echo "Config file was not found, ensure $config_file exists"
338 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
343 docker_version
=($
($docker_path --version))
344 docker_version
=${test[2]//,/}
345 restart_policy
=${restart_policy:---restart=always}
347 set_existing_container
(){
348 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
353 set_existing_container
355 if [ ! -z $existing ]
359 $docker_path stop
-t 10 $config
362 echo "$config was not started !"
368 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
369 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
371 if [ -z "$run_image" ]; then
372 run_image
="$local_discourse/$config"
377 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
378 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
380 if [ -z "$boot_command" ]; then
382 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
383 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
385 if [ -z "$no_boot_command" ]; then
386 boot_command
="/sbin/boot"
393 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
395 if [ ! -z $existing ]
397 echo "Nothing to do, your container has already started!"
401 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
402 if [ ! -z $existing ]
404 echo "starting up existing container"
407 $docker_path start
$config
414 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
415 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
417 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
418 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
426 # get hostname and settings from container configuration
427 for envar
in "${env[@]}"
429 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
431 # use as environment variable
437 hostname
=`hostname -s`
439 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
441 hostname
=$DISCOURSE_HOSTNAME
443 hostname
=$hostname-$config
446 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
447 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
448 # docker added more hostname rules
449 hostname
=${hostname//_/-}
451 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/'")"
454 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \
455 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $mac_address $docker_args \
456 $run_image $boot_command
466 # I got no frigging clue what this does, ask Sam Saffron. It RUNS STUFF ON THE HOST I GUESS?
469 # Is the image available?
470 # If not, pull it here so the user is aware what's happening.
471 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
475 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
476 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
478 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
479 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
481 if [[ ! X
"" = X
"$base_image" ]]; then
490 run_command
="cd /pups &&"
491 if [[ ! "false" = $update_pups ]]; then
492 run_command
="$run_command git pull &&"
494 run_command
="$run_command /pups/bin/pups --stdin"
499 (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 \
500 /bin
/bash
-c "$run_command") || FAILED
="TRUE"
502 if [[ $FAILED = "TRUE" ]]; then
503 if [[ ! -z "$DEBUG" ]]; then
504 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config-debug ||
echo 'FAILED TO COMMIT'
505 echo "** DEBUG ** Maintaining image for diagnostics $local_discourse/$config-debug"
508 $docker_path rm `cat $cidbootstrap`
510 echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one"
516 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
517 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
525 echo "Successfully bootstrapped, to startup use ./launcher start $config"
530 exec $docker_path exec -it $config /bin
/bash
--login
540 $docker_path logs
$config
556 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
557 echo "Ensuring launcher is up to date"
561 LOCAL
=$
(git rev-parse @
)
562 REMOTE
=$
(git rev-parse @
{u
})
563 BASE
=$
(git merge-base @ @
{u
})
565 if [ $LOCAL = $REMOTE ]; then
566 echo "Launcher is up-to-date"
568 elif [ $LOCAL = $BASE ]; then
569 echo "Updating Launcher"
570 git pull ||
(echo 'failed to update' && exit 1)
573 elif [ $REMOTE = $BASE ]; then
574 echo "Your version of Launcher is ahead of origin"
577 echo "Launcher has diverged source, this is only expected in Dev mode"
582 set_existing_container
584 if [ ! -z $existing ]
586 echo "Stopping old container"
589 $docker_path stop
-t 10 $config
595 if [ ! -z $existing ]
597 echo "Removing old container"
600 $docker_path rm $config
610 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)