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
41 # Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
42 re
='[A-Z/ !@#$%^&*()+~`=]'
43 if [[ $config =~
$re ]];
46 echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
53 docker_min_version
='1.6.0'
54 docker_rec_version
='1.6.0'
55 git_min_version
='1.8.0'
56 git_rec_version
='1.8.0'
58 config_file
=containers
/"$config".yml
59 cidbootstrap
=cids
/"$config"_bootstrap.cid
60 local_discourse
=local_discourse
61 image
=discourse
/discourse
:1.0.17
62 docker_path
=`which docker.io || which docker`
65 if [ "${SUPERVISED}" = "true" ]; then
66 restart_policy
="--restart=no"
68 attach_on_run
="-a stdout -a stderr"
73 if [ -n "$DOCKER_HOST" ]; then
74 docker_ip
=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
75 elif [ -x "$(which ip 2>/dev/null)" ]; then
76 docker_ip
=`ip addr show docker0 | \
78 awk '{ split($2,a,"/"); print a[1] }';`
80 docker_ip
=`ifconfig | \
81 grep -B1 "inet addr" | \
82 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
84 awk -F: '{ print $3 }';`
90 IFS
=.
read -a ver_a
<<< "$1"
91 IFS
=.
read -a ver_b
<<< "$2"
93 while [[ -n $ver_a ]]; do
94 if (( ver_a
> ver_b
)); then
96 elif (( ver_b
> ver_a
)); then
100 ver_a
=("${ver_a[@]}")
102 ver_b
=("${ver_b[@]}")
105 return 1 # They are equal
110 echo "Docker is not installed, you will need to install Docker in order to run Launcher"
111 echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system"
113 echo "If you are running a recent Ubuntu Server, try the following:"
114 echo "sudo apt-get install docker-engine"
120 if [ -z $docker_path ]; then
124 # 1. docker daemon running?
125 # we send stderr to /dev/null cause we don't care about warnings,
126 # it usually complains about swap which does not matter
127 test=`$docker_path info 2> /dev/null`
128 if [[ $?
-ne 0 ]] ; then
129 echo "Cannot connect to the docker daemon - verify it is running and you have access"
133 # 2. running aufs or btrfs
134 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
135 if [[ "$test" =~
[aufs|btrfs|zfs|overlay
] ]] ; then : ; else
136 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
137 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
138 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
140 echo "If you wish to continue anyway using your existing unsupported filesystem, "
141 echo "read the source code of launcher and figure out how to bypass this."
145 # 3. running recommended docker version
146 test=($
($docker_path --version)) # Get docker version string
147 test=${test[2]//,/} # Get version alone and strip comma if exists
149 # At least minimum docker version
150 if compare_version
"${docker_min_version}" "${test}"; then
151 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
155 # Recommend newer docker version
156 if compare_version
"${docker_rec_version}" "${test}"; then
157 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
160 # 4. discourse docker image is downloaded
161 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
163 if [ -z "$test" ]; then
165 echo "WARNING: We are about to start downloading the Discourse base image"
166 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
168 echo "Please be patient"
172 # 5. running recommended git version
173 test=($
($git_path --version)) # Get git version string
174 test=${test[2]//,/} # Get version alone and strip comma if exists
176 # At least minimum version
177 if compare_version
"${git_min_version}" "${test}"; then
178 echo "ERROR: Git version ${test} not supported, please upgrade to at least ${git_min_version}, or recommended ${git_rec_version}"
182 # Recommend best version
183 if compare_version
"${git_rec_version}" "${test}"; then
184 echo "WARNING: Git version ${test} deprecated, recommend upgrade to ${git_rec_version} or newer."
187 # 6. able to attach stderr / out / tty
188 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
189 if [[ "$test" =~
"working" ]] ; then : ; else
190 echo "Your Docker installation is not working correctly"
192 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
199 if [ -z "$SKIP_PREREQS" ] ; then
204 read -r -d '' env_ruby
<< 'RUBY'
207 input
= STDIN.readlines.
join
208 yaml
= YAML.load
(input
)
210 if host_run
= yaml
['host_run']
211 params
= yaml
['params'] ||
{}
212 host_run.each
do |run|
214 run
= run.gsub
("$#{k}", v
)
216 STDOUT.
write "#{run}--SEP--"
221 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
223 while [ "$host_run" ] ; do
224 iter
=${host_run%%--SEP--*}
226 echo "Host run: $iter"
229 host_run
="${host_run#*--SEP--}"
235 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
236 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
240 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
241 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
244 set_template_info
() {
246 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
247 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
249 arrTemplates
=(${templates// / })
250 config_data
=$
(cat $config_file)
254 for template
in "${arrTemplates[@]}"
256 [ ! -z $template ] && {
257 input
="$input _FILE_SEPERATOR_ $(cat $template)"
261 # we always want our config file last so it takes priority
262 input
="$input _FILE_SEPERATOR_ $config_data"
264 read -r -d '' env_ruby
<< 'RUBY'
267 input
=STDIN.readlines.
join
268 # default to UTF-8 for the dbs sake
269 env
= {'LANG' => 'en_US.UTF-8'}
270 input.
split('_FILE_SEPERATOR_').each
do |yml|
273 env.merge
!(YAML.load
(yml
)['env'] ||
{})
274 rescue Psych
::SyntaxError
=> e
282 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
285 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
290 if [ "$i" == "*ERROR." ]; then
292 elif [ -n "$i" ]; then
297 if [ "$ok" -ne 1 ]; then
299 echo "YAML syntax error. Please check your containers/*.yml config files."
304 if [ -z $docker_path ]; then
308 [ "$command" == "cleanup" ] && {
310 echo "The following command will"
311 echo "- Delete all docker images for old containers"
312 echo "- Delete all stopped and orphan containers"
314 read -p "Are you sure (Y/n): " -n 1 -r && echo
315 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
317 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
318 echo "Starting Cleanup (bytes free $space)"
320 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
322 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
323 echo "Finished Cleanup (bytes free $space)"
331 if [ -z "$command" -a -z "$config" ]; then
335 if [ ! "$command" == "setup" ]; then
336 if [[ ! -e $config_file ]]; then
337 echo "Config file was not found, ensure $config_file exists"
339 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
344 docker_version
=($
($docker_path --version))
345 docker_version
=${test[2]//,/}
346 restart_policy
=${restart_policy:---restart=always}
348 set_existing_container
(){
349 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
354 set_existing_container
356 if [ ! -z $existing ]
360 $docker_path stop
-t 10 $config
363 echo "$config was not started !"
369 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
370 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
372 if [ -z "$run_image" ]; then
373 run_image
="$local_discourse/$config"
378 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
379 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
381 if [ -z "$boot_command" ]; then
383 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
384 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
386 if [ -z "$no_boot_command" ]; then
387 boot_command
="/sbin/boot"
394 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
396 if [ ! -z $existing ]
398 echo "Nothing to do, your container has already started!"
402 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
403 if [ ! -z $existing ]
405 echo "starting up existing container"
408 $docker_path start
$config
415 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
416 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
424 # get hostname and settings from container configuration
425 for envar
in "${env[@]}"
427 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
429 # use as environment variable
435 hostname
=`hostname -s`
437 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
439 hostname
=$DISCOURSE_HOSTNAME
441 hostname
=$hostname-$config
444 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
445 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
446 # docker added more hostname rules
447 hostname
=${hostname//_/-}
450 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \
451 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $docker_args $run_image $boot_command
461 # I got no frigging clue what this does, ask Sam Saffron. It RUNS STUFF ON THE HOST I GUESS?
464 # Is the image available?
465 # If not, pull it here so the user is aware what's happening.
466 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
470 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
471 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
473 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
474 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
476 if [[ ! X
"" = X
"$base_image" ]]; then
485 run_command
="cd /pups &&"
486 if [[ ! "false" = $update_pups ]]; then
487 run_command
="$run_command git pull &&"
489 run_command
="$run_command /pups/bin/pups --stdin"
493 (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 \
494 /bin
/bash
-c "$run_command") \
495 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
497 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
501 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
502 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
510 echo "Successfully bootstrapped, to startup use ./launcher start $config"
515 exec $docker_path exec -it $config /bin
/bash
--login
525 $docker_path logs
$config
541 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
542 echo "Ensuring launcher is up to date"
546 LOCAL
=$
(git rev-parse @
)
547 REMOTE
=$
(git rev-parse @
{u
})
548 BASE
=$
(git merge-base @ @
{u
})
550 if [ $LOCAL = $REMOTE ]; then
551 echo "Launcher is up-to-date"
553 elif [ $LOCAL = $BASE ]; then
554 echo "Updating Launcher"
555 git pull ||
(echo 'failed to update' && exit 1)
558 elif [ $REMOTE = $BASE ]; then
559 echo "Your version of Launcher is ahead of origin"
562 echo "Launcher has diverged source, this is only expected in Dev mode"
567 set_existing_container
569 if [ ! -z $existing ]
571 echo "Stopping old container"
574 $docker_path stop
-t 10 $config
580 if [ ! -z $existing ]
582 echo "Removing old container"
585 $docker_path rm $config
595 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)