Merge pull request #94 from tolbrino/tb/systemd-support
[discourse_docker.git] / launcher
CommitLineData
ace450bd 1#!/bin/bash
7e738616
S
2
3command=$1
4config=$2
55d17203
RW
5opt=$3
6
7936ebaa 7cd "$(dirname "$0")"
55d17203 8
5701c085
S
9docker_min_version='1.2.0'
10docker_rec_version='1.2.0'
60668406 11
8dea575c 12config_file=containers/"$config".yml
7e738616 13cidfile=cids/"$config".cid
1acce9e4 14cidbootstrap=cids/"$config"_boostrap.cid
5efded6a 15local_discourse=local_discourse
6510f3ec 16image=samsaffron/discourse:1.0.3
4807b1b8 17docker_path=`which docker.io || which docker`
7e738616 18
e0fd1f5b
TB
19if [ "${SUPERVISED}" = "true" ]; then
20 restart_policy="--restart=no"
21 attach_on_start="-a"
22 attach_on_run="-a stdout -a stderr"
23else
24 attach_on_run="-d"
25fi
26
03bb0735
LG
27if [ -x /sbin/ip ]; then
28 docker_ip=`/sbin/ip addr show docker0 | \
29 grep 'inet ' | \
30 awk '{ split($2,a,"/"); print a[1] }';`
31else
32 docker_ip=`/sbin/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 }';`
37fi
80c11be3
SS
38
39
5f803fb4 40usage () {
55d17203 41 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
7e738616 42 echo "Commands:"
1acce9e4
SS
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"
2fc6ff36 47 echo " enter: Use nsenter to enter a container"
5f803fb4 48 echo " ssh: Start a bash shell in a running container"
1acce9e4 49 echo " logs: Docker logs for container"
7936ebaa 50 echo " mailtest: Test the mail settings in a container"
408a9c19 51 echo " bootstrap: Bootstrap a container for the config based on a template"
680dd4ea 52 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
55d17203
RW
53 echo
54 echo "Options:"
55 echo " --skip-prereqs Don't check prerequisites"
7e738616
S
56 exit 1
57}
58
60668406
DP
59compare_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
a3e18d95
S
80prereqs() {
81
e741295a 82 # 1. docker daemon running?
4807b1b8 83 test=`$docker_path info >/dev/null`
e741295a
MB
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
2d7d1501 90 # 2. running aufs
4807b1b8 91 test=`$docker_path info 2> /dev/null | grep 'Driver: aufs'`
a3e18d95 92 if [[ "$test" =~ "aufs" ]] ; then : ; else
9717d9cb
JA
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:"
2d7d1501 95 echo "https://meta.discourse.org/t/discourse-docker-installation-without-aufs/15639"
a3e18d95
S
96 fi
97
60668406
DP
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
a3e18d95 101
cf00fce0
S
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
60668406 104 # At least minimum version
cf00fce0 105 if compare_version "${docker_min_version}" "${test}"; then
60668406 106 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
a3e18d95
S
107 exit 1
108 fi
109
60668406
DP
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
e741295a 115 # 4. able to attach stderr / out / tty
4807b1b8 116 test=`$docker_path run -i --rm -a stdout -a stderr $image echo working`
a3e18d95
S
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
55d17203
RW
125if [ "$opt" != "--skip-prereqs" ] ; then
126 prereqs
127fi
a3e18d95 128
88126eba 129get_ssh_pub_key() {
9d28af0e
MB
130 if tty -s ; then
131 if [[ ! -e ~/.ssh/id_rsa.pub && ! -e ~/.ssh/id_dsa.pub ]] ; then
8aca5cb7
JA
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
4c01e298
S
134 if [[ $REPLY =~ ^[Nn]$ ]] ; then
135 echo
136 echo WARNING: You may not be able to log in to your container.
88126eba 137 echo
88126eba
S
138 else
139 echo
4c01e298
S
140 echo Generating SSH key
141 mkdir -p ~/.ssh && ssh-keygen -f ~/.ssh/id_rsa -t rsa -N ''
88126eba
S
142 echo
143 fi
144 fi
145 fi
146
9d28af0e 147 ssh_pub_key="$(cat ~/.ssh/id_rsa.pub 2>/dev/null || cat ~/.ssh/id_dsa.pub)"
88126eba
S
148}
149
150
52388b87
SS
151install_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
60f9f04c
S
169host_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
185RUBY
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
d90671f3 200set_volumes() {
4807b1b8 201 volumes=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \
d90671f3
SS
202 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
203}
204
7f77c274
SS
205set_template_info() {
206
4807b1b8 207 templates=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
7f77c274
SS
208 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
209
210
211 arrTemplates=(${templates// / })
212 config_data=$(cat $config_file)
213
214 input="hack: true"
215
216
217 for template in "${arrTemplates[@]}"
218 do
219 [ ! -z $template ] && {
220 input="$input _FILE_SEPERATOR_ $(cat $template)"
221 }
222 done
223
224 # we always want our config file last so it takes priority
225 input="$input _FILE_SEPERATOR_ $config_data"
226
227 read -r -d '' env_ruby << 'RUBY'
228 require 'yaml'
229
230 input=STDIN.readlines.join
3cb3d9c4
S
231 # default to UTF-8 for the dbs sake
232 env = {'LANG' => 'en_US.UTF-8'}
7f77c274
SS
233 input.split('_FILE_SEPERATOR_').each do |yml|
234 yml.strip!
235 begin
236 env.merge!(YAML.load(yml)['env'] || {})
f3824347 237 rescue Psych::SyntaxError => e
238 puts e
239 puts "*ERROR."
7f77c274
SS
240 rescue => e
241 puts yml
242 p e
243 end
244 end
4b563ee8 245 puts env.map{|k,v| "-e\n#{k}=#{v}" }.join("\n")
7f77c274
SS
246RUBY
247
4807b1b8 248 raw=`exec echo "$input" | $docker_path run --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
4b563ee8
SS
249
250 env=()
f3824347 251 ok=1
4b563ee8 252 while read i; do
f3824347 253 if [ "$i" == "*ERROR." ]; then
254 ok=0
255 elif [ -n "$i" ]; then
16f2d250
S
256 env[${#env[@]}]=$i
257 fi
4b563ee8
SS
258 done <<< "$raw"
259
f3824347 260 if [ "$ok" -ne 1 ]; then
261 echo "${env[@]}"
a3322f2a 262 echo "YAML syntax error. Please check your /var/docker/containers/*.yml config files."
f3824347 263 exit 1
264 fi
4b563ee8 265 echo "Calculated ENV: ${env[@]}"
7f77c274
SS
266}
267
52388b87
SS
268[ -z $docker_path ] && {
269 install_docker
270}
271
5f803fb4 272
c1005add 273[ $# -lt 2 ] && {
5f803fb4
SS
274 usage
275}
276
7e738616
S
277if [ ! -e $config_file ]
278 then
279 echo "Config file was not found, ensure $config_file exists"
71680b16
S
280 echo ""
281 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
7e738616
S
282 exit 1
283fi
284
337a89aa 285
e2ed1fb6
S
286docker_version=($($docker_path --version))
287docker_version=${test[2]//,/}
288
289if compare_version "1.2.0" "$docker_version"; then
290 echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
291 restart_policy=""
292else
e0fd1f5b 293 restart_policy=${restart_policy:---restart=always}
e2ed1fb6
S
294fi
295
296
7936ebaa
MB
297run_mailtest(){
298 if [ ! -e $config_file ]; then
299 echo "Config does not exist: $config_file" >&2
300 exit 1
301 fi
302 exec scripts/mailtest $config_file
303}
304
337a89aa
S
305run_stop(){
306 if [ ! -e $cidfile ]
307 then
308 echo "No cid found"
309 exit 1
310 else
4807b1b8 311 $docker_path stop -t 10 `cat $cidfile`
337a89aa
S
312 fi
313}
314
315run_start(){
316
60f9f04c
S
317 host_run
318
337a89aa
S
319 if [ ! -e $cidfile ]
320 then
321 echo "No cid found, creating a new container"
4807b1b8 322 ports=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \
337a89aa
S
323 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"`
324
325 set_template_info
326 set_volumes
327
4807b1b8 328 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep "$config$" | awk '{ print $1 }'`
337a89aa
S
329 if [ ! -z $existing ]
330 then
331 echo "Found an existing container by its name, recovering cidfile, please rerun"
332 echo $existing > $cidfile
333 exit 1
334 fi
335
e0fd1f5b
TB
336 $docker_path run $attach_on_run $restart_policy "${env[@]}" -h "`hostname`-$config" -e DOCKER_HOST_IP=$docker_ip --name $config -t --cidfile $cidfile $ports \
337 $volumes $local_discourse/$config /sbin/runit
337a89aa
S
338
339 exit 0
340 else
341 cid=`cat $cidfile`
342
343 if [ -z $cid ]
344 then
345 echo "Detected empty cid file, deleting, please re-run"
346 rm $cidfile
347 exit 1
348 fi
349
4807b1b8 350 found=`$docker_path ps -q -a --no-trunc | grep $cid`
337a89aa
S
351 if [ -z $found ]
352 then
353 echo "Invalid cid file, deleting, please re-run"
354 rm $cidfile
355 exit 1
356 fi
357
358 echo "cid found, ensuring container is started"
e0fd1f5b 359 $docker_path start $attach_on_start `cat $cidfile`
337a89aa
S
360 exit 0
361 fi
362
363}
364
680dd4ea 365run_bootstrap(){
60f9f04c
S
366
367 host_run
368
680dd4ea 369 get_ssh_pub_key
88126eba 370
680dd4ea
S
371 # Is the image available?
372 # If not, pull it here so the user is aware what's happening.
4807b1b8 373 $docker_path history $image >/dev/null 2>&1 || $docker_path pull $image
88126eba 374
680dd4ea 375 set_template_info
93149421 376
4807b1b8 377 base_image=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
680dd4ea 378 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
93149421 379
4807b1b8 380 update_pups=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
680dd4ea 381 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
b9c7b50e 382
680dd4ea
S
383 if [[ ! X"" = X"$base_image" ]]; then
384 image=$base_image
385 fi
b9c7b50e 386
680dd4ea 387 set_volumes
b9c7b50e 388
680dd4ea 389 rm -f $cidbootstrap
d90671f3 390
680dd4ea
S
391 run_command="cd /pups &&"
392 if [[ ! "false" = $update_pups ]]; then
393 run_command="$run_command git pull &&"
394 fi
395 run_command="$run_command /pups/bin/pups --stdin"
2162f1d4 396
680dd4ea 397 echo $run_command
b9c7b50e 398
680dd4ea 399 env=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
c4498636 400
4807b1b8 401 (exec echo "$input" | $docker_path run "${env[@]}" -e DOCKER_HOST_IP=$docker_ip --cidfile $cidbootstrap -i -a stdin -a stdout -a stderr $volumes $image \
680dd4ea 402 /bin/bash -c "$run_command") \
4807b1b8 403 || ($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
88126eba 404
680dd4ea 405 [ ! -e $cidbootstrap ] && echo "FAILED TO BOOTSTRAP" && exit 1
9fb5f2d3 406
680dd4ea 407 sleep 5
2162f1d4 408
4807b1b8
MB
409 $docker_path commit `cat $cidbootstrap` $local_discourse/$config || echo 'FAILED TO COMMIT'
410 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
680dd4ea 411}
9fb5f2d3 412
680dd4ea
S
413case "$command" in
414 bootstrap)
680dd4ea 415 run_bootstrap
2dd2e330 416 echo "Successfully bootstrapped, to startup use ./launcher start $config"
4b3aebe1 417 exit 0
5f803fb4 418 ;;
1acce9e4 419
7936ebaa
MB
420 mailtest)
421 run_mailtest
422 exit 0
423 ;;
424
2fc6ff36
S
425 enter)
426
427 if [ ! -e $cidfile ]
428 then
429 echo "No cid found"
430 exit 1
431 fi
432
433 if [ ! $UID -eq 0 ] ;
434 then
435 echo "enter command must run as root, will attempt to sudo"
436 echo
437 fi
438
439 if [ ! -e bin/nsenter ]
440 then
441 echo "Downloading nsenter"
442 $docker_path pull samsaffron/nsenter
443 ($docker_path run --rm samsaffron/nsenter cat /nsenter > bin/nsenter1) || exit 1
444 cp bin/nsenter1 bin/nsenter
445 chmod +x bin/nsenter
446 fi
447
448 PID=$($docker_path inspect --format {{.State.Pid}} `cat $cidfile`)
449 SHELL=/bin/bash sudo -E bin/nsenter --target $PID --mount --uts --ipc --net --pid
450
451 exit 0;
452 ;;
453
5f803fb4
SS
454 ssh)
455 if [ ! -e $cidfile ]
456 then
457 echo "No cid found"
458 exit 1
459 else
460 cid="`cat $cidfile`"
4807b1b8 461 address="`$docker_path port $cid 22`"
5f803fb4 462 split=(${address//:/ })
38c09f5e 463 exec ssh -o StrictHostKeyChecking=no root@${split[0]} -p ${split[1]}
5f803fb4
SS
464 fi
465 ;;
7e738616 466
5f803fb4 467 stop)
337a89aa
S
468 run_stop
469 exit 0
5f803fb4 470 ;;
7e738616 471
5f803fb4 472 logs)
7e738616 473
5f803fb4
SS
474 if [ ! -e $cidfile ]
475 then
476 echo "No cid found"
477 exit 1
478 else
4807b1b8 479 $docker_path logs `cat $cidfile`
5f803fb4
SS
480 exit 0
481 fi
482 ;;
7e738616 483
337a89aa
S
484 restart)
485 run_stop
486 run_start
487 exit 0
488 ;;
80c11be3 489
337a89aa
S
490 start)
491 run_start
492 exit 0
5f803fb4 493 ;;
7e738616 494
680dd4ea 495 rebuild)
4b6456ef
MB
496 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
497 echo "Updating discourse docker"
498 git pull || (echo 'failed to update' && exit 1)
499 fi
680dd4ea
S
500 if [ -e $cidfile ]
501 then
502 echo "Stopping old container"
4807b1b8 503 $docker_path stop -t 10 `cat $cidfile`
680dd4ea
S
504 fi
505
506 run_bootstrap
507
508 if [ -e $cidfile ]
509 then
4807b1b8 510 $docker_path rm `cat $cidfile` && rm $cidfile
680dd4ea
S
511 fi
512
513 run_start
514 exit 0
515 ;;
516
7e738616 517
5f803fb4
SS
518 destroy)
519 if [ -e $cidfile ]
520 then
521 echo "destroying container $cidfile"
4807b1b8
MB
522 $docker_path stop -t 10 `cat $cidfile`
523 $docker_path rm `cat $cidfile` && rm $cidfile
5f803fb4
SS
524 exit 0
525 else
526 echo "nothing to destroy cidfile does not exist"
527 exit 1
528 fi
529 ;;
530esac
7e738616 531
5f803fb4 532usage