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