we don't care about ssh these days
[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
dbaac6e7 15image=samsaffron/discourse:1.0.8
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
e741295a 124 # 4. able to attach stderr / out / tty
e02c1511 125 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
a3e18d95
S
126 if [[ "$test" =~ "working" ]] ; then : ; else
127 echo "Your Docker installation is not working correctly"
128 echo
129 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
130 exit 1
131 fi
132}
133
55d17203
RW
134if [ "$opt" != "--skip-prereqs" ] ; then
135 prereqs
136fi
a3e18d95 137
e02c1511
S
138if [ "$opt" == "--docker-args" ] ; then
139 user_args=$4
140else
141 user_args=""
142fi
143
88126eba 144get_ssh_pub_key() {
c76db1ea
MK
145 local ${ssh_key_locations}
146 ssh_key_locations=(
841334d9
MK
147 ~/.ssh/id_ed25519.pub
148 ~/.ssh/id_ecdsa.pub
c76db1ea
MK
149 ~/.ssh/id_rsa.pub
150 ~/.ssh/id_dsa.pub
841334d9 151 ~core/.ssh/authorized_keys
c76db1ea
MK
152 )
153
154 local $keyfile
155 for keyfile in "${ssh_key_locations[@]}"; do
156 if [[ -e ${keyfile} ]] ; then
157 ssh_pub_key="$(cat ${keyfile})"
841334d9 158 return 0
c76db1ea
MK
159 fi
160 done
161
e3709631 162 return 0
88126eba
S
163}
164
165
52388b87
SS
166install_docker() {
167
168 echo "Docker is not installed, make sure you are running on the 3.8 kernel"
169 echo "The best supported Docker release is Ubuntu 12.04.03 for it run the following"
170 echo
171 echo "sudo apt-get update"
172 echo "sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring"
173 echo "sudo reboot"
174 echo
175
176 echo "sudo sh -c \"wget -qO- https://get.docker.io/gpg | apt-key add -\""
177 echo "sudo sh -c \"echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list\""
178 echo "sudo apt-get update"
179 echo "sudo apt-get install lxc-docker"
180
181 exit 1
182}
183
60f9f04c
S
184host_run() {
185 read -r -d '' env_ruby << 'RUBY'
186 require 'yaml'
187
188 input = STDIN.readlines.join
189 yaml = YAML.load(input)
190
191 if host_run = yaml['host_run']
192 params = yaml['params'] || {}
193 host_run.each do |run|
194 params.each do |k,v|
195 run = run.gsub("$#{k}", v)
196 end
197 STDOUT.write "#{run}--SEP--"
198 end
199 end
200RUBY
201
e02c1511 202 host_run=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
60f9f04c
S
203
204 while [ "$host_run" ] ; do
205 iter=${host_run%%--SEP--*}
206 echo
207 echo "Host run: $iter"
208 $iter || exit 1
209 echo
210 host_run="${host_run#*--SEP--}"
211 done
212}
213
214
d90671f3 215set_volumes() {
e02c1511 216 volumes=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
d90671f3
SS
217 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
218}
219
41daa523 220set_links() {
e02c1511 221 links=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
41daa523 222 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
223}
224
7f77c274
SS
225set_template_info() {
226
e02c1511 227 templates=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
7f77c274
SS
228 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
229
230
231 arrTemplates=(${templates// / })
232 config_data=$(cat $config_file)
233
234 input="hack: true"
235
236
237 for template in "${arrTemplates[@]}"
238 do
239 [ ! -z $template ] && {
240 input="$input _FILE_SEPERATOR_ $(cat $template)"
241 }
242 done
243
244 # we always want our config file last so it takes priority
245 input="$input _FILE_SEPERATOR_ $config_data"
246
247 read -r -d '' env_ruby << 'RUBY'
248 require 'yaml'
249
250 input=STDIN.readlines.join
3cb3d9c4
S
251 # default to UTF-8 for the dbs sake
252 env = {'LANG' => 'en_US.UTF-8'}
7f77c274
SS
253 input.split('_FILE_SEPERATOR_').each do |yml|
254 yml.strip!
255 begin
256 env.merge!(YAML.load(yml)['env'] || {})
f3824347 257 rescue Psych::SyntaxError => e
258 puts e
259 puts "*ERROR."
7f77c274
SS
260 rescue => e
261 puts yml
262 p e
263 end
264 end
4b563ee8 265 puts env.map{|k,v| "-e\n#{k}=#{v}" }.join("\n")
7f77c274
SS
266RUBY
267
e02c1511 268 raw=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
4b563ee8
SS
269
270 env=()
f3824347 271 ok=1
4b563ee8 272 while read i; do
f3824347 273 if [ "$i" == "*ERROR." ]; then
274 ok=0
275 elif [ -n "$i" ]; then
16f2d250
S
276 env[${#env[@]}]=$i
277 fi
4b563ee8
SS
278 done <<< "$raw"
279
f3824347 280 if [ "$ok" -ne 1 ]; then
281 echo "${env[@]}"
d6d1fb6e 282 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
f3824347 283 exit 1
284 fi
7f77c274
SS
285}
286
52388b87
SS
287[ -z $docker_path ] && {
288 install_docker
289}
290
a8b66f98 291[ $command == "cleanup" ] && {
c692f743
L
292 echo
293 echo "The following command will"
294 echo "- Delete all docker images for old containers"
295 echo "- Delete all stopped and orphan containers"
296 echo
297 read -p "Are you sure (Y/n): " -n 1 -r && echo
298 if [[ $REPLY =~ ^[Yy]$ || ! $REPLY ]]
299 then
30835a52
S
300 space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
301
c692f743 302 echo "Starting Cleanup"
30835a52
S
303
304 if [[ ! -z `docker ps -aq` ]]; then
305 docker inspect -f '{{.Id}},{{.State.Running}},{{.State.FinishedAt}}' $(docker ps -qa) | \
306 awk -F, 'BEGIN { TIME=strftime("%FT%T.000000000Z",systime()-60*60*24); } $2=="false" && $3 < TIME {print $1;}' | \
307 xargs --no-run-if-empty docker rm >/dev/null 2>/dev/null
308 fi
309
310 docker rmi `docker images -a | grep '<none>' | awk '{print $3}'` 2> /dev/null
311
312 let freed=$space-$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
313
314 echo $space
315 echo $(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
316
317
318 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 }}}'
319 [ -z "$output" ] && {
320 [[ $freed > 0 ]] && { echo "./launcher cleanup cleared up $freed of disk space."; } || { echo "./launcher cleanup has finished, no files were removed."; }
321 } || { echo "./launcher cleanup cleared up $freed of disk space."; }
c692f743 322 else
30835a52 323 exit 1
c692f743 324 fi
a8b66f98
L
325 exit 0
326}
5f803fb4 327
c1005add 328[ $# -lt 2 ] && {
5f803fb4
SS
329 usage
330}
331
7e738616
S
332if [ ! -e $config_file ]
333 then
334 echo "Config file was not found, ensure $config_file exists"
71680b16
S
335 echo ""
336 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
7e738616
S
337 exit 1
338fi
339
337a89aa 340
e2ed1fb6
S
341docker_version=($($docker_path --version))
342docker_version=${test[2]//,/}
343
344if compare_version "1.2.0" "$docker_version"; then
345 echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
346 restart_policy=""
347else
e0fd1f5b 348 restart_policy=${restart_policy:---restart=always}
e2ed1fb6
S
349fi
350
351
7936ebaa
MB
352run_mailtest(){
353 if [ ! -e $config_file ]; then
354 echo "Config does not exist: $config_file" >&2
355 exit 1
356 fi
357 exec scripts/mailtest $config_file
358}
359
30835a52
S
360set_existing_container(){
361 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep "$config$" | awk '{ print $1 }'`
337a89aa
S
362}
363
30835a52 364run_stop(){
337a89aa 365
30835a52 366 set_existing_container
60f9f04c 367
30835a52 368 if [ ! -z $existing ]
337a89aa 369 then
30835a52
S
370 (
371 set -x
372 $docker_path stop -t 10 $config
373 )
337a89aa 374 else
30835a52
S
375 echo "$config was not started !"
376 exit 1
377 fi
378}
337a89aa 379
30835a52 380run_start(){
337a89aa 381
30835a52
S
382 existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep "$config$" | awk '{ print $1 }'`
383 if [ ! -z $existing ]
384 then
385 echo "Nothing to do, your container has already started!"
386 exit 1
387 fi
388
389 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep "$config$" | awk '{ print $1 }'`
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
680dd4ea 460 [ ! -e $cidbootstrap ] && echo "FAILED TO BOOTSTRAP" && 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
7936ebaa
MB
475 mailtest)
476 run_mailtest
477 exit 0
478 ;;
479
2fc6ff36 480 enter)
30835a52 481 exec $docker_path exec -it $config /bin/bash
2fc6ff36
S
482 ;;
483
5f803fb4 484 ssh)
30835a52
S
485 existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep "$config$" | awk '{ print $1 }'`
486
487 if [[ ! -z $existing ]]; then
488 address="`$docker_path port $config 22`"
489 split=(${address//:/ })
490 exec ssh -o StrictHostKeyChecking=no root@${split[0]} -p ${split[1]}
491 else
492 echo "$config is not running!"
493 exit 1
494 fi
5f803fb4 495 ;;
7e738616 496
5f803fb4 497 stop)
337a89aa
S
498 run_stop
499 exit 0
5f803fb4 500 ;;
7e738616 501
5f803fb4 502 logs)
7e738616 503
30835a52
S
504 $docker_path logs $config
505 exit 0
5f803fb4 506 ;;
7e738616 507
337a89aa
S
508 restart)
509 run_stop
510 run_start
511 exit 0
512 ;;
80c11be3 513
337a89aa
S
514 start)
515 run_start
516 exit 0
5f803fb4 517 ;;
7e738616 518
680dd4ea 519 rebuild)
4b6456ef 520 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
098533cb
S
521 echo "Ensuring discourse docker is up to date"
522
523 git remote update
524
525 LOCAL=$(git rev-parse @)
526 REMOTE=$(git rev-parse @{u})
527 BASE=$(git merge-base @ @{u})
528
529 if [ $LOCAL = $REMOTE ]; then
530 echo "Discourse Docker is up-to-date"
531
532 elif [ $LOCAL = $BASE ]; then
533 echo "Updating Discourse Docker"
534 git pull || (echo 'failed to update' && exit 1)
535 exec /bin/bash $0 $@
536
537 elif [ $REMOTE = $BASE ]; then
538 echo "Your version of Discourse Docker is ahead of origin"
539
540 else
905ec302 541 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
098533cb
S
542 fi
543
4b6456ef 544 fi
30835a52
S
545
546 set_existing_container
547
548 if [ ! -z $existing ]
680dd4ea
S
549 then
550 echo "Stopping old container"
30835a52
S
551 (
552 set -x
553 $docker_path stop -t 10 $config
554 )
680dd4ea
S
555 fi
556
557 run_bootstrap
558
30835a52 559 if [ ! -z $existing ]
680dd4ea 560 then
30835a52
S
561 echo "Removing old container"
562 (
563 set -x
564 $docker_path rm $config
565 )
680dd4ea
S
566 fi
567
568 run_start
569 exit 0
570 ;;
571
7e738616 572
5f803fb4 573 destroy)
30835a52
S
574 (set -x; $docker_path stop -t 10 $config && $docker_path rm $config) || (echo "$config was not found" && exit 0)
575 exit 0
5f803fb4
SS
576 ;;
577esac
7e738616 578
5f803fb4 579usage