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