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