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