Merge pull request #177 from krichprollsch/patch-1
[discourse_docker.git] / launcher
CommitLineData
ace450bd 1#!/bin/bash
7e738616
S
2
3command=$1
4config=$2
55d17203
RW
5opt=$3
6
87756d6d
EH
7# Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
8re='[A-Z/ !@#$%^&*()+~`=]'
9if [[ $config =~ $re ]];
10 then
11 echo
12 echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
13 echo
14 exit 1
15fi
16
7936ebaa 17cd "$(dirname "$0")"
55d17203 18
5701c085
S
19docker_min_version='1.2.0'
20docker_rec_version='1.2.0'
60668406 21
8dea575c 22config_file=containers/"$config".yml
5201a40c 23cidbootstrap=cids/"$config"_bootstrap.cid
5efded6a 24local_discourse=local_discourse
9e83982d 25image=samsaffron/discourse:1.0.12
4807b1b8 26docker_path=`which docker.io || which docker`
7e738616 27
e0fd1f5b
TB
28if [ "${SUPERVISED}" = "true" ]; then
29 restart_policy="--restart=no"
30 attach_on_start="-a"
31 attach_on_run="-a stdout -a stderr"
32else
33 attach_on_run="-d"
34fi
35
f2a3edee
AY
36if [ -n "$DOCKER_HOST" ]; then
37 docker_ip=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
38elif [ -x "$(which ip 2>/dev/null)" ]; then
813fef38 39 docker_ip=`ip addr show docker0 | \
03bb0735
LG
40 grep 'inet ' | \
41 awk '{ split($2,a,"/"); print a[1] }';`
42else
813fef38 43 docker_ip=`ifconfig | \
03bb0735
LG
44 grep -B1 "inet addr" | \
45 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
46 grep docker0 | \
47 awk -F: '{ print $3 }';`
48fi
80c11be3
SS
49
50
5f803fb4 51usage () {
55d17203 52 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
7e738616 53 echo "Commands:"
1acce9e4
SS
54 echo " start: Start/initialize a container"
55 echo " stop: Stop a running container"
56 echo " restart: Restart a container"
57 echo " destroy: Stop and remove a container"
2fc6ff36 58 echo " enter: Use nsenter to enter a container"
5f803fb4 59 echo " ssh: Start a bash shell in a running container"
1acce9e4 60 echo " logs: Docker logs for container"
408a9c19 61 echo " bootstrap: Bootstrap a container for the config based on a template"
680dd4ea 62 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
a8b66f98 63 echo " cleanup: Remove all containers that have stopped for > 24 hours"
55d17203
RW
64 echo
65 echo "Options:"
66 echo " --skip-prereqs Don't check prerequisites"
e02c1511 67 echo " --docker-args Extra arguments to pass when running docker"
7e738616
S
68 exit 1
69}
70
60668406
DP
71compare_version() {
72 declare -a ver_a
73 declare -a ver_b
74 IFS=. read -a ver_a <<< "$1"
75 IFS=. read -a ver_b <<< "$2"
76
77 while [[ -n $ver_a ]]; do
78 if (( ver_a > ver_b )); then
79 return 0
80 elif (( ver_b > ver_a )); then
81 return 1
82 else
83 unset ver_a[0]
84 ver_a=("${ver_a[@]}")
85 unset ver_b[0]
86 ver_b=("${ver_b[@]}")
87 fi
88 done
89 return 1 # They are equal
90}
91
a3e18d95
S
92prereqs() {
93
e741295a 94 # 1. docker daemon running?
30835a52
S
95 # we send stderr to /dev/null cause we don't care about warnings,
96 # it usually complains about swap which does not matter
97 test=`$docker_path info 2> /dev/null`
e741295a
MB
98
99 if [[ $? -ne 0 ]] ; then
100 echo "Cannot connect to the docker daemon - verify it is running and you have access"
101 exit 1
102 fi
103
185e78c6
S
104 # 2. running aufs or btrfs
105 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
48f22d14
S
106 if [[ "$test" =~ [aufs|btrfs|zfs|overlay] ]] ; then : ; else
107 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
108 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
109 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
7d87f85f 110 echo ""
48f22d14
S
111 echo "If you wish to continue anyway using your existing unsupported filesystem"
112 echo "read the source code of launcher and figure out how to bypass this."
113 exit 1
a3e18d95
S
114 fi
115
60668406
DP
116 # 3. running recommended docker version
117 test=($($docker_path --version)) # Get docker version string
118 test=${test[2]//,/} # Get version alone and strip comma if exists
a3e18d95 119
cf00fce0
S
120 [[ "$test" =~ "0.12.0" ]] && echo "You are running a broken version of Docker, please upgrade ASAP. See: https://meta.discourse.org/t/the-installation-stopped-in-the-middle/16311/ for more details." && exit 1
121
60668406 122 # At least minimum version
cf00fce0 123 if compare_version "${docker_min_version}" "${test}"; then
60668406 124 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
a3e18d95
S
125 exit 1
126 fi
127
60668406
DP
128 # Recommend best version
129 if compare_version "${docker_rec_version}" "${test}"; then
130 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
131 fi
132
405948ee
S
133 # 4. discourse docker image is downloaded
134 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
135
136 if [ -z "$test" ]; then
137 echo
138 echo "WARNING: We are about to start downloading the Discourse base image"
139 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
140 echo
141 echo "Please be patient"
142 echo
143
144 fi
405948ee
S
145
146 # 5. able to attach stderr / out / tty
e02c1511 147 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
a3e18d95
S
148 if [[ "$test" =~ "working" ]] ; then : ; else
149 echo "Your Docker installation is not working correctly"
150 echo
151 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
152 exit 1
153 fi
154}
155
55d17203
RW
156if [ "$opt" != "--skip-prereqs" ] ; then
157 prereqs
158fi
a3e18d95 159
e02c1511
S
160if [ "$opt" == "--docker-args" ] ; then
161 user_args=$4
162else
163 user_args=""
164fi
165
88126eba 166get_ssh_pub_key() {
c76db1ea
MK
167 local ${ssh_key_locations}
168 ssh_key_locations=(
841334d9
MK
169 ~/.ssh/id_ed25519.pub
170 ~/.ssh/id_ecdsa.pub
c76db1ea
MK
171 ~/.ssh/id_rsa.pub
172 ~/.ssh/id_dsa.pub
841334d9 173 ~core/.ssh/authorized_keys
c76db1ea
MK
174 )
175
176 local $keyfile
177 for keyfile in "${ssh_key_locations[@]}"; do
178 if [[ -e ${keyfile} ]] ; then
179 ssh_pub_key="$(cat ${keyfile})"
841334d9 180 return 0
c76db1ea
MK
181 fi
182 done
183
e3709631 184 return 0
88126eba
S
185}
186
187
52388b87
SS
188install_docker() {
189
190 echo "Docker is not installed, make sure you are running on the 3.8 kernel"
191 echo "The best supported Docker release is Ubuntu 12.04.03 for it run the following"
192 echo
193 echo "sudo apt-get update"
194 echo "sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring"
195 echo "sudo reboot"
196 echo
197
b749fe0c
PT
198 echo "sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D"
199 echo "sudo sh -c \"echo deb https://apt.dockerproject.org/repo ubuntu-precise main > /etc/apt/sources.list.d/docker.list\""
52388b87 200 echo "sudo apt-get update"
b749fe0c 201 echo "sudo apt-get install docker-engine"
52388b87
SS
202
203 exit 1
204}
205
60f9f04c
S
206host_run() {
207 read -r -d '' env_ruby << 'RUBY'
208 require 'yaml'
209
210 input = STDIN.readlines.join
211 yaml = YAML.load(input)
212
213 if host_run = yaml['host_run']
214 params = yaml['params'] || {}
215 host_run.each do |run|
216 params.each do |k,v|
217 run = run.gsub("$#{k}", v)
218 end
219 STDOUT.write "#{run}--SEP--"
220 end
221 end
222RUBY
223
e02c1511 224 host_run=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
60f9f04c
S
225
226 while [ "$host_run" ] ; do
227 iter=${host_run%%--SEP--*}
228 echo
229 echo "Host run: $iter"
230 $iter || exit 1
231 echo
232 host_run="${host_run#*--SEP--}"
233 done
234}
235
236
d90671f3 237set_volumes() {
e02c1511 238 volumes=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
d90671f3
SS
239 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
240}
241
41daa523 242set_links() {
e02c1511 243 links=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
41daa523 244 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
245}
246
7f77c274
SS
247set_template_info() {
248
e02c1511 249 templates=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
7f77c274
SS
250 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
251
252
253 arrTemplates=(${templates// / })
254 config_data=$(cat $config_file)
255
256 input="hack: true"
257
258
259 for template in "${arrTemplates[@]}"
260 do
261 [ ! -z $template ] && {
262 input="$input _FILE_SEPERATOR_ $(cat $template)"
263 }
264 done
265
266 # we always want our config file last so it takes priority
267 input="$input _FILE_SEPERATOR_ $config_data"
268
269 read -r -d '' env_ruby << 'RUBY'
270 require 'yaml'
271
272 input=STDIN.readlines.join
3cb3d9c4
S
273 # default to UTF-8 for the dbs sake
274 env = {'LANG' => 'en_US.UTF-8'}
7f77c274
SS
275 input.split('_FILE_SEPERATOR_').each do |yml|
276 yml.strip!
277 begin
278 env.merge!(YAML.load(yml)['env'] || {})
f3824347 279 rescue Psych::SyntaxError => e
280 puts e
281 puts "*ERROR."
7f77c274
SS
282 rescue => e
283 puts yml
284 p e
285 end
286 end
4b563ee8 287 puts env.map{|k,v| "-e\n#{k}=#{v}" }.join("\n")
7f77c274
SS
288RUBY
289
e02c1511 290 raw=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
4b563ee8
SS
291
292 env=()
f3824347 293 ok=1
4b563ee8 294 while read i; do
f3824347 295 if [ "$i" == "*ERROR." ]; then
296 ok=0
297 elif [ -n "$i" ]; then
16f2d250
S
298 env[${#env[@]}]=$i
299 fi
4b563ee8
SS
300 done <<< "$raw"
301
f3824347 302 if [ "$ok" -ne 1 ]; then
303 echo "${env[@]}"
d6d1fb6e 304 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
f3824347 305 exit 1
306 fi
7f77c274
SS
307}
308
52388b87
SS
309[ -z $docker_path ] && {
310 install_docker
311}
312
de869404 313[ "$command" == "cleanup" ] && {
c692f743
L
314 echo
315 echo "The following command will"
316 echo "- Delete all docker images for old containers"
317 echo "- Delete all stopped and orphan containers"
318 echo
319 read -p "Are you sure (Y/n): " -n 1 -r && echo
320 if [[ $REPLY =~ ^[Yy]$ || ! $REPLY ]]
321 then
30835a52 322 space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
29a35d1b 323 echo "Starting Cleanup (bytes free $space)"
30835a52 324
29a35d1b 325 STATE_DIR=./.gc-state scripts/docker-gc
30835a52 326
29a35d1b
S
327 space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
328 echo "Finished Cleanup (bytes free $space)"
30835a52 329
c692f743 330 else
30835a52 331 exit 1
c692f743 332 fi
a8b66f98
L
333 exit 0
334}
5f803fb4 335
c1005add 336[ $# -lt 2 ] && {
5f803fb4
SS
337 usage
338}
339
7e738616
S
340if [ ! -e $config_file ]
341 then
342 echo "Config file was not found, ensure $config_file exists"
71680b16
S
343 echo ""
344 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
7e738616
S
345 exit 1
346fi
347
337a89aa 348
e2ed1fb6
S
349docker_version=($($docker_path --version))
350docker_version=${test[2]//,/}
351
352if compare_version "1.2.0" "$docker_version"; then
353 echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
354 restart_policy=""
355else
e0fd1f5b 356 restart_policy=${restart_policy:---restart=always}
e2ed1fb6
S
357fi
358
30835a52 359set_existing_container(){
295e8f19 360 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
337a89aa
S
361}
362
30835a52 363run_stop(){
337a89aa 364
30835a52 365 set_existing_container
60f9f04c 366
30835a52 367 if [ ! -z $existing ]
337a89aa 368 then
30835a52
S
369 (
370 set -x
371 $docker_path stop -t 10 $config
372 )
337a89aa 373 else
30835a52
S
374 echo "$config was not started !"
375 exit 1
376 fi
377}
337a89aa 378
30835a52 379run_start(){
337a89aa 380
295e8f19
S
381 existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
382 echo $existing
30835a52
S
383 if [ ! -z $existing ]
384 then
385 echo "Nothing to do, your container has already started!"
386 exit 1
387 fi
388
295e8f19 389 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
30835a52
S
390 if [ ! -z $existing ]
391 then
392 echo "starting up existing container"
393 (
394 set -x
395 $docker_path start $config
396 )
397 exit 0
398 fi
399
400 host_run
401 ports=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
402 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"`
403
404 set_template_info
405 set_volumes
406 set_links
407
408 (
409 hostname=`hostname`
410 set -x
411 $docker_path run $user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname-$config" \
412 -e DOCKER_HOST_IP=$docker_ip --name $config -t $ports $volumes $local_discourse/$config /sbin/boot
413
414 )
415 exit 0
337a89aa
S
416
417}
418
680dd4ea 419run_bootstrap(){
60f9f04c
S
420
421 host_run
422
680dd4ea 423 get_ssh_pub_key
88126eba 424
680dd4ea
S
425 # Is the image available?
426 # If not, pull it here so the user is aware what's happening.
4807b1b8 427 $docker_path history $image >/dev/null 2>&1 || $docker_path pull $image
88126eba 428
680dd4ea 429 set_template_info
93149421 430
e02c1511 431 base_image=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
680dd4ea 432 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
93149421 433
e02c1511 434 update_pups=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
680dd4ea 435 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
b9c7b50e 436
680dd4ea
S
437 if [[ ! X"" = X"$base_image" ]]; then
438 image=$base_image
439 fi
b9c7b50e 440
680dd4ea 441 set_volumes
41daa523 442 set_links
b9c7b50e 443
680dd4ea 444 rm -f $cidbootstrap
d90671f3 445
680dd4ea
S
446 run_command="cd /pups &&"
447 if [[ ! "false" = $update_pups ]]; then
448 run_command="$run_command git pull &&"
449 fi
450 run_command="$run_command /pups/bin/pups --stdin"
2162f1d4 451
680dd4ea 452 echo $run_command
b9c7b50e 453
680dd4ea 454 env=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
c4498636 455
e02c1511 456 (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 \
680dd4ea 457 /bin/bash -c "$run_command") \
4807b1b8 458 || ($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
88126eba 459
4b733d87 460 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
9fb5f2d3 461
680dd4ea 462 sleep 5
2162f1d4 463
4807b1b8
MB
464 $docker_path commit `cat $cidbootstrap` $local_discourse/$config || echo 'FAILED TO COMMIT'
465 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
680dd4ea 466}
9fb5f2d3 467
680dd4ea
S
468case "$command" in
469 bootstrap)
680dd4ea 470 run_bootstrap
2dd2e330 471 echo "Successfully bootstrapped, to startup use ./launcher start $config"
4b3aebe1 472 exit 0
5f803fb4 473 ;;
1acce9e4 474
2fc6ff36 475 enter)
30835a52 476 exec $docker_path exec -it $config /bin/bash
2fc6ff36
S
477 ;;
478
5f803fb4 479 ssh)
295e8f19 480 existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
30835a52
S
481
482 if [[ ! -z $existing ]]; then
483 address="`$docker_path port $config 22`"
484 split=(${address//:/ })
485 exec ssh -o StrictHostKeyChecking=no root@${split[0]} -p ${split[1]}
486 else
487 echo "$config is not running!"
488 exit 1
489 fi
5f803fb4 490 ;;
7e738616 491
5f803fb4 492 stop)
337a89aa
S
493 run_stop
494 exit 0
5f803fb4 495 ;;
7e738616 496
5f803fb4 497 logs)
7e738616 498
30835a52
S
499 $docker_path logs $config
500 exit 0
5f803fb4 501 ;;
7e738616 502
337a89aa
S
503 restart)
504 run_stop
505 run_start
506 exit 0
507 ;;
80c11be3 508
337a89aa
S
509 start)
510 run_start
511 exit 0
5f803fb4 512 ;;
7e738616 513
680dd4ea 514 rebuild)
4b6456ef 515 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
098533cb
S
516 echo "Ensuring discourse docker is up to date"
517
518 git remote update
519
520 LOCAL=$(git rev-parse @)
521 REMOTE=$(git rev-parse @{u})
522 BASE=$(git merge-base @ @{u})
523
524 if [ $LOCAL = $REMOTE ]; then
525 echo "Discourse Docker is up-to-date"
526
527 elif [ $LOCAL = $BASE ]; then
528 echo "Updating Discourse Docker"
529 git pull || (echo 'failed to update' && exit 1)
530 exec /bin/bash $0 $@
531
532 elif [ $REMOTE = $BASE ]; then
533 echo "Your version of Discourse Docker is ahead of origin"
534
535 else
905ec302 536 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
098533cb
S
537 fi
538
4b6456ef 539 fi
30835a52
S
540
541 set_existing_container
542
543 if [ ! -z $existing ]
680dd4ea
S
544 then
545 echo "Stopping old container"
30835a52
S
546 (
547 set -x
548 $docker_path stop -t 10 $config
549 )
680dd4ea
S
550 fi
551
552 run_bootstrap
553
30835a52 554 if [ ! -z $existing ]
680dd4ea 555 then
30835a52
S
556 echo "Removing old container"
557 (
558 set -x
559 $docker_path rm $config
560 )
680dd4ea
S
561 fi
562
563 run_start
564 exit 0
565 ;;
566
7e738616 567
5f803fb4 568 destroy)
30835a52
S
569 (set -x; $docker_path stop -t 10 $config && $docker_path rm $config) || (echo "$config was not found" && exit 0)
570 exit 0
5f803fb4
SS
571 ;;
572esac
7e738616 573
5f803fb4 574usage