X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=launcher;h=1f3561f1a4e1de3ceef91fdbfe9125cadad56119;hb=b2f7b7a0d0ae3283fada74e7e794a2295c404af0;hp=090028aa82ec0b44a022dd57d30b7eb6277741c4;hpb=444a4122a67c0240af942dba4c61f45772e01e77;p=discourse_docker.git diff --git a/launcher b/launcher index 090028a..1f3561f 100755 --- a/launcher +++ b/launcher @@ -4,16 +4,28 @@ command=$1 config=$2 opt=$3 +# Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out +re='[A-Z/ !@#$%^&*()+~`=]' +if [[ $config =~ $re ]]; + then + echo + echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0." + echo + exit 1 +fi + cd "$(dirname "$0")" -docker_min_version='1.2.0' -docker_rec_version='1.2.0' +docker_min_version='1.6.0' +docker_rec_version='1.6.0' config_file=containers/"$config".yml cidbootstrap=cids/"$config"_bootstrap.cid local_discourse=local_discourse -image=samsaffron/discourse:1.0.10 +image=discourse/discourse:1.0.17 docker_path=`which docker.io || which docker` +template_path=samples/standalone_template.yml +changelog=/tmp/changelog # used to test whether sed did anything if [ "${SUPERVISED}" = "true" ]; then restart_policy="--restart=no" @@ -23,7 +35,9 @@ else attach_on_run="-d" fi -if [ -x "$(which ip 2>/dev/null)" ]; then +if [ -n "$DOCKER_HOST" ]; then + docker_ip=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"` +elif [ -x "$(which ip 2>/dev/null)" ]; then docker_ip=`ip addr show docker0 | \ grep 'inet ' | \ awk '{ split($2,a,"/"); print a[1] }';` @@ -46,13 +60,13 @@ usage () { echo " enter: Use nsenter to enter a container" echo " ssh: Start a bash shell in a running container" echo " logs: Docker logs for container" - echo " mailtest: Test the mail settings in a container" echo " bootstrap: Bootstrap a container for the config based on a template" echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)" + echo " memconfig: Configure defaults based on available RAM" echo " cleanup: Remove all containers that have stopped for > 24 hours" echo echo "Options:" - echo " --skip-prereqs Don't check prerequisites" + echo " --skip-prereqs Don't check prerequisites or resource requirements" echo " --docker-args Extra arguments to pass when running docker" exit 1 } @@ -92,16 +106,14 @@ prereqs() { # 2. running aufs or btrfs test=`$docker_path info 2> /dev/null | grep 'Driver: '` - if [[ "$test" =~ [aufs|btrfs] ]] ; then : ; else - echo "Your Docker installation is not using the recommended AuFS (union filesystem) and may be unstable." - echo "If you are unable to bootstrap / stop your image please report the issue at:" - echo "https://meta.discourse.org/t/discourse-docker-installation-without-aufs/15639" + if [[ "$test" =~ [aufs|btrfs|zfs|overlay] ]] ; then : ; else + echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install." + echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)" + echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver" echo "" - read -p "Continue without proper filesystem? [yN]" yn - case $yn in - [Yy]* ) break;; - * ) exit 1;; - esac + echo "If you wish to continue anyway using your existing unsupported filesystem" + echo "read the source code of launcher and figure out how to bypass this." + exit 1 fi # 3. running recommended docker version @@ -142,6 +154,65 @@ prereqs() { echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam" exit 1 fi + +} + +check_resources() { + # Memory + resources="ok" + avail_mem="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')" + if [ "$avail_mem" -lt 900 ]; then + resources="insufficient" + echo "WARNING: You do not appear to have sufficient memory to run Discourse." + echo + echo "Your system may not work properly, or future upgrades of Discourse may" + echo "not complete successfully." + echo + echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#create-new-cloud-server" + elif [ "$avail_mem" -lt 1800 ]; then + total_swap="$(LANG=C free -m | grep ^Swap: | awk '{print $2}')" + if [ "$total_swap" -lt 1000 ]; then + resources="insufficient" + echo "WARNING: You must have at least 1GB of swap when running with less" + echo "than 2GB of RAM." + echo + echo "Your system may not work properly, or future upgrades of Discourse may" + echo "not complete successfully." + echo + echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#set-up-swap-if-needed" + fi + fi + + # Disk space + free_disk="$(df /var | tail -n 1 | awk '{print $4}')" + if [ "$free_disk" -lt 5000 ]; then + resources="insufficient" + echo "WARNING: You must have at least 5GB of *free* disk space to run Discourse." + echo + echo "Insufficient disk space may result in problems running your site, and may" + echo "not even allow Discourse installation to complete successfully." + echo + echo "Please free up some space, or expand your disk, before continuing." + exit 1 + fi + + if [ -t 0 ] && [ "$resources" != "ok" ]; then + echo + read -p "Press ENTER to continue, or Ctrl-C to exit and give your system more resources" + fi +} + +check_ports() { + local valid=$(netstat -tln | awk '{print $4}' | grep ":${1}\$") + + if [ -n "$valid" ]; then + echo "Launcher has detected that port ${1} is in use." + echo "" + 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." + echo "See https://meta.discourse.org/t/17247 for help." + echo "To continue anyway, re-run Launcher with --skip-prereqs" + exit 1 + fi } if [ "$opt" != "--skip-prereqs" ] ; then @@ -178,18 +249,16 @@ get_ssh_pub_key() { install_docker() { - echo "Docker is not installed, make sure you are running on the 3.8 kernel" - echo "The best supported Docker release is Ubuntu 12.04.03 for it run the following" + echo "Docker is not installed, you will need to install Docker in order to run Discourse" + echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system" echo - echo "sudo apt-get update" - echo "sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring" - echo "sudo reboot" + echo "If you are running Ubuntu Trusty or later, you can try the following:" echo - echo "sudo sh -c \"wget -qO- https://get.docker.io/gpg | apt-key add -\"" - echo "sudo sh -c \"echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list\"" + echo "sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D" + echo "sudo sh -c \"echo deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -sc) main > /etc/apt/sources.list.d/docker.list\"" echo "sudo apt-get update" - echo "sudo apt-get install lxc-docker" + echo "sudo apt-get install docker-engine" exit 1 } @@ -301,7 +370,7 @@ RUBY install_docker } -[ $command == "cleanup" ] && { +[ "$command" == "cleanup" ] && { echo echo "The following command will" echo "- Delete all docker images for old containers" @@ -311,27 +380,13 @@ RUBY if [[ $REPLY =~ ^[Yy]$ || ! $REPLY ]] then space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available) + echo "Starting Cleanup (bytes free $space)" - echo "Starting Cleanup" - - if [[ ! -z `docker ps -aq` ]]; then - docker inspect -f '{{.Id}},{{.State.Running}},{{.State.FinishedAt}}' $(docker ps -qa) | \ - awk -F, 'BEGIN { TIME=strftime("%FT%T.000000000Z",systime()-60*60*24); } $2=="false" && $3 < TIME {print $1;}' | \ - xargs --no-run-if-empty docker rm >/dev/null 2>/dev/null - fi - - docker rmi `docker images -a | grep '' | awk '{print $3}'` 2> /dev/null - - let freed=$space-$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available) - - echo $space - echo $(df /var/lib/docker | awk '{ print $4 }' | grep -v Available) + STATE_DIR=./.gc-state scripts/docker-gc + space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available) + echo "Finished Cleanup (bytes free $space)" - 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 }}}' - [ -z "$output" ] && { - [[ $freed > 0 ]] && { echo "./launcher cleanup cleared up $freed of disk space."; } || { echo "./launcher cleanup has finished, no files were removed."; } - } || { echo "./launcher cleanup cleared up $freed of disk space."; } else exit 1 fi @@ -342,7 +397,7 @@ RUBY usage } -if [ ! -e $config_file ] +if [[ ! -e $config_file && $command -ne "memconfig" ]] then echo "Config file was not found, ensure $config_file exists" echo "" @@ -361,15 +416,6 @@ else restart_policy=${restart_policy:---restart=always} fi - -run_mailtest(){ - if [ ! -e $config_file ]; then - echo "Config does not exist: $config_file" >&2 - exit 1 - fi - exec scripts/mailtest $config_file -} - set_existing_container(){ existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'` } @@ -390,6 +436,90 @@ run_stop(){ fi } +set_run_image() { + run_image=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \ + "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"` + + if [ -z "$run_image" ]; then + run_image="$local_discourse/$config" + fi +} + +set_boot_command() { + boot_command=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \ + "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"` + + if [ -z "$boot_command" ]; then + + no_boot_command=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \ + "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"` + + if [ -z "$no_boot_command" ]; then + boot_command="/sbin/boot" + fi + fi +} + +run_memconfig(){ + if [ "$opt" != "--skip-prereqs" ] ; then + check_resources + fi + if [ -f $config_file ] + then + cp $config_file $config_file.bak + echo "Saving $config_file as $config_file.bak" + else + echo "Creating $config_file from $template_path" + if [ ! -f $template_path ] + then + echo "$template_path is missing. Exiting." + exit 1 + fi + cp $template_path $config_file + fi + + # get free mem + avail_mem="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')" + avail_gb=`expr $(($avail_mem / 950))` + avail_cores=`grep -c processor /proc/cpuinfo` + echo "Found $avail_mem (${avail_gb}GB), of memory and $avail_cores cores." + + # set db_shared_buffers: "128MB" (1GB) or 256MB * GB + if [ "$avail_gb" -eq "1" ] + then + db_shared_buffers="128" + else + db_shared_buffers=`expr $avail_gb \* 256` + fi + echo -e "Setting db_shared_buffers to ${db_shared_buffers}GB\c" + sed -i -e "s/^ db_shared_buffers:.*/ db_shared_buffers: \"${db_shared_buffers}GB\"/w $changelog" $config_file + if [ -s $changelog ] + then + echo " successfully." + rm $changelog + else + echo -e ". . . oops!\n---> db_shared_buffers not found in $config_file. Retaining defaults." + fi + + # set UNICORN_WORKERS: 2*GB or 2*cores (the same on DO) + if [ "$avail_gb" -le "2" ] + then + unicorn_workers=`expr $avail_gb \* 2` + else + unicorn_workers=`expr $avail_cores \* 2` + fi + + echo -e "Setting UNICORN_WORKERS to $unicorn_workers\c" + sed -i -e "s/^ UNICORN_WORKERS:.*/ UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $config_file + if [ -s $changelog ] + then + echo " successfully." + rm $changelog + else + echo -e ". . . oops!\n---> UNICORN_WORKERS not found in $config_file. Retaining defaults.\n" + fi +} + run_start(){ existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'` @@ -413,17 +543,56 @@ run_start(){ host_run ports=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \ - "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"` + "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"` + + IFS='-p ' read -a array <<< "$ports" + for element in "${array[@]}" + do + IFS=':' read -a args <<< "$element" + if [ "${#args[@]}" == "2" ]; then + check_ports "${args[0]}" + elif [ "${#args[@]}" == "3" ]; then + check_ports "${args[1]}" + fi + done + + docker_args=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \ + "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"` set_template_info set_volumes set_links + set_run_image + set_boot_command + + # get hostname and settings from container configuration + for envar in "${env[@]}" + do + if [[ $envar == DOCKER_USE_HOSTNAME* ]] || [[ $envar == DISCOURSE_HOSTNAME* ]] + then + # use as environment variable + eval $envar + fi + done ( - hostname=`hostname` + hostname=`hostname -s` + # overwrite hostname + if [ "$DOCKER_USE_HOSTNAME" = "true" ] + then + hostname=$DISCOURSE_HOSTNAME + else + hostname=$hostname-$config + fi + + # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first + # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname` + # docker added more hostname rules + hostname=${hostname/_/-} + set -x - $docker_path run $user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname-$config" \ - -e DOCKER_HOST_IP=$docker_ip --name $config -t $ports $volumes $local_discourse/$config /sbin/boot + $docker_path run $user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \ + -e DOCKER_HOST_IP=$docker_ip --name $config -t $ports $volumes $docker_args $run_image $boot_command ) exit 0 @@ -432,6 +601,10 @@ run_start(){ run_bootstrap(){ + if [ "$opt" != "--skip-prereqs" ] ; then + check_resources + fi + host_run get_ssh_pub_key @@ -471,7 +644,7 @@ run_bootstrap(){ /bin/bash -c "$run_command") \ || ($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap) - [ ! -e $cidbootstrap ] && echo "FAILED TO BOOTSTRAP" && exit 1 + [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1 sleep 5 @@ -479,6 +652,8 @@ run_bootstrap(){ $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap } + + case "$command" in bootstrap) run_bootstrap @@ -486,13 +661,8 @@ case "$command" in exit 0 ;; - mailtest) - run_mailtest - exit 0 - ;; - enter) - exec $docker_path exec -it $config /bin/bash + exec $docker_path exec -it $config /bin/bash --login ;; ssh) @@ -530,6 +700,11 @@ case "$command" in exit 0 ;; + memconfig) + run_memconfig + exit 0 + ;; + rebuild) if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then echo "Ensuring discourse docker is up to date"