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.6.0'
20 docker_rec_version
='1.6.0'
22 config_file
=containers
/"$config".yml
23 cidbootstrap
=cids
/"$config"_bootstrap.cid
24 local_discourse
=local_discourse
25 image
=discourse
/discourse
:1.0.16
26 docker_path
=`which docker.io || which docker`
29 if [ "${SUPERVISED}" = "true" ]; then
30 restart_policy
="--restart=no"
32 attach_on_run
="-a stdout -a stderr"
37 if [ -n "$DOCKER_HOST" ]; then
38 docker_ip
=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
39 elif [ -x "$(which ip 2>/dev/null)" ]; then
40 docker_ip
=`ip addr show docker0 | \
42 awk '{ split($2,a,"/"); print a[1] }';`
44 docker_ip
=`ifconfig | \
45 grep -B1 "inet addr" | \
46 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
48 awk -F: '{ print $3 }';`
53 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
55 echo " start: Start/initialize a container"
56 echo " stop: Stop a running container"
57 echo " restart: Restart a container"
58 echo " destroy: Stop and remove a container"
59 echo " enter: Use nsenter to enter a container"
60 echo " ssh: Start a bash shell in a running container"
61 echo " logs: Docker logs for container"
62 echo " bootstrap: Bootstrap a container for the config based on a template"
63 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
64 echo " cleanup: Remove all containers that have stopped for > 24 hours"
67 echo " --skip-prereqs Don't check prerequisites or resource requirements"
68 echo " --docker-args Extra arguments to pass when running docker"
75 IFS
=.
read -a ver_a
<<< "$1"
76 IFS
=.
read -a ver_b
<<< "$2"
78 while [[ -n $ver_a ]]; do
79 if (( ver_a
> ver_b
)); then
81 elif (( ver_b
> ver_a
)); then
90 return 1 # They are equal
95 # 1. docker daemon running?
96 # we send stderr to /dev/null cause we don't care about warnings,
97 # it usually complains about swap which does not matter
98 test=`$docker_path info 2> /dev/null`
100 if [[ $?
-ne 0 ]] ; then
101 echo "Cannot connect to the docker daemon - verify it is running and you have access"
105 # 2. running aufs or btrfs
106 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
107 if [[ "$test" =~
[aufs|btrfs|zfs|overlay
] ]] ; then : ; else
108 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
109 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
110 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
112 echo "If you wish to continue anyway using your existing unsupported filesystem"
113 echo "read the source code of launcher and figure out how to bypass this."
117 # 3. running recommended docker version
118 test=($
($docker_path --version)) # Get docker version string
119 test=${test[2]//,/} # Get version alone and strip comma if exists
121 [[ "$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
123 # At least minimum version
124 if compare_version
"${docker_min_version}" "${test}"; then
125 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
129 # Recommend best version
130 if compare_version
"${docker_rec_version}" "${test}"; then
131 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
134 # 4. discourse docker image is downloaded
135 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
137 if [ -z "$test" ]; then
139 echo "WARNING: We are about to start downloading the Discourse base image"
140 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
142 echo "Please be patient"
147 # 5. able to attach stderr / out / tty
148 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
149 if [[ "$test" =~
"working" ]] ; then : ; else
150 echo "Your Docker installation is not working correctly"
152 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
161 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
162 if [ "$avail_mem" -lt 900 ]; then
163 resources
="insufficient"
164 echo "WARNING: You do not appear to have sufficient memory to run Discourse."
166 echo "Your system may not work properly, or future upgrades of Discourse may"
167 echo "not complete successfully."
169 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#create-new-cloud-server"
170 elif [ "$avail_mem" -lt 1800 ]; then
171 total_swap
="$(LANG=C free -m | grep ^Swap: | awk '{print $2}')"
172 if [ "$total_swap" -lt 1000 ]; then
173 resources
="insufficient"
174 echo "WARNING: You must have at least 1GB of swap when running with less"
175 echo "than 2GB of RAM."
177 echo "Your system may not work properly, or future upgrades of Discourse may"
178 echo "not complete successfully."
180 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#set-up-swap-if-needed"
185 free_disk
="$(df /var | tail -n 1 | awk '{print $4}')"
186 if [ "$free_disk" -lt 5000 ]; then
187 resources
="insufficient"
188 echo "WARNING: You must have at least 5GB of *free* disk space to run Discourse."
190 echo "Insufficient disk space may result in problems running your site, and may"
191 echo "not even allow Discourse installation to complete successfully."
193 echo "Please free up some space, or expand your disk, before continuing."
197 if [ -t 0 ] && [ "$resources" != "ok" ]; then
199 read -p "Press ENTER to continue, or Ctrl-C to exit and give your system more resources"
203 if [ "$opt" != "--skip-prereqs" ] ; then
207 if [ "$opt" == "--docker-args" ] ; then
214 local ${ssh_key_locations}
216 ~
/.ssh
/id_ed25519.pub
220 ~core
/.ssh
/authorized_keys
224 for keyfile
in "${ssh_key_locations[@]}"; do
225 if [[ -e ${keyfile} ]] ; then
226 ssh_pub_key
="$(cat ${keyfile})"
237 echo "Docker is not installed, you will need to install Docker in order to run Discourse"
238 echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system"
240 echo "If you are running Ubuntu Trusty or later, you can try the following:"
243 echo "sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D"
244 echo "sudo sh -c \"echo deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -sc) main > /etc/apt/sources.list.d/docker.list\""
245 echo "sudo apt-get update"
246 echo "sudo apt-get install docker-engine"
252 read -r -d '' env_ruby
<< 'RUBY'
255 input
= STDIN.readlines.
join
256 yaml
= YAML.load
(input
)
258 if host_run
= yaml
['host_run']
259 params
= yaml
['params'] ||
{}
260 host_run.each
do |run|
262 run
= run.gsub
("$#{k}", v
)
264 STDOUT.
write "#{run}--SEP--"
269 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
271 while [ "$host_run" ] ; do
272 iter
=${host_run%%--SEP--*}
274 echo "Host run: $iter"
277 host_run
="${host_run#*--SEP--}"
283 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
284 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
288 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
289 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
292 set_template_info
() {
294 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
295 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
298 arrTemplates
=(${templates// / })
299 config_data
=$
(cat $config_file)
304 for template
in "${arrTemplates[@]}"
306 [ ! -z $template ] && {
307 input
="$input _FILE_SEPERATOR_ $(cat $template)"
311 # we always want our config file last so it takes priority
312 input
="$input _FILE_SEPERATOR_ $config_data"
314 read -r -d '' env_ruby
<< 'RUBY'
317 input
=STDIN.readlines.
join
318 # default to UTF-8 for the dbs sake
319 env
= {'LANG' => 'en_US.UTF-8'}
320 input.
split('_FILE_SEPERATOR_').each
do |yml|
323 env.merge
!(YAML.load
(yml
)['env'] ||
{})
324 rescue Psych
::SyntaxError
=> e
332 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
335 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
340 if [ "$i" == "*ERROR." ]; then
342 elif [ -n "$i" ]; then
347 if [ "$ok" -ne 1 ]; then
349 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
354 [ -z $docker_path ] && {
358 [ "$command" == "cleanup" ] && {
360 echo "The following command will"
361 echo "- Delete all docker images for old containers"
362 echo "- Delete all stopped and orphan containers"
364 read -p "Are you sure (Y/n): " -n 1 -r && echo
365 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
367 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
368 echo "Starting Cleanup (bytes free $space)"
370 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
372 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
373 echo "Finished Cleanup (bytes free $space)"
385 if [ ! -e $config_file ]
387 echo "Config file was not found, ensure $config_file exists"
389 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
394 docker_version
=($
($docker_path --version))
395 docker_version
=${test[2]//,/}
397 if compare_version
"1.2.0" "$docker_version"; then
398 echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
401 restart_policy
=${restart_policy:---restart=always}
404 set_existing_container
(){
405 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
410 set_existing_container
412 if [ ! -z $existing ]
416 $docker_path stop
-t 10 $config
419 echo "$config was not started !"
425 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
426 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
428 if [ -z "$run_image" ]; then
429 run_image
="$local_discourse/$config"
434 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
435 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
437 if [ -z "$boot_command" ]; then
439 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
440 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
442 if [ -z "$no_boot_command" ]; then
443 boot_command
="/sbin/boot"
450 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
452 if [ ! -z $existing ]
454 echo "Nothing to do, your container has already started!"
458 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
459 if [ ! -z $existing ]
461 echo "starting up existing container"
464 $docker_path start
$config
470 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
471 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"`
473 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
474 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
485 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname-$config" \
486 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $docker_args $run_image $boot_command
495 if [ "$opt" != "--skip-prereqs" ] ; then
503 # Is the image available?
504 # If not, pull it here so the user is aware what's happening.
505 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
509 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
510 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
512 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
513 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
515 if [[ ! X
"" = X
"$base_image" ]]; then
524 run_command
="cd /pups &&"
525 if [[ ! "false" = $update_pups ]]; then
526 run_command
="$run_command git pull &&"
528 run_command
="$run_command /pups/bin/pups --stdin"
532 env
=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
534 (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 \
535 /bin
/bash
-c "$run_command") \
536 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
538 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
542 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
543 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
551 echo "Successfully bootstrapped, to startup use ./launcher start $config"
556 exec $docker_path exec -it $config /bin
/bash
560 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
562 if [[ ! -z $existing ]]; then
563 address
="`$docker_path port $config 22`"
564 split=(${address//:/ })
565 exec ssh -o StrictHostKeyChecking
=no root@
${split[0]} -p ${split[1]}
567 echo "$config is not running!"
579 $docker_path logs
$config
595 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
596 echo "Ensuring discourse docker is up to date"
600 LOCAL
=$
(git rev-parse @
)
601 REMOTE
=$
(git rev-parse @
{u
})
602 BASE
=$
(git merge-base @ @
{u
})
604 if [ $LOCAL = $REMOTE ]; then
605 echo "Discourse Docker is up-to-date"
607 elif [ $LOCAL = $BASE ]; then
608 echo "Updating Discourse Docker"
609 git pull ||
(echo 'failed to update' && exit 1)
612 elif [ $REMOTE = $BASE ]; then
613 echo "Your version of Discourse Docker is ahead of origin"
616 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
621 set_existing_container
623 if [ ! -z $existing ]
625 echo "Stopping old container"
628 $docker_path stop
-t 10 $config
634 if [ ! -z $existing ]
636 echo "Removing old container"
639 $docker_path rm $config
649 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)