launcher: Loop over possible SSH authorized key file locations.
[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
7e738616 13cidfile=cids/"$config".cid
1acce9e4 14cidbootstrap=cids/"$config"_boostrap.cid
5efded6a 15local_discourse=local_discourse
074f2b6a 16image=samsaffron/discourse:1.0.5
4807b1b8 17docker_path=`which docker.io || which docker`
7e738616 18
e0fd1f5b
TB
19if [ "${SUPERVISED}" = "true" ]; then
20 restart_policy="--restart=no"
21 attach_on_start="-a"
22 attach_on_run="-a stdout -a stderr"
23else
24 attach_on_run="-d"
25fi
26
813fef38
MK
27if [ -x "$(which ip 2>/dev/null)" ]; then
28 docker_ip=`ip addr show docker0 | \
03bb0735
LG
29 grep 'inet ' | \
30 awk '{ split($2,a,"/"); print a[1] }';`
31else
813fef38 32 docker_ip=`ifconfig | \
03bb0735
LG
33 grep -B1 "inet addr" | \
34 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
35 grep docker0 | \
36 awk -F: '{ print $3 }';`
37fi
80c11be3
SS
38
39
5f803fb4 40usage () {
55d17203 41 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
7e738616 42 echo "Commands:"
1acce9e4
SS
43 echo " start: Start/initialize a container"
44 echo " stop: Stop a running container"
45 echo " restart: Restart a container"
46 echo " destroy: Stop and remove a container"
2fc6ff36 47 echo " enter: Use nsenter to enter a container"
5f803fb4 48 echo " ssh: Start a bash shell in a running container"
1acce9e4 49 echo " logs: Docker logs for container"
7936ebaa 50 echo " mailtest: Test the mail settings in a container"
408a9c19 51 echo " bootstrap: Bootstrap a container for the config based on a template"
680dd4ea 52 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
55d17203
RW
53 echo
54 echo "Options:"
55 echo " --skip-prereqs Don't check prerequisites"
7e738616
S
56 exit 1
57}
58
60668406
DP
59compare_version() {
60 declare -a ver_a
61 declare -a ver_b
62 IFS=. read -a ver_a <<< "$1"
63 IFS=. read -a ver_b <<< "$2"
64
65 while [[ -n $ver_a ]]; do
66 if (( ver_a > ver_b )); then
67 return 0
68 elif (( ver_b > ver_a )); then
69 return 1
70 else
71 unset ver_a[0]
72 ver_a=("${ver_a[@]}")
73 unset ver_b[0]
74 ver_b=("${ver_b[@]}")
75 fi
76 done
77 return 1 # They are equal
78}
79
a3e18d95
S
80prereqs() {
81
e741295a 82 # 1. docker daemon running?
4807b1b8 83 test=`$docker_path info >/dev/null`
e741295a
MB
84
85 if [[ $? -ne 0 ]] ; then
86 echo "Cannot connect to the docker daemon - verify it is running and you have access"
87 exit 1
88 fi
89
185e78c6
S
90 # 2. running aufs or btrfs
91 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
92 if [[ "$test" =~ [aufs|btrfs] ]] ; then : ; else
9717d9cb
JA
93 echo "Your Docker installation is not using the recommended AuFS (union filesystem) and may be unstable."
94 echo "If you are unable to bootstrap / stop your image please report the issue at:"
2d7d1501 95 echo "https://meta.discourse.org/t/discourse-docker-installation-without-aufs/15639"
a3e18d95
S
96 fi
97
60668406
DP
98 # 3. running recommended docker version
99 test=($($docker_path --version)) # Get docker version string
100 test=${test[2]//,/} # Get version alone and strip comma if exists
a3e18d95 101
cf00fce0
S
102 [[ "$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
103
60668406 104 # At least minimum version
cf00fce0 105 if compare_version "${docker_min_version}" "${test}"; then
60668406 106 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
a3e18d95
S
107 exit 1
108 fi
109
60668406
DP
110 # Recommend best version
111 if compare_version "${docker_rec_version}" "${test}"; then
112 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
113 fi
114
e741295a 115 # 4. able to attach stderr / out / tty
4807b1b8 116 test=`$docker_path run -i --rm -a stdout -a stderr $image echo working`
a3e18d95
S
117 if [[ "$test" =~ "working" ]] ; then : ; else
118 echo "Your Docker installation is not working correctly"
119 echo
120 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
121 exit 1
122 fi
123}
124
55d17203
RW
125if [ "$opt" != "--skip-prereqs" ] ; then
126 prereqs
127fi
a3e18d95 128
88126eba 129get_ssh_pub_key() {
c76db1ea
MK
130 local ${ssh_key_locations}
131 ssh_key_locations=(
132 ~/.ssh/id_rsa.pub
133 ~/.ssh/id_dsa.pub
134 )
135
136 local $keyfile
137 for keyfile in "${ssh_key_locations[@]}"; do
138 if [[ -e ${keyfile} ]] ; then
139 ssh_pub_key="$(cat ${keyfile})"
140 return 1
141 fi
142 done
143
9d28af0e 144 if tty -s ; then
c76db1ea
MK
145 echo "This user has no SSH key, but a SSH key is required to access the Discourse Docker container."
146 read -p "Generate a SSH key? (Y/n) " -n 1 -r
147 if [[ $REPLY =~ ^[Nn]$ ]] ; then
148 echo
149 echo WARNING: You may not be able to log in to your container.
150 echo
151 else
152 echo
153 echo Generating SSH key
154 mkdir -p ~/.ssh && ssh-keygen -f ~/.ssh/id_rsa -t rsa -N ''
155 echo
156 ssh_pub_key="$(cat ~/.ssh/id_rsa.pub)"
157 return 1
88126eba
S
158 fi
159 fi
160
c76db1ea 161 return 0
88126eba
S
162}
163
164
52388b87
SS
165install_docker() {
166
167 echo "Docker is not installed, make sure you are running on the 3.8 kernel"
168 echo "The best supported Docker release is Ubuntu 12.04.03 for it run the following"
169 echo
170 echo "sudo apt-get update"
171 echo "sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring"
172 echo "sudo reboot"
173 echo
174
175 echo "sudo sh -c \"wget -qO- https://get.docker.io/gpg | apt-key add -\""
176 echo "sudo sh -c \"echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list\""
177 echo "sudo apt-get update"
178 echo "sudo apt-get install lxc-docker"
179
180 exit 1
181}
182
60f9f04c
S
183host_run() {
184 read -r -d '' env_ruby << 'RUBY'
185 require 'yaml'
186
187 input = STDIN.readlines.join
188 yaml = YAML.load(input)
189
190 if host_run = yaml['host_run']
191 params = yaml['params'] || {}
192 host_run.each do |run|
193 params.each do |k,v|
194 run = run.gsub("$#{k}", v)
195 end
196 STDOUT.write "#{run}--SEP--"
197 end
198 end
199RUBY
200
201 host_run=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
202
203 while [ "$host_run" ] ; do
204 iter=${host_run%%--SEP--*}
205 echo
206 echo "Host run: $iter"
207 $iter || exit 1
208 echo
209 host_run="${host_run#*--SEP--}"
210 done
211}
212
213
d90671f3 214set_volumes() {
4807b1b8 215 volumes=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \
d90671f3
SS
216 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
217}
218
41daa523 219set_links() {
220 links=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \
221 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
222}
223
7f77c274
SS
224set_template_info() {
225
4807b1b8 226 templates=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
7f77c274
SS
227 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
228
229
230 arrTemplates=(${templates// / })
231 config_data=$(cat $config_file)
232
233 input="hack: true"
234
235
236 for template in "${arrTemplates[@]}"
237 do
238 [ ! -z $template ] && {
239 input="$input _FILE_SEPERATOR_ $(cat $template)"
240 }
241 done
242
243 # we always want our config file last so it takes priority
244 input="$input _FILE_SEPERATOR_ $config_data"
245
246 read -r -d '' env_ruby << 'RUBY'
247 require 'yaml'
248
249 input=STDIN.readlines.join
3cb3d9c4
S
250 # default to UTF-8 for the dbs sake
251 env = {'LANG' => 'en_US.UTF-8'}
7f77c274
SS
252 input.split('_FILE_SEPERATOR_').each do |yml|
253 yml.strip!
254 begin
255 env.merge!(YAML.load(yml)['env'] || {})
f3824347 256 rescue Psych::SyntaxError => e
257 puts e
258 puts "*ERROR."
7f77c274
SS
259 rescue => e
260 puts yml
261 p e
262 end
263 end
4b563ee8 264 puts env.map{|k,v| "-e\n#{k}=#{v}" }.join("\n")
7f77c274
SS
265RUBY
266
4807b1b8 267 raw=`exec echo "$input" | $docker_path run --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
4b563ee8
SS
268
269 env=()
f3824347 270 ok=1
4b563ee8 271 while read i; do
f3824347 272 if [ "$i" == "*ERROR." ]; then
273 ok=0
274 elif [ -n "$i" ]; then
16f2d250
S
275 env[${#env[@]}]=$i
276 fi
4b563ee8
SS
277 done <<< "$raw"
278
f3824347 279 if [ "$ok" -ne 1 ]; then
280 echo "${env[@]}"
a3322f2a 281 echo "YAML syntax error. Please check your /var/docker/containers/*.yml config files."
f3824347 282 exit 1
283 fi
4b563ee8 284 echo "Calculated ENV: ${env[@]}"
7f77c274
SS
285}
286
52388b87
SS
287[ -z $docker_path ] && {
288 install_docker
289}
290
5f803fb4 291
c1005add 292[ $# -lt 2 ] && {
5f803fb4
SS
293 usage
294}
295
7e738616
S
296if [ ! -e $config_file ]
297 then
298 echo "Config file was not found, ensure $config_file exists"
71680b16
S
299 echo ""
300 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
7e738616
S
301 exit 1
302fi
303
337a89aa 304
e2ed1fb6
S
305docker_version=($($docker_path --version))
306docker_version=${test[2]//,/}
307
308if compare_version "1.2.0" "$docker_version"; then
309 echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
310 restart_policy=""
311else
e0fd1f5b 312 restart_policy=${restart_policy:---restart=always}
e2ed1fb6
S
313fi
314
315
7936ebaa
MB
316run_mailtest(){
317 if [ ! -e $config_file ]; then
318 echo "Config does not exist: $config_file" >&2
319 exit 1
320 fi
321 exec scripts/mailtest $config_file
322}
323
337a89aa
S
324run_stop(){
325 if [ ! -e $cidfile ]
326 then
327 echo "No cid found"
328 exit 1
329 else
4807b1b8 330 $docker_path stop -t 10 `cat $cidfile`
337a89aa
S
331 fi
332}
333
334run_start(){
335
60f9f04c
S
336 host_run
337
337a89aa
S
338 if [ ! -e $cidfile ]
339 then
340 echo "No cid found, creating a new container"
4807b1b8 341 ports=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \
337a89aa
S
342 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"`
343
344 set_template_info
345 set_volumes
41daa523 346 set_links
337a89aa 347
4807b1b8 348 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep "$config$" | awk '{ print $1 }'`
337a89aa
S
349 if [ ! -z $existing ]
350 then
351 echo "Found an existing container by its name, recovering cidfile, please rerun"
352 echo $existing > $cidfile
353 exit 1
354 fi
355
41daa523 356 $docker_path run $links $attach_on_run $restart_policy "${env[@]}" -h "`hostname`-$config" -e DOCKER_HOST_IP=$docker_ip --name $config -t --cidfile $cidfile $ports \
074f2b6a 357 $volumes $local_discourse/$config /sbin/boot
337a89aa
S
358
359 exit 0
360 else
361 cid=`cat $cidfile`
362
363 if [ -z $cid ]
364 then
365 echo "Detected empty cid file, deleting, please re-run"
366 rm $cidfile
367 exit 1
368 fi
369
4807b1b8 370 found=`$docker_path ps -q -a --no-trunc | grep $cid`
337a89aa
S
371 if [ -z $found ]
372 then
373 echo "Invalid cid file, deleting, please re-run"
374 rm $cidfile
375 exit 1
376 fi
377
378 echo "cid found, ensuring container is started"
e0fd1f5b 379 $docker_path start $attach_on_start `cat $cidfile`
337a89aa
S
380 exit 0
381 fi
382
383}
384
680dd4ea 385run_bootstrap(){
60f9f04c
S
386
387 host_run
388
680dd4ea 389 get_ssh_pub_key
88126eba 390
680dd4ea
S
391 # Is the image available?
392 # If not, pull it here so the user is aware what's happening.
4807b1b8 393 $docker_path history $image >/dev/null 2>&1 || $docker_path pull $image
88126eba 394
680dd4ea 395 set_template_info
93149421 396
4807b1b8 397 base_image=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
680dd4ea 398 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
93149421 399
4807b1b8 400 update_pups=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
680dd4ea 401 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
b9c7b50e 402
680dd4ea
S
403 if [[ ! X"" = X"$base_image" ]]; then
404 image=$base_image
405 fi
b9c7b50e 406
680dd4ea 407 set_volumes
41daa523 408 set_links
b9c7b50e 409
680dd4ea 410 rm -f $cidbootstrap
d90671f3 411
680dd4ea
S
412 run_command="cd /pups &&"
413 if [[ ! "false" = $update_pups ]]; then
414 run_command="$run_command git pull &&"
415 fi
416 run_command="$run_command /pups/bin/pups --stdin"
2162f1d4 417
680dd4ea 418 echo $run_command
b9c7b50e 419
680dd4ea 420 env=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
c4498636 421
41daa523 422 (exec echo "$input" | $docker_path run $links "${env[@]}" -e DOCKER_HOST_IP=$docker_ip --cidfile $cidbootstrap -i -a stdin -a stdout -a stderr $volumes $image \
680dd4ea 423 /bin/bash -c "$run_command") \
4807b1b8 424 || ($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
88126eba 425
680dd4ea 426 [ ! -e $cidbootstrap ] && echo "FAILED TO BOOTSTRAP" && exit 1
9fb5f2d3 427
680dd4ea 428 sleep 5
2162f1d4 429
4807b1b8
MB
430 $docker_path commit `cat $cidbootstrap` $local_discourse/$config || echo 'FAILED TO COMMIT'
431 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
680dd4ea 432}
9fb5f2d3 433
680dd4ea
S
434case "$command" in
435 bootstrap)
680dd4ea 436 run_bootstrap
2dd2e330 437 echo "Successfully bootstrapped, to startup use ./launcher start $config"
4b3aebe1 438 exit 0
5f803fb4 439 ;;
1acce9e4 440
7936ebaa
MB
441 mailtest)
442 run_mailtest
443 exit 0
444 ;;
445
2fc6ff36
S
446 enter)
447
448 if [ ! -e $cidfile ]
449 then
450 echo "No cid found"
451 exit 1
452 fi
453
454 if [ ! $UID -eq 0 ] ;
455 then
456 echo "enter command must run as root, will attempt to sudo"
457 echo
458 fi
459
460 if [ ! -e bin/nsenter ]
461 then
462 echo "Downloading nsenter"
463 $docker_path pull samsaffron/nsenter
464 ($docker_path run --rm samsaffron/nsenter cat /nsenter > bin/nsenter1) || exit 1
465 cp bin/nsenter1 bin/nsenter
466 chmod +x bin/nsenter
467 fi
468
469 PID=$($docker_path inspect --format {{.State.Pid}} `cat $cidfile`)
470 SHELL=/bin/bash sudo -E bin/nsenter --target $PID --mount --uts --ipc --net --pid
471
472 exit 0;
473 ;;
474
5f803fb4
SS
475 ssh)
476 if [ ! -e $cidfile ]
477 then
478 echo "No cid found"
479 exit 1
480 else
481 cid="`cat $cidfile`"
4807b1b8 482 address="`$docker_path port $cid 22`"
5f803fb4 483 split=(${address//:/ })
38c09f5e 484 exec ssh -o StrictHostKeyChecking=no root@${split[0]} -p ${split[1]}
5f803fb4
SS
485 fi
486 ;;
7e738616 487
5f803fb4 488 stop)
337a89aa
S
489 run_stop
490 exit 0
5f803fb4 491 ;;
7e738616 492
5f803fb4 493 logs)
7e738616 494
5f803fb4
SS
495 if [ ! -e $cidfile ]
496 then
497 echo "No cid found"
498 exit 1
499 else
4807b1b8 500 $docker_path logs `cat $cidfile`
5f803fb4
SS
501 exit 0
502 fi
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
MB
517 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
518 echo "Updating discourse docker"
519 git pull || (echo 'failed to update' && exit 1)
520 fi
680dd4ea
S
521 if [ -e $cidfile ]
522 then
523 echo "Stopping old container"
4807b1b8 524 $docker_path stop -t 10 `cat $cidfile`
680dd4ea
S
525 fi
526
527 run_bootstrap
528
529 if [ -e $cidfile ]
530 then
4807b1b8 531 $docker_path rm `cat $cidfile` && rm $cidfile
680dd4ea
S
532 fi
533
534 run_start
535 exit 0
536 ;;
537
7e738616 538
5f803fb4
SS
539 destroy)
540 if [ -e $cidfile ]
541 then
542 echo "destroying container $cidfile"
4807b1b8
MB
543 $docker_path stop -t 10 `cat $cidfile`
544 $docker_path rm `cat $cidfile` && rm $cidfile
5f803fb4
SS
545 exit 0
546 else
547 echo "nothing to destroy cidfile does not exist"
548 exit 1
549 fi
550 ;;
551esac
7e738616 552
5f803fb4 553usage