Merge pull request #83 from Supermathie/v1run
[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='0.9.1'
10 docker_rec_version='0.11.1'
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.0
17 docker_path=`which docker.io || which docker`
18
19 docker_ip=`/sbin/ifconfig | \
20 grep -B1 "inet addr" | \
21 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
22 grep docker0 | \
23 awk -F: '{ print $3 }';`
24
25
26 usage () {
27 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
28 echo "Commands:"
29 echo " start: Start/initialize a container"
30 echo " stop: Stop a running container"
31 echo " restart: Restart a container"
32 echo " destroy: Stop and remove a container"
33 echo " enter: Use nsenter to enter a container"
34 echo " ssh: Start a bash shell in a running container"
35 echo " logs: Docker logs for container"
36 echo " mailtest: Test the mail settings in a container"
37 echo " bootstrap: Bootstrap a container for the config based on a template"
38 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
39 echo
40 echo "Options:"
41 echo " --skip-prereqs Don't check prerequisites"
42 exit 1
43 }
44
45 compare_version() {
46 declare -a ver_a
47 declare -a ver_b
48 IFS=. read -a ver_a <<< "$1"
49 IFS=. read -a ver_b <<< "$2"
50
51 while [[ -n $ver_a ]]; do
52 if (( ver_a > ver_b )); then
53 return 0
54 elif (( ver_b > ver_a )); then
55 return 1
56 else
57 unset ver_a[0]
58 ver_a=("${ver_a[@]}")
59 unset ver_b[0]
60 ver_b=("${ver_b[@]}")
61 fi
62 done
63 return 1 # They are equal
64 }
65
66 prereqs() {
67
68 # 1. docker daemon running?
69 test=`$docker_path info >/dev/null`
70
71 if [[ $? -ne 0 ]] ; then
72 echo "Cannot connect to the docker daemon - verify it is running and you have access"
73 exit 1
74 fi
75
76 # 2. running aufs
77 test=`$docker_path info 2> /dev/null | grep 'Driver: aufs'`
78 if [[ "$test" =~ "aufs" ]] ; then : ; else
79 echo "Your Docker installation is not using aufs, in the past we have had issues with it"
80 echo "If you are unable to bootstrap your image (or stop it) please report the issue at:"
81 echo "https://meta.discourse.org/t/discourse-docker-installation-without-aufs/15639"
82 fi
83
84 # 3. running recommended docker version
85 test=($($docker_path --version)) # Get docker version string
86 test=${test[2]//,/} # Get version alone and strip comma if exists
87
88 [[ "$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
89
90 # At least minimum version
91 if compare_version "${docker_min_version}" "${test}"; then
92 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
93 exit 1
94 fi
95
96 # Recommend best version
97 if compare_version "${docker_rec_version}" "${test}"; then
98 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
99 fi
100
101 # 4. able to attach stderr / out / tty
102 test=`$docker_path run -i --rm -a stdout -a stderr $image echo working`
103 if [[ "$test" =~ "working" ]] ; then : ; else
104 echo "Your Docker installation is not working correctly"
105 echo
106 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
107 exit 1
108 fi
109 }
110
111 if [ "$opt" != "--skip-prereqs" ] ; then
112 prereqs
113 fi
114
115 get_ssh_pub_key() {
116 if tty -s ; then
117 if [[ ! -e ~/.ssh/id_rsa.pub && ! -e ~/.ssh/id_dsa.pub ]] ; then
118 echo "This user has no SSH key, but a SSH key is required to access the Discourse Docker container."
119 read -p "Generate a SSH key? (Y/n) " -n 1 -r
120 if [[ $REPLY =~ ^[Nn]$ ]] ; then
121 echo
122 echo WARNING: You may not be able to log in to your container.
123 echo
124 else
125 echo
126 echo Generating SSH key
127 mkdir -p ~/.ssh && ssh-keygen -f ~/.ssh/id_rsa -t rsa -N ''
128 echo
129 fi
130 fi
131 fi
132
133 ssh_pub_key="$(cat ~/.ssh/id_rsa.pub 2>/dev/null || cat ~/.ssh/id_dsa.pub)"
134 }
135
136
137 install_docker() {
138
139 echo "Docker is not installed, make sure you are running on the 3.8 kernel"
140 echo "The best supported Docker release is Ubuntu 12.04.03 for it run the following"
141 echo
142 echo "sudo apt-get update"
143 echo "sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring"
144 echo "sudo reboot"
145 echo
146
147 echo "sudo sh -c \"wget -qO- https://get.docker.io/gpg | apt-key add -\""
148 echo "sudo sh -c \"echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list\""
149 echo "sudo apt-get update"
150 echo "sudo apt-get install lxc-docker"
151
152 exit 1
153 }
154
155 set_volumes() {
156 volumes=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \
157 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
158 }
159
160 set_template_info() {
161
162 templates=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
163 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
164
165
166 arrTemplates=(${templates// / })
167 config_data=$(cat $config_file)
168
169 input="hack: true"
170
171
172 for template in "${arrTemplates[@]}"
173 do
174 [ ! -z $template ] && {
175 input="$input _FILE_SEPERATOR_ $(cat $template)"
176 }
177 done
178
179 # we always want our config file last so it takes priority
180 input="$input _FILE_SEPERATOR_ $config_data"
181
182 read -r -d '' env_ruby << 'RUBY'
183 require 'yaml'
184
185 input=STDIN.readlines.join
186 # default to UTF-8 for the dbs sake
187 env = {'LANG' => 'en_US.UTF-8'}
188 input.split('_FILE_SEPERATOR_').each do |yml|
189 yml.strip!
190 begin
191 env.merge!(YAML.load(yml)['env'] || {})
192 rescue Psych::SyntaxError => e
193 puts e
194 puts "*ERROR."
195 rescue => e
196 puts yml
197 p e
198 end
199 end
200 puts env.map{|k,v| "-e\n#{k}=#{v}" }.join("\n")
201 RUBY
202
203 raw=`exec echo "$input" | $docker_path run --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
204
205 env=()
206 ok=1
207 while read i; do
208 if [ "$i" == "*ERROR." ]; then
209 ok=0
210 elif [ -n "$i" ]; then
211 env[${#env[@]}]=$i
212 fi
213 done <<< "$raw"
214
215 if [ "$ok" -ne 1 ]; then
216 echo "${env[@]}"
217 echo "YAML syntax error. Please check your /var/docker/containers/*.yml config files."
218 exit 1
219 fi
220 echo "Calculated ENV: ${env[@]}"
221 }
222
223 [ -z $docker_path ] && {
224 install_docker
225 }
226
227
228 [ $# -lt 2 ] && {
229 usage
230 }
231
232 if [ ! -e $config_file ]
233 then
234 echo "Config file was not found, ensure $config_file exists"
235 echo ""
236 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
237 exit 1
238 fi
239
240
241 run_mailtest(){
242 if [ ! -e $config_file ]; then
243 echo "Config does not exist: $config_file" >&2
244 exit 1
245 fi
246 exec scripts/mailtest $config_file
247 }
248
249 run_stop(){
250 if [ ! -e $cidfile ]
251 then
252 echo "No cid found"
253 exit 1
254 else
255 $docker_path stop -t 10 `cat $cidfile`
256 fi
257 }
258
259 run_start(){
260
261 if [ ! -e $cidfile ]
262 then
263 echo "No cid found, creating a new container"
264 ports=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \
265 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"`
266
267 set_template_info
268 set_volumes
269
270 existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep "$config$" | awk '{ print $1 }'`
271 if [ ! -z $existing ]
272 then
273 echo "Found an existing container by its name, recovering cidfile, please rerun"
274 echo $existing > $cidfile
275 exit 1
276 fi
277
278 $docker_path run "${env[@]}" -h "`hostname`-$config" -e DOCKER_HOST_IP=$docker_ip --name $config -t --cidfile $cidfile $ports \
279 -d $volumes $local_discourse/$config /sbin/runit
280
281 exit 0
282 else
283 cid=`cat $cidfile`
284
285 if [ -z $cid ]
286 then
287 echo "Detected empty cid file, deleting, please re-run"
288 rm $cidfile
289 exit 1
290 fi
291
292 found=`$docker_path ps -q -a --no-trunc | grep $cid`
293 if [ -z $found ]
294 then
295 echo "Invalid cid file, deleting, please re-run"
296 rm $cidfile
297 exit 1
298 fi
299
300 echo "cid found, ensuring container is started"
301 $docker_path start `cat $cidfile`
302 exit 0
303 fi
304
305 }
306
307 run_bootstrap(){
308 get_ssh_pub_key
309
310 # Is the image available?
311 # If not, pull it here so the user is aware what's happening.
312 $docker_path history $image >/dev/null 2>&1 || $docker_path pull $image
313
314 set_template_info
315
316 base_image=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
317 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
318
319 update_pups=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \
320 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
321
322 if [[ ! X"" = X"$base_image" ]]; then
323 image=$base_image
324 fi
325
326 set_volumes
327
328 rm -f $cidbootstrap
329
330 run_command="cd /pups &&"
331 if [[ ! "false" = $update_pups ]]; then
332 run_command="$run_command git pull &&"
333 fi
334 run_command="$run_command /pups/bin/pups --stdin"
335
336 echo $run_command
337
338 env=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
339
340 (exec echo "$input" | $docker_path run "${env[@]}" -e DOCKER_HOST_IP=$docker_ip --cidfile $cidbootstrap -i -a stdin -a stdout -a stderr $volumes $image \
341 /bin/bash -c "$run_command") \
342 || ($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
343
344 [ ! -e $cidbootstrap ] && echo "FAILED TO BOOTSTRAP" && exit 1
345
346 sleep 5
347
348 $docker_path commit `cat $cidbootstrap` $local_discourse/$config || echo 'FAILED TO COMMIT'
349 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
350 }
351
352 case "$command" in
353 bootstrap)
354 run_bootstrap
355 echo "Successfully bootstrapped, to startup use ./launcher start $config"
356 exit 0
357 ;;
358
359 mailtest)
360 run_mailtest
361 exit 0
362 ;;
363
364 enter)
365
366 if [ ! -e $cidfile ]
367 then
368 echo "No cid found"
369 exit 1
370 fi
371
372 if [ ! $UID -eq 0 ] ;
373 then
374 echo "enter command must run as root, will attempt to sudo"
375 echo
376 fi
377
378 if [ ! -e bin/nsenter ]
379 then
380 echo "Downloading nsenter"
381 $docker_path pull samsaffron/nsenter
382 ($docker_path run --rm samsaffron/nsenter cat /nsenter > bin/nsenter1) || exit 1
383 cp bin/nsenter1 bin/nsenter
384 chmod +x bin/nsenter
385 fi
386
387 PID=$($docker_path inspect --format {{.State.Pid}} `cat $cidfile`)
388 SHELL=/bin/bash sudo -E bin/nsenter --target $PID --mount --uts --ipc --net --pid
389
390 exit 0;
391 ;;
392
393 ssh)
394 if [ ! -e $cidfile ]
395 then
396 echo "No cid found"
397 exit 1
398 else
399 cid="`cat $cidfile`"
400 address="`$docker_path port $cid 22`"
401 split=(${address//:/ })
402 exec ssh -o StrictHostKeyChecking=no root@${split[0]} -p ${split[1]}
403 fi
404 ;;
405
406 stop)
407 run_stop
408 exit 0
409 ;;
410
411 logs)
412
413 if [ ! -e $cidfile ]
414 then
415 echo "No cid found"
416 exit 1
417 else
418 $docker_path logs `cat $cidfile`
419 exit 0
420 fi
421 ;;
422
423 restart)
424 run_stop
425 run_start
426 exit 0
427 ;;
428
429 start)
430 run_start
431 exit 0
432 ;;
433
434 rebuild)
435 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
436 echo "Updating discourse docker"
437 git pull || (echo 'failed to update' && exit 1)
438 fi
439 if [ -e $cidfile ]
440 then
441 echo "Stopping old container"
442 $docker_path stop -t 10 `cat $cidfile`
443 fi
444
445 run_bootstrap
446
447 if [ -e $cidfile ]
448 then
449 $docker_path rm `cat $cidfile` && rm $cidfile
450 fi
451
452 run_start
453 exit 0
454 ;;
455
456
457 destroy)
458 if [ -e $cidfile ]
459 then
460 echo "destroying container $cidfile"
461 $docker_path stop -t 10 `cat $cidfile`
462 $docker_path rm `cat $cidfile` && rm $cidfile
463 exit 0
464 else
465 echo "nothing to destroy cidfile does not exist"
466 exit 1
467 fi
468 ;;
469 esac
470
471 usage