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