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