4 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
6 echo " start: Start/initialize a container"
7 echo " stop: Stop a running container"
8 echo " restart: Restart a container"
9 echo " destroy: Stop and remove a container"
10 echo " enter: Use nsenter to enter a container"
11 echo " logs: Docker logs for container"
12 echo " bootstrap: Bootstrap a container for the config based on a template"
13 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
14 echo " cleanup: Remove all containers that have stopped for > 24 hours"
17 echo " --skip-prereqs Don't check prerequisites or resource requirements"
18 echo " --docker-args Extra arguments to pass when running docker"
26 # Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
27 re
='[A-Z/ !@#$%^&*()+~`=]'
28 if [[ $config =~
$re ]];
31 echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
38 docker_min_version
='1.6.0'
39 docker_rec_version
='1.6.0'
41 config_file
=containers
/"$config".yml
42 cidbootstrap
=cids
/"$config"_bootstrap.cid
43 local_discourse
=local_discourse
44 image
=discourse
/discourse
:1.0.17
45 docker_path
=`which docker.io || which docker`
46 template_path
=samples
/standalone.yml
47 changelog
=/tmp
/changelog
# used to test whether sed did anything
49 if [ "${SUPERVISED}" = "true" ]; then
50 restart_policy
="--restart=no"
52 attach_on_run
="-a stdout -a stderr"
57 if [ -n "$DOCKER_HOST" ]; then
58 docker_ip
=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
59 elif [ -x "$(which ip 2>/dev/null)" ]; then
60 docker_ip
=`ip addr show docker0 | \
62 awk '{ split($2,a,"/"); print a[1] }';`
64 docker_ip
=`ifconfig | \
65 grep -B1 "inet addr" | \
66 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
68 awk -F: '{ print $3 }';`
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
95 echo "Docker is not installed, you will need to install Docker in order to run Discourse"
96 echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system"
98 echo "If you are running a recent Ubuntu Server, try the following:"
99 echo "sudo apt-get install docker-engine"
106 if [ -z $docker_path ]; then
110 # 1. docker daemon running?
111 # we send stderr to /dev/null cause we don't care about warnings,
112 # it usually complains about swap which does not matter
113 test=`$docker_path info 2> /dev/null`
114 if [[ $?
-ne 0 ]] ; then
115 echo "Cannot connect to the docker daemon - verify it is running and you have access"
119 # 2. running aufs or btrfs
120 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
121 if [[ "$test" =~
[aufs|btrfs|zfs|overlay
] ]] ; then : ; else
122 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
123 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
124 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
126 echo "If you wish to continue anyway using your existing unsupported filesystem"
127 echo "read the source code of launcher and figure out how to bypass this."
131 # 3. running recommended docker version
132 test=($
($docker_path --version)) # Get docker version string
133 test=${test[2]//,/} # Get version alone and strip comma if exists
135 # At least minimum version
136 if compare_version
"${docker_min_version}" "${test}"; then
137 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
141 # Recommend best version
142 if compare_version
"${docker_rec_version}" "${test}"; then
143 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
146 # 4. discourse docker image is downloaded
147 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
149 if [ -z "$test" ]; then
151 echo "WARNING: We are about to start downloading the Discourse base image"
152 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
154 echo "Please be patient"
159 # 5. able to attach stderr / out / tty
160 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
161 if [[ "$test" =~
"working" ]] ; then : ; else
162 echo "Your Docker installation is not working correctly"
164 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
173 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
174 if [ "$avail_mem" -lt 900 ]; then
175 resources
="insufficient"
176 echo "WARNING: You do not appear to have sufficient memory to run Discourse."
178 echo "Your system may not work properly, or future upgrades of Discourse may"
179 echo "not complete successfully."
181 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#create-new-cloud-server"
182 elif [ "$avail_mem" -lt 1800 ]; then
183 total_swap
="$(LANG=C free -m | grep ^Swap: | awk '{print $2}')"
184 if [ "$total_swap" -lt 1000 ]; then
185 resources
="insufficient"
186 echo "WARNING: You must have at least 1GB of swap when running with less"
187 echo "than 2GB of RAM."
189 echo "Your system may not work properly, or future upgrades of Discourse may"
190 echo "not complete successfully."
192 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#set-up-swap-if-needed"
197 free_disk
="$(df /var | tail -n 1 | awk '{print $4}')"
198 if [ "$free_disk" -lt 5000 ]; then
199 resources
="insufficient"
200 echo "WARNING: You must have at least 5GB of *free* disk space to run Discourse."
202 echo "Insufficient disk space may result in problems running your site, and may"
203 echo "not even allow Discourse installation to complete successfully."
205 echo "Please free up some space, or expand your disk, before continuing."
207 echo "Run \`apt-get autoremove && apt-get autoclean\` to clean up unused packages and \`./launcher cleanup\` to remove stale Docker containers."
211 if [ -t 0 ] && [ "$resources" != "ok" ]; then
213 read -p "Press ENTER to continue, or Ctrl-C to exit and give your system more resources"
218 local valid
=$
(netstat
-tln |
awk '{print $4}' |
grep ":${1}\$")
220 if [ -n "$valid" ]; then
221 echo "Launcher has detected that port ${1} is in use."
223 echo "If you are trying to run Discourse simultaneously with another web server like Apache or nginx, you will need to bind to a different port."
224 echo "See https://meta.discourse.org/t/17247 for help."
225 echo "To continue anyway, re-run Launcher with --skip-prereqs"
230 if [ "$opt" != "--skip-prereqs" ] ; then
234 if [ "$opt" == "--docker-args" ] ; then
241 read -r -d '' env_ruby
<< 'RUBY'
244 input
= STDIN.readlines.
join
245 yaml
= YAML.load
(input
)
247 if host_run
= yaml
['host_run']
248 params
= yaml
['params'] ||
{}
249 host_run.each
do |run|
251 run
= run.gsub
("$#{k}", v
)
253 STDOUT.
write "#{run}--SEP--"
258 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
260 while [ "$host_run" ] ; do
261 iter
=${host_run%%--SEP--*}
263 echo "Host run: $iter"
266 host_run
="${host_run#*--SEP--}"
272 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
273 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
277 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
278 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
281 set_template_info
() {
283 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
284 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
286 arrTemplates
=(${templates// / })
287 config_data
=$
(cat $config_file)
291 for template
in "${arrTemplates[@]}"
293 [ ! -z $template ] && {
294 input
="$input _FILE_SEPERATOR_ $(cat $template)"
298 # we always want our config file last so it takes priority
299 input
="$input _FILE_SEPERATOR_ $config_data"
301 read -r -d '' env_ruby
<< 'RUBY'
304 input
=STDIN.readlines.
join
305 # default to UTF-8 for the dbs sake
306 env
= {'LANG' => 'en_US.UTF-8'}
307 input.
split('_FILE_SEPERATOR_').each
do |yml|
310 env.merge
!(YAML.load
(yml
)['env'] ||
{})
311 rescue Psych
::SyntaxError
=> e
319 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
322 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
327 if [ "$i" == "*ERROR." ]; then
329 elif [ -n "$i" ]; then
334 if [ "$ok" -ne 1 ]; then
336 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
341 if [ -z $docker_path ]; then
345 [ "$command" == "cleanup" ] && {
347 echo "The following command will"
348 echo "- Delete all docker images for old containers"
349 echo "- Delete all stopped and orphan containers"
351 read -p "Are you sure (Y/n): " -n 1 -r && echo
352 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
354 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
355 echo "Starting Cleanup (bytes free $space)"
357 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
359 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
360 echo "Finished Cleanup (bytes free $space)"
372 if [[ ! -e $config_file && $command -ne "memconfig" ]]
374 echo "Config file was not found, ensure $config_file exists"
376 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
380 docker_version
=($
($docker_path --version))
381 docker_version
=${test[2]//,/}
382 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 !"
405 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
406 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
408 if [ -z "$run_image" ]; then
409 run_image
="$local_discourse/$config"
414 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
415 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
417 if [ -z "$boot_command" ]; then
419 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
420 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
422 if [ -z "$no_boot_command" ]; then
423 boot_command
="/sbin/boot"
428 scale_ram_and_cpu
() {
431 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
432 avail_gb
=`expr $(($avail_mem / 950))`
433 avail_cores
=`grep -c processor /proc/cpuinfo`
434 echo "Found ${avail_gb}GB of memory and $avail_cores CPU cores"
436 # set db_shared_buffers: "128MB" (1GB) or 256MB * GB
437 if [ "$avail_gb" -eq "1" ]
439 db_shared_buffers
="128"
441 db_shared_buffers
=`expr $avail_gb \* 256`
443 sed -i -e "s/^ #db_shared_buffers:.*/ db_shared_buffers: \"${db_shared_buffers}MB\"/w $changelog" $config_file
446 echo "setting db_shared_buffers = ${db_shared_buffers}MB based on detected CPU/RAM"
451 # set UNICORN_WORKERS: 2*GB or 2*cores (the same on DO)
452 if [ "$avail_gb" -le "2" ]
454 unicorn_workers
=`expr $avail_gb \* 2`
456 unicorn_workers
=`expr \$avail_cores \* 2`
458 sed -i -e "s/^ #UNICORN_WORKERS:.*/ UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $config_file
461 echo "setting UNICORN_WORKERS = ${unicorn_workers} based on detected CPU/RAM"
469 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
471 if [ ! -z $existing ]
473 echo "Nothing to do, your container has already started!"
477 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
478 if [ ! -z $existing ]
480 echo "starting up existing container"
483 $docker_path start
$config
489 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
490 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
492 IFS
='-p ' read -a array
<<< "$ports"
493 for element
in "${array[@]}"
495 IFS
=':' read -a args
<<< "$element"
496 if [ "${#args[@]}" == "2" ]; then
497 check_ports
"${args[0]}"
498 elif [ "${#args[@]}" == "3" ]; then
499 check_ports
"${args[1]}"
503 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
504 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
512 # get hostname and settings from container configuration
513 for envar
in "${env[@]}"
515 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
517 # use as environment variable
523 hostname
=`hostname -s`
525 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
527 hostname
=$DISCOURSE_HOSTNAME
529 hostname
=$hostname-$config
532 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
533 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
534 # docker added more hostname rules
535 hostname
=${hostname/_/-}
538 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \
539 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $docker_args $run_image $boot_command
546 valid_config_check
() {
549 for x
in DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD \
550 DISCOURSE_DEVELOPER_EMAILS DISCOURSE_HOSTNAME
552 mail_var
=`grep "^ $x:" $config_file`
556 if [[ $mail_var = *"example.com"* ]]
558 echo "Warning: $x left at incorrect default of example.com"
562 echo "Warning: $x not configured."
566 if [ -t 0 ] && [ "$valid_config" != "y" ]; then
568 echo "Please edit $config_file to add missing settings."
575 # Does your system meet the minimum requirements?
576 if [ "$opt" != "--skip-prereqs" ] ; then
580 # is our configuration file valid?
583 # make minor scaling adjustments for RAM and CPU
588 # Is the image available?
589 # If not, pull it here so the user is aware what's happening.
590 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
594 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
595 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
597 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
598 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
600 if [[ ! X
"" = X
"$base_image" ]]; then
609 run_command
="cd /pups &&"
610 if [[ ! "false" = $update_pups ]]; then
611 run_command
="$run_command git pull &&"
613 run_command
="$run_command /pups/bin/pups --stdin"
617 (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 \
618 /bin
/bash
-c "$run_command") \
619 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
621 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
625 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
626 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
634 echo "Successfully bootstrapped, to startup use ./launcher start $config"
639 exec $docker_path exec -it $config /bin
/bash
--login
649 $docker_path logs
$config
665 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
666 echo "Ensuring discourse docker is up to date"
670 LOCAL
=$
(git rev-parse @
)
671 REMOTE
=$
(git rev-parse @
{u
})
672 BASE
=$
(git merge-base @ @
{u
})
674 if [ $LOCAL = $REMOTE ]; then
675 echo "Discourse Docker is up-to-date"
677 elif [ $LOCAL = $BASE ]; then
678 echo "Updating Discourse Docker"
679 git pull ||
(echo 'failed to update' && exit 1)
682 elif [ $REMOTE = $BASE ]; then
683 echo "Your version of Discourse Docker is ahead of origin"
686 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
691 set_existing_container
693 if [ ! -z $existing ]
695 echo "Stopping old container"
698 $docker_path stop
-t 10 $config
704 if [ ! -z $existing ]
706 echo "Removing old container"
709 $docker_path rm $config
719 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)