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