Test that we have enough memory and diskspace before installing
[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
4770834b
MP
154
155 # 6. sufficient available resources
156 # 6a. Memory
157 free_mem="$(LANG=C free -m | grep 'buffers/cache' | awk '{print $4}')"
158 if [ "$free_mem" -lt 800 ]; then
159 echo "You do not appear to have sufficient memory to run Discourse."
160 echo
161 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#create-new-cloud-server"
162 exit 1
163 elif [ "$free_mem" -lt 1800 ]; then
164 total_swap="$(LANG=C free -m | grep ^Swap: | awk '{print $2}')"
165 if [ "$total_swap" -lt 1000 ]; then
166 echo "You must have at least 1GB of swap when running in a low-memory environment."
167 echo
168 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#set-up-swap-if-needed"
169 exit 1
170 fi
171 fi
172
173 # 6b. Disk space
174 free_disk="$(df /var | tail -n 1 | awk '{print $4}')"
175 if [ "$free_disk" -lt 5000 ]; then
176 echo "You must have at least 5GB of *free* disk space to install Discourse."
177 echo
178 echo "Please free up some space, or expand your disk, before continuing."
179 exit 1
180 fi
a3e18d95
S
181}
182
55d17203
RW
183if [ "$opt" != "--skip-prereqs" ] ; then
184 prereqs
185fi
a3e18d95 186
e02c1511
S
187if [ "$opt" == "--docker-args" ] ; then
188 user_args=$4
189else
190 user_args=""
191fi
192
88126eba 193get_ssh_pub_key() {
c76db1ea
MK
194 local ${ssh_key_locations}
195 ssh_key_locations=(
841334d9
MK
196 ~/.ssh/id_ed25519.pub
197 ~/.ssh/id_ecdsa.pub
c76db1ea
MK
198 ~/.ssh/id_rsa.pub
199 ~/.ssh/id_dsa.pub
841334d9 200 ~core/.ssh/authorized_keys
c76db1ea
MK
201 )
202
203 local $keyfile
204 for keyfile in "${ssh_key_locations[@]}"; do
205 if [[ -e ${keyfile} ]] ; then
206 ssh_pub_key="$(cat ${keyfile})"
841334d9 207 return 0
c76db1ea
MK
208 fi
209 done
210
e3709631 211 return 0
88126eba
S
212}
213
214
52388b87
SS
215install_docker() {
216
5933f559
DP
217 echo "Docker is not installed, you will need to install Docker in order to run Discourse"
218 echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system"
52388b87 219 echo
5933f559 220 echo "If you are running Ubuntu Trusty or later, you can try the following:"
52388b87
SS
221 echo
222
b749fe0c 223 echo "sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D"
5933f559 224 echo "sudo sh -c \"echo deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -sc) main > /etc/apt/sources.list.d/docker.list\""
52388b87 225 echo "sudo apt-get update"
b749fe0c 226 echo "sudo apt-get install docker-engine"
52388b87
SS
227
228 exit 1
229}
230
60f9f04c
S
231host_run() {
232 read -r -d '' env_ruby << 'RUBY'
233 require 'yaml'
234
235 input = STDIN.readlines.join
236 yaml = YAML.load(input)
237
238 if host_run = yaml['host_run']
239 params = yaml['params'] || {}
240 host_run.each do |run|
241 params.each do |k,v|
242 run = run.gsub("$#{k}", v)
243 end
244 STDOUT.write "#{run}--SEP--"
245 end
246 end
247RUBY
248
e02c1511 249 host_run=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
60f9f04c
S
250
251 while [ "$host_run" ] ; do
252 iter=${host_run%%--SEP--*}
253 echo
254 echo "Host run: $iter"
255 $iter || exit 1
256 echo
257 host_run="${host_run#*--SEP--}"
258 done
259}
260
261
d90671f3 262set_volumes() {
e02c1511 263 volumes=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
d90671f3
SS
264 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
265}
266
41daa523 267set_links() {
e02c1511 268 links=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
41daa523 269 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
270}
271
7f77c274
SS
272set_template_info() {
273
e02c1511 274 templates=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
7f77c274
SS
275 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
276
277
278 arrTemplates=(${templates// / })
279 config_data=$(cat $config_file)
280
281 input="hack: true"
282
283
284 for template in "${arrTemplates[@]}"
285 do
286 [ ! -z $template ] && {
287 input="$input _FILE_SEPERATOR_ $(cat $template)"
288 }
289 done
290
291 # we always want our config file last so it takes priority
292 input="$input _FILE_SEPERATOR_ $config_data"
293
294 read -r -d '' env_ruby << 'RUBY'
295 require 'yaml'
296
297 input=STDIN.readlines.join
3cb3d9c4
S
298 # default to UTF-8 for the dbs sake
299 env = {'LANG' => 'en_US.UTF-8'}
7f77c274
SS
300 input.split('_FILE_SEPERATOR_').each do |yml|
301 yml.strip!
302 begin
303 env.merge!(YAML.load(yml)['env'] || {})
f3824347 304 rescue Psych::SyntaxError => e
305 puts e
306 puts "*ERROR."
7f77c274
SS
307 rescue => e
308 puts yml
309 p e
310 end
311 end
4b563ee8 312 puts env.map{|k,v| "-e\n#{k}=#{v}" }.join("\n")
7f77c274
SS
313RUBY
314
e02c1511 315 raw=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
4b563ee8
SS
316
317 env=()
f3824347 318 ok=1
4b563ee8 319 while read i; do
f3824347 320 if [ "$i" == "*ERROR." ]; then
321 ok=0
322 elif [ -n "$i" ]; then
16f2d250
S
323 env[${#env[@]}]=$i
324 fi
4b563ee8
SS
325 done <<< "$raw"
326
f3824347 327 if [ "$ok" -ne 1 ]; then
328 echo "${env[@]}"
d6d1fb6e 329 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
f3824347 330 exit 1
331 fi
7f77c274
SS
332}
333
52388b87
SS
334[ -z $docker_path ] && {
335 install_docker
336}
337
de869404 338[ "$command" == "cleanup" ] && {
c692f743
L
339 echo
340 echo "The following command will"
341 echo "- Delete all docker images for old containers"
342 echo "- Delete all stopped and orphan containers"
343 echo
344 read -p "Are you sure (Y/n): " -n 1 -r && echo
345 if [[ $REPLY =~ ^[Yy]$ || ! $REPLY ]]
346 then
30835a52 347 space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
29a35d1b 348 echo "Starting Cleanup (bytes free $space)"
30835a52 349
29a35d1b 350 STATE_DIR=./.gc-state scripts/docker-gc
30835a52 351
29a35d1b
S
352 space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
353 echo "Finished Cleanup (bytes free $space)"
30835a52 354
c692f743 355 else
30835a52 356 exit 1
c692f743 357 fi
a8b66f98
L
358 exit 0
359}
5f803fb4 360
c1005add 361[ $# -lt 2 ] && {
5f803fb4
SS
362 usage
363}
364
7e738616
S
365if [ ! -e $config_file ]
366 then
367 echo "Config file was not found, ensure $config_file exists"
71680b16
S
368 echo ""
369 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
7e738616
S
370 exit 1
371fi
372
337a89aa 373
e2ed1fb6
S
374docker_version=($($docker_path --version))
375docker_version=${test[2]//,/}
376
377if compare_version "1.2.0" "$docker_version"; then
378 echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
379 restart_policy=""
380else
e0fd1f5b 381 restart_policy=${restart_policy:---restart=always}
e2ed1fb6
S
382fi
383
30835a52 384set_existing_container(){
295e8f19 385 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
337a89aa
S
386}
387
30835a52 388run_stop(){
337a89aa 389
30835a52 390 set_existing_container
60f9f04c 391
30835a52 392 if [ ! -z $existing ]
337a89aa 393 then
30835a52
S
394 (
395 set -x
396 $docker_path stop -t 10 $config
397 )
337a89aa 398 else
30835a52
S
399 echo "$config was not started !"
400 exit 1
401 fi
402}
337a89aa 403
30835a52 404run_start(){
337a89aa 405
295e8f19
S
406 existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
407 echo $existing
30835a52
S
408 if [ ! -z $existing ]
409 then
410 echo "Nothing to do, your container has already started!"
411 exit 1
412 fi
413
295e8f19 414 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
30835a52
S
415 if [ ! -z $existing ]
416 then
417 echo "starting up existing container"
418 (
419 set -x
420 $docker_path start $config
421 )
422 exit 0
423 fi
424
425 host_run
426 ports=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
427 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"`
428
8b617e6e
PG
429 docker_args=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
430 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
431
30835a52
S
432 set_template_info
433 set_volumes
434 set_links
435
436 (
437 hostname=`hostname`
438 set -x
439 $docker_path run $user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname-$config" \
8b617e6e
PG
440 -e DOCKER_HOST_IP=$docker_ip --name $config -t $ports $volumes $docker_args $local_discourse/$config \
441 /sbin/boot
30835a52
S
442
443 )
444 exit 0
337a89aa
S
445
446}
447
680dd4ea 448run_bootstrap(){
60f9f04c
S
449
450 host_run
451
680dd4ea 452 get_ssh_pub_key
88126eba 453
680dd4ea
S
454 # Is the image available?
455 # If not, pull it here so the user is aware what's happening.
4807b1b8 456 $docker_path history $image >/dev/null 2>&1 || $docker_path pull $image
88126eba 457
680dd4ea 458 set_template_info
93149421 459
e02c1511 460 base_image=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
680dd4ea 461 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
93149421 462
e02c1511 463 update_pups=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
680dd4ea 464 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
b9c7b50e 465
680dd4ea
S
466 if [[ ! X"" = X"$base_image" ]]; then
467 image=$base_image
468 fi
b9c7b50e 469
680dd4ea 470 set_volumes
41daa523 471 set_links
b9c7b50e 472
680dd4ea 473 rm -f $cidbootstrap
d90671f3 474
680dd4ea
S
475 run_command="cd /pups &&"
476 if [[ ! "false" = $update_pups ]]; then
477 run_command="$run_command git pull &&"
478 fi
479 run_command="$run_command /pups/bin/pups --stdin"
2162f1d4 480
680dd4ea 481 echo $run_command
b9c7b50e 482
680dd4ea 483 env=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
c4498636 484
e02c1511 485 (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 486 /bin/bash -c "$run_command") \
4807b1b8 487 || ($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
88126eba 488
4b733d87 489 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
9fb5f2d3 490
680dd4ea 491 sleep 5
2162f1d4 492
4807b1b8
MB
493 $docker_path commit `cat $cidbootstrap` $local_discourse/$config || echo 'FAILED TO COMMIT'
494 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
680dd4ea 495}
9fb5f2d3 496
680dd4ea
S
497case "$command" in
498 bootstrap)
680dd4ea 499 run_bootstrap
2dd2e330 500 echo "Successfully bootstrapped, to startup use ./launcher start $config"
4b3aebe1 501 exit 0
5f803fb4 502 ;;
1acce9e4 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