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"
155 # 6. sufficient available resources
157 free_mem
="$(LANG=C free -m | grep 'buffers/cache' | awk '{print $4}')"
158 if [ "$free_mem" -lt 800 ]; then
159 echo "You do not appear to have sufficient memory to run Discourse."
161 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#create-new-cloud-server"
163 elif [ "$free_mem" -lt 1800 ]; then
164 total_swap
="$(LANG=C free -m | grep ^Swap: | awk '{print $2}')"
165 if [ "$total_swap" -lt 1000 ]; then
166 echo "You must have at least 1GB of swap when running in a low-memory environment."
168 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#set-up-swap-if-needed"
174 free_disk
="$(df /var | tail -n 1 | awk '{print $4}')"
175 if [ "$free_disk" -lt 5000 ]; then
176 echo "You must have at least 5GB of *free* disk space to install Discourse."
178 echo "Please free up some space, or expand your disk, before continuing."
183 if [ "$opt" != "--skip-prereqs" ] ; then
187 if [ "$opt" == "--docker-args" ] ; then
194 local ${ssh_key_locations}
196 ~
/.ssh
/id_ed25519.pub
200 ~core
/.ssh
/authorized_keys
204 for keyfile
in "${ssh_key_locations[@]}"; do
205 if [[ -e ${keyfile} ]] ; then
206 ssh_pub_key
="$(cat ${keyfile})"
217 echo "Docker is not installed, you will need to install Docker in order to run Discourse"
218 echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system"
220 echo "If you are running Ubuntu Trusty or later, you can try the following:"
223 echo "sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D"
224 echo "sudo sh -c \"echo deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -sc) main > /etc/apt/sources.list.d/docker.list\""
225 echo "sudo apt-get update"
226 echo "sudo apt-get install docker-engine"
232 read -r -d '' env_ruby
<< 'RUBY'
235 input
= STDIN.readlines.
join
236 yaml
= YAML.load
(input
)
238 if host_run
= yaml
['host_run']
239 params
= yaml
['params'] ||
{}
240 host_run.each
do |run|
242 run
= run.gsub
("$#{k}", v
)
244 STDOUT.
write "#{run}--SEP--"
249 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
251 while [ "$host_run" ] ; do
252 iter
=${host_run%%--SEP--*}
254 echo "Host run: $iter"
257 host_run
="${host_run#*--SEP--}"
263 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
264 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
268 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
269 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
272 set_template_info
() {
274 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
275 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
278 arrTemplates
=(${templates// / })
279 config_data
=$
(cat $config_file)
284 for template
in "${arrTemplates[@]}"
286 [ ! -z $template ] && {
287 input
="$input _FILE_SEPERATOR_ $(cat $template)"
291 # we always want our config file last so it takes priority
292 input
="$input _FILE_SEPERATOR_ $config_data"
294 read -r -d '' env_ruby
<< 'RUBY'
297 input
=STDIN.readlines.
join
298 # default to UTF-8 for the dbs sake
299 env
= {'LANG' => 'en_US.UTF-8'}
300 input.
split('_FILE_SEPERATOR_').each
do |yml|
303 env.merge
!(YAML.load
(yml
)['env'] ||
{})
304 rescue Psych
::SyntaxError
=> e
312 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
315 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
320 if [ "$i" == "*ERROR." ]; then
322 elif [ -n "$i" ]; then
327 if [ "$ok" -ne 1 ]; then
329 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
334 [ -z $docker_path ] && {
338 [ "$command" == "cleanup" ] && {
340 echo "The following command will"
341 echo "- Delete all docker images for old containers"
342 echo "- Delete all stopped and orphan containers"
344 read -p "Are you sure (Y/n): " -n 1 -r && echo
345 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
347 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
348 echo "Starting Cleanup (bytes free $space)"
350 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
352 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
353 echo "Finished Cleanup (bytes free $space)"
365 if [ ! -e $config_file ]
367 echo "Config file was not found, ensure $config_file exists"
369 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
374 docker_version
=($
($docker_path --version))
375 docker_version
=${test[2]//,/}
377 if compare_version
"1.2.0" "$docker_version"; then
378 echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
381 restart_policy
=${restart_policy:---restart=always}
384 set_existing_container
(){
385 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
390 set_existing_container
392 if [ ! -z $existing ]
396 $docker_path stop
-t 10 $config
399 echo "$config was not started !"
406 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
408 if [ ! -z $existing ]
410 echo "Nothing to do, your container has already started!"
414 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
415 if [ ! -z $existing ]
417 echo "starting up existing container"
420 $docker_path start
$config
426 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
427 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"`
429 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
430 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
439 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname-$config" \
440 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $docker_args $local_discourse/$config \
454 # Is the image available?
455 # If not, pull it here so the user is aware what's happening.
456 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
460 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
461 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
463 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
464 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
466 if [[ ! X
"" = X
"$base_image" ]]; then
475 run_command
="cd /pups &&"
476 if [[ ! "false" = $update_pups ]]; then
477 run_command
="$run_command git pull &&"
479 run_command
="$run_command /pups/bin/pups --stdin"
483 env
=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
485 (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 \
486 /bin
/bash
-c "$run_command") \
487 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
489 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
493 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
494 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
500 echo "Successfully bootstrapped, to startup use ./launcher start $config"
505 exec $docker_path exec -it $config /bin
/bash
509 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
511 if [[ ! -z $existing ]]; then
512 address
="`$docker_path port $config 22`"
513 split=(${address//:/ })
514 exec ssh -o StrictHostKeyChecking
=no root@
${split[0]} -p ${split[1]}
516 echo "$config is not running!"
528 $docker_path logs
$config
544 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
545 echo "Ensuring discourse docker is up to date"
549 LOCAL
=$
(git rev-parse @
)
550 REMOTE
=$
(git rev-parse @
{u
})
551 BASE
=$
(git merge-base @ @
{u
})
553 if [ $LOCAL = $REMOTE ]; then
554 echo "Discourse Docker is up-to-date"
556 elif [ $LOCAL = $BASE ]; then
557 echo "Updating Discourse Docker"
558 git pull ||
(echo 'failed to update' && exit 1)
561 elif [ $REMOTE = $BASE ]; then
562 echo "Your version of Discourse Docker is ahead of origin"
565 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
570 set_existing_container
572 if [ ! -z $existing ]
574 echo "Stopping old container"
577 $docker_path stop
-t 10 $config
583 if [ ! -z $existing ]
585 echo "Removing old container"
588 $docker_path rm $config
598 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)