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