56742d28fa84c19f9a465a5b78873fac94c94dd6
[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.5
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 exit 1
57 }
58
59 compare_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
80 prereqs() {
81
82 # 1. docker daemon running?
83 test=`$docker_path info >/dev/null`
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
90 # 2. running aufs or btrfs
91 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
92 if [[ "$test" =~ [aufs|btrfs] ]] ; then : ; else
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:"
95 echo "https://meta.discourse.org/t/discourse-docker-installation-without-aufs/15639"
96 fi
97
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
101
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
104 # At least minimum version
105 if compare_version "${docker_min_version}" "${test}"; then
106 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
107 exit 1
108 fi
109
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
115 # 4. able to attach stderr / out / tty
116 test=`$docker_path run -i --rm -a stdout -a stderr $image echo working`
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
125 if [ "$opt" != "--skip-prereqs" ] ; then
126 prereqs
127 fi
128
129 get_ssh_pub_key() {
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
144 if tty -s ; then
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
158 fi
159 fi
160
161 return 0
162 }
163
164
165 install_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
183 host_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
199 RUBY
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
214 set_volumes() {
215 volumes=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \
216 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
217 }
218
219 set_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
224 set_template_info() {
225
226 templates=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
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
250 # default to UTF-8 for the dbs sake
251 env = {'LANG' => 'en_US.UTF-8'}
252 input.split('_FILE_SEPERATOR_').each do |yml|
253 yml.strip!
254 begin
255 env.merge!(YAML.load(yml)['env'] || {})
256 rescue Psych::SyntaxError => e
257 puts e
258 puts "*ERROR."
259 rescue => e
260 puts yml
261 p e
262 end
263 end
264 puts env.map{|k,v| "-e\n#{k}=#{v}" }.join("\n")
265 RUBY
266
267 raw=`exec echo "$input" | $docker_path run --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
268
269 env=()
270 ok=1
271 while read i; do
272 if [ "$i" == "*ERROR." ]; then
273 ok=0
274 elif [ -n "$i" ]; then
275 env[${#env[@]}]=$i
276 fi
277 done <<< "$raw"
278
279 if [ "$ok" -ne 1 ]; then
280 echo "${env[@]}"
281 echo "YAML syntax error. Please check your /var/docker/containers/*.yml config files."
282 exit 1
283 fi
284 echo "Calculated ENV: ${env[@]}"
285 }
286
287 [ -z $docker_path ] && {
288 install_docker
289 }
290
291
292 [ $# -lt 2 ] && {
293 usage
294 }
295
296 if [ ! -e $config_file ]
297 then
298 echo "Config file was not found, ensure $config_file exists"
299 echo ""
300 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
301 exit 1
302 fi
303
304
305 docker_version=($($docker_path --version))
306 docker_version=${test[2]//,/}
307
308 if 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=""
311 else
312 restart_policy=${restart_policy:---restart=always}
313 fi
314
315
316 run_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
324 run_stop(){
325 if [ ! -e $cidfile ]
326 then
327 echo "No cid found"
328 exit 1
329 else
330 $docker_path stop -t 10 `cat $cidfile`
331 fi
332 }
333
334 run_start(){
335
336 host_run
337
338 if [ ! -e $cidfile ]
339 then
340 echo "No cid found, creating a new container"
341 ports=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \
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
346 set_links
347
348 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep "$config$" | awk '{ print $1 }'`
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
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 \
357 $volumes $local_discourse/$config /sbin/boot
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
370 found=`$docker_path ps -q -a --no-trunc | grep $cid`
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"
379 $docker_path start $attach_on_start `cat $cidfile`
380 exit 0
381 fi
382
383 }
384
385 run_bootstrap(){
386
387 host_run
388
389 get_ssh_pub_key
390
391 # Is the image available?
392 # If not, pull it here so the user is aware what's happening.
393 $docker_path history $image >/dev/null 2>&1 || $docker_path pull $image
394
395 set_template_info
396
397 base_image=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
398 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
399
400 update_pups=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
401 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
402
403 if [[ ! X"" = X"$base_image" ]]; then
404 image=$base_image
405 fi
406
407 set_volumes
408 set_links
409
410 rm -f $cidbootstrap
411
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"
417
418 echo $run_command
419
420 env=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
421
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 \
423 /bin/bash -c "$run_command") \
424 || ($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
425
426 [ ! -e $cidbootstrap ] && echo "FAILED TO BOOTSTRAP" && exit 1
427
428 sleep 5
429
430 $docker_path commit `cat $cidbootstrap` $local_discourse/$config || echo 'FAILED TO COMMIT'
431 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
432 }
433
434 case "$command" in
435 bootstrap)
436 run_bootstrap
437 echo "Successfully bootstrapped, to startup use ./launcher start $config"
438 exit 0
439 ;;
440
441 mailtest)
442 run_mailtest
443 exit 0
444 ;;
445
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
475 ssh)
476 if [ ! -e $cidfile ]
477 then
478 echo "No cid found"
479 exit 1
480 else
481 cid="`cat $cidfile`"
482 address="`$docker_path port $cid 22`"
483 split=(${address//:/ })
484 exec ssh -o StrictHostKeyChecking=no root@${split[0]} -p ${split[1]}
485 fi
486 ;;
487
488 stop)
489 run_stop
490 exit 0
491 ;;
492
493 logs)
494
495 if [ ! -e $cidfile ]
496 then
497 echo "No cid found"
498 exit 1
499 else
500 $docker_path logs `cat $cidfile`
501 exit 0
502 fi
503 ;;
504
505 restart)
506 run_stop
507 run_start
508 exit 0
509 ;;
510
511 start)
512 run_start
513 exit 0
514 ;;
515
516 rebuild)
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
521 if [ -e $cidfile ]
522 then
523 echo "Stopping old container"
524 $docker_path stop -t 10 `cat $cidfile`
525 fi
526
527 run_bootstrap
528
529 if [ -e $cidfile ]
530 then
531 $docker_path rm `cat $cidfile` && rm $cidfile
532 fi
533
534 run_start
535 exit 0
536 ;;
537
538
539 destroy)
540 if [ -e $cidfile ]
541 then
542 echo "destroying container $cidfile"
543 $docker_path stop -t 10 `cat $cidfile`
544 $docker_path rm `cat $cidfile` && rm $cidfile
545 exit 0
546 else
547 echo "nothing to destroy cidfile does not exist"
548 exit 1
549 fi
550 ;;
551 esac
552
553 usage