7 # Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
8 re
='[A-Z/ !@#$%^&*()+~`=]'
9 if [[ $config =~
$re ]];
12 echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
19 docker_min_version
='1.2.0'
20 docker_rec_version
='1.2.0'
22 config_file
=containers
/"$config".yml
23 cidbootstrap
=cids
/"$config"_bootstrap.cid
24 local_discourse
=local_discourse
25 image
=samsaffron
/discourse
:1.0.12
26 docker_path
=`which docker.io || which docker`
28 if [ "${SUPERVISED}" = "true" ]; then
29 restart_policy
="--restart=no"
31 attach_on_run
="-a stdout -a stderr"
36 if [ -n "$DOCKER_HOST" ]; then
37 docker_ip
=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
38 elif [ -x "$(which ip 2>/dev/null)" ]; then
39 docker_ip
=`ip addr show docker0 | \
41 awk '{ split($2,a,"/"); print a[1] }';`
43 docker_ip
=`ifconfig | \
44 grep -B1 "inet addr" | \
45 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
47 awk -F: '{ print $3 }';`
52 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
54 echo " start: Start/initialize a container"
55 echo " stop: Stop a running container"
56 echo " restart: Restart a container"
57 echo " destroy: Stop and remove a container"
58 echo " enter: Use nsenter to enter a container"
59 echo " ssh: Start a bash shell in a running container"
60 echo " logs: Docker logs for container"
61 echo " bootstrap: Bootstrap a container for the config based on a template"
62 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
63 echo " cleanup: Remove all containers that have stopped for > 24 hours"
66 echo " --skip-prereqs Don't check prerequisites"
67 echo " --docker-args Extra arguments to pass when running docker"
74 IFS
=.
read -a ver_a
<<< "$1"
75 IFS
=.
read -a ver_b
<<< "$2"
77 while [[ -n $ver_a ]]; do
78 if (( ver_a
> ver_b
)); then
80 elif (( ver_b
> ver_a
)); then
89 return 1 # They are equal
94 # 1. docker daemon running?
95 # we send stderr to /dev/null cause we don't care about warnings,
96 # it usually complains about swap which does not matter
97 test=`$docker_path info 2> /dev/null`
99 if [[ $?
-ne 0 ]] ; then
100 echo "Cannot connect to the docker daemon - verify it is running and you have access"
104 # 2. running aufs or btrfs
105 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
106 if [[ "$test" =~
[aufs|btrfs|zfs|overlay
] ]] ; then : ; else
107 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
108 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
109 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
111 echo "If you wish to continue anyway using your existing unsupported filesystem"
112 echo "read the source code of launcher and figure out how to bypass this."
116 # 3. running recommended docker version
117 test=($
($docker_path --version)) # Get docker version string
118 test=${test[2]//,/} # Get version alone and strip comma if exists
120 [[ "$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
122 # At least minimum version
123 if compare_version
"${docker_min_version}" "${test}"; then
124 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
128 # Recommend best version
129 if compare_version
"${docker_rec_version}" "${test}"; then
130 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
133 # 4. discourse docker image is downloaded
134 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
136 if [ -z "$test" ]; then
138 echo "WARNING: We are about to start downloading the Discourse base image"
139 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
141 echo "Please be patient"
146 # 5. able to attach stderr / out / tty
147 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
148 if [[ "$test" =~
"working" ]] ; then : ; else
149 echo "Your Docker installation is not working correctly"
151 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
156 if [ "$opt" != "--skip-prereqs" ] ; then
160 if [ "$opt" == "--docker-args" ] ; then
167 local ${ssh_key_locations}
169 ~
/.ssh
/id_ed25519.pub
173 ~core
/.ssh
/authorized_keys
177 for keyfile
in "${ssh_key_locations[@]}"; do
178 if [[ -e ${keyfile} ]] ; then
179 ssh_pub_key
="$(cat ${keyfile})"
190 echo "Docker is not installed, make sure you are running on the 3.8 kernel"
191 echo "The best supported Docker release is Ubuntu 12.04.03 for it run the following"
193 echo "sudo apt-get update"
194 echo "sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring"
198 echo "sudo sh -c \"wget -qO- https://get.docker.io/gpg | apt-key add -\""
199 echo "sudo sh -c \"echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list\""
200 echo "sudo apt-get update"
201 echo "sudo apt-get install lxc-docker"
207 read -r -d '' env_ruby
<< 'RUBY'
210 input
= STDIN.readlines.
join
211 yaml
= YAML.load
(input
)
213 if host_run
= yaml
['host_run']
214 params
= yaml
['params'] ||
{}
215 host_run.each
do |run|
217 run
= run.gsub
("$#{k}", v
)
219 STDOUT.
write "#{run}--SEP--"
224 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
226 while [ "$host_run" ] ; do
227 iter
=${host_run%%--SEP--*}
229 echo "Host run: $iter"
232 host_run
="${host_run#*--SEP--}"
238 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
239 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
243 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
244 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
247 set_template_info
() {
249 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
250 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
253 arrTemplates
=(${templates// / })
254 config_data
=$
(cat $config_file)
259 for template
in "${arrTemplates[@]}"
261 [ ! -z $template ] && {
262 input
="$input _FILE_SEPERATOR_ $(cat $template)"
266 # we always want our config file last so it takes priority
267 input
="$input _FILE_SEPERATOR_ $config_data"
269 read -r -d '' env_ruby
<< 'RUBY'
272 input
=STDIN.readlines.
join
273 # default to UTF-8 for the dbs sake
274 env
= {'LANG' => 'en_US.UTF-8'}
275 input.
split('_FILE_SEPERATOR_').each
do |yml|
278 env.merge
!(YAML.load
(yml
)['env'] ||
{})
279 rescue Psych
::SyntaxError
=> e
287 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
290 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
295 if [ "$i" == "*ERROR." ]; then
297 elif [ -n "$i" ]; then
302 if [ "$ok" -ne 1 ]; then
304 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
309 [ -z $docker_path ] && {
313 [ $command == "cleanup" ] && {
315 echo "The following command will"
316 echo "- Delete all docker images for old containers"
317 echo "- Delete all stopped and orphan containers"
319 read -p "Are you sure (Y/n): " -n 1 -r && echo
320 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
322 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
324 echo "Starting Cleanup"
326 if [[ ! -z `docker ps -aq` ]]; then
327 docker inspect
-f '{{.Id}},{{.State.Running}},{{.State.FinishedAt}}' $
(docker ps
-qa) | \
328 awk -F, 'BEGIN { TIME=strftime("%FT%T.000000000Z",systime()-60*60*24); } $2=="false" && $3 < TIME {print $1;}' | \
329 xargs --no-run-if-empty docker
rm >/dev
/null
2>/dev
/null
332 docker rmi
`docker images -a | grep '<none>' | awk '{print $3}'` 2> /dev
/null
334 let freed
=$space-$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
337 echo $
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
340 output
="$freed" |
awk '{sum=$1;hum[1024**3]="GB"; hum[1024**2]="MB"; hum[1024]="KB"; for (x=1024**3;x>=1024; x/=1024){ if (sum>=x) { printf "%.2f %s\n",sum/x,hum[x];break }}}'
341 [ -z "$output" ] && {
342 [[ $freed > 0 ]] && { echo "./launcher cleanup cleared up $freed of disk space."; } ||
{ echo "./launcher cleanup has finished, no files were removed."; }
343 } ||
{ echo "./launcher cleanup cleared up $freed of disk space."; }
354 if [ ! -e $config_file ]
356 echo "Config file was not found, ensure $config_file exists"
358 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
363 docker_version
=($
($docker_path --version))
364 docker_version
=${test[2]//,/}
366 if compare_version
"1.2.0" "$docker_version"; then
367 echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
370 restart_policy
=${restart_policy:---restart=always}
373 set_existing_container
(){
374 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
379 set_existing_container
381 if [ ! -z $existing ]
385 $docker_path stop
-t 10 $config
388 echo "$config was not started !"
395 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
397 if [ ! -z $existing ]
399 echo "Nothing to do, your container has already started!"
403 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
404 if [ ! -z $existing ]
406 echo "starting up existing container"
409 $docker_path start
$config
415 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
416 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"`
425 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname-$config" \
426 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $local_discourse/$config /sbin
/boot
439 # Is the image available?
440 # If not, pull it here so the user is aware what's happening.
441 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
445 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
446 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
448 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
449 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
451 if [[ ! X
"" = X
"$base_image" ]]; then
460 run_command
="cd /pups &&"
461 if [[ ! "false" = $update_pups ]]; then
462 run_command
="$run_command git pull &&"
464 run_command
="$run_command /pups/bin/pups --stdin"
468 env
=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
470 (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 \
471 /bin
/bash
-c "$run_command") \
472 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
474 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
478 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
479 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
485 echo "Successfully bootstrapped, to startup use ./launcher start $config"
490 exec $docker_path exec -it $config /bin
/bash
494 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
496 if [[ ! -z $existing ]]; then
497 address
="`$docker_path port $config 22`"
498 split=(${address//:/ })
499 exec ssh -o StrictHostKeyChecking
=no root@
${split[0]} -p ${split[1]}
501 echo "$config is not running!"
513 $docker_path logs
$config
529 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
530 echo "Ensuring discourse docker is up to date"
534 LOCAL
=$
(git rev-parse @
)
535 REMOTE
=$
(git rev-parse @
{u
})
536 BASE
=$
(git merge-base @ @
{u
})
538 if [ $LOCAL = $REMOTE ]; then
539 echo "Discourse Docker is up-to-date"
541 elif [ $LOCAL = $BASE ]; then
542 echo "Updating Discourse Docker"
543 git pull ||
(echo 'failed to update' && exit 1)
546 elif [ $REMOTE = $BASE ]; then
547 echo "Your version of Discourse Docker is ahead of origin"
550 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
555 set_existing_container
557 if [ ! -z $existing ]
559 echo "Stopping old container"
562 $docker_path stop
-t 10 $config
568 if [ ! -z $existing ]
570 echo "Removing old container"
573 $docker_path rm $config
583 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)