Pin uglify-js to 2.x for now
[discourse_docker.git] / launcher
index 1852382bb01263f21cd6f0f6eeae5c676b3d6413..61b22ab6da74949a6d29afd8f6d91085dac12ab9 100755 (executable)
--- a/launcher
+++ b/launcher
@@ -1,8 +1,48 @@
 #!/bin/bash
 
+usage () {
+  echo "Usage: launcher COMMAND CONFIG [--skip-prereqs] [--docker-args STRING]"
+  echo "Commands:"
+  echo "    start:      Start/initialize a container"
+  echo "    stop:       Stop a running container"
+  echo "    restart:    Restart a container"
+  echo "    destroy:    Stop and remove a container"
+  echo "    enter:      Open a shell to run commands inside the container"
+  echo "    logs:       View the Docker logs for 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 "    cleanup:    Remove all containers that have stopped for > 24 hours"
+  echo
+  echo "Options:"
+  echo "    --skip-prereqs             Don't check launcher prerequisites"
+  echo "    --docker-args              Extra arguments to pass when running docker"
+  echo "    --skip-mac-address         Don't assign a mac address"
+  exit 1
+}
+
 command=$1
 config=$2
-opt=$3
+user_args=""
+
+while [ ${#} -gt 0 ]; do
+  case "${1}" in
+  --debug)
+    DEBUG="1"
+    ;;
+  --skip-prereqs)
+    SKIP_PREREQS="1"
+    ;;
+  --skip-mac-address)
+    SKIP_MAC_ADDRESS="1"
+    ;;
+  --docker-args)
+    user_args="$2"
+    shift
+    ;;
+  esac
+
+  shift 1
+done
 
 # 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/ !@#$%^&*()+~`=]'
@@ -16,15 +56,17 @@ fi
 
 cd "$(dirname "$0")"
 
-docker_min_version='1.6.0'
-docker_rec_version='1.6.0'
+docker_min_version='1.8.0'
+docker_rec_version='1.8.0'
+git_min_version='1.8.0'
+git_rec_version='1.8.0'
 
 config_file=containers/"$config".yml
 cidbootstrap=cids/"$config"_bootstrap.cid
 local_discourse=local_discourse
-image=discourse/discourse:1.0.17
+image=discourse/discourse:1.3.10
 docker_path=`which docker.io || which docker`
-
+git_path=`which git`
 
 if [ "${SUPERVISED}" = "true" ]; then
   restart_policy="--restart=no"
@@ -48,27 +90,6 @@ else
                   awk -F: '{ print $3 }';`
 fi
 
-
-usage () {
-  echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
-  echo "Commands:"
-  echo "    start:      Start/initialize a container"
-  echo "    stop:       Stop a running container"
-  echo "    restart:    Restart a container"
-  echo "    destroy:    Stop and remove a container"
-  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 "    bootstrap:  Bootstrap a container for the config based on a template"
-  echo "    rebuild:    Rebuild a container (destroy old, bootstrap, start new)"
-  echo "    cleanup:    Remove all containers that have stopped for > 24 hours"
-  echo
-  echo "Options:"
-  echo "    --skip-prereqs   Don't check prerequisites or resource requirements"
-  echo "    --docker-args    Extra arguments to pass when running docker"
-  exit 1
-}
-
 compare_version() {
     declare -a ver_a
     declare -a ver_b
@@ -90,13 +111,23 @@ compare_version() {
     return 1  # They are equal
 }
 
-prereqs() {
+
+install_docker() {
+  echo "Docker is not installed, you will need to install Docker in order to run Launcher"
+  echo "See https://docs.docker.com/installation/"
+  exit 1
+}
+
+check_prereqs() {
+
+  if [ -z $docker_path ]; then
+    install_docker
+  fi
 
   # 1. docker daemon running?
   # we send stderr to /dev/null cause we don't care about warnings,
   # it usually complains about swap which does not matter
   test=`$docker_path info 2> /dev/null`
-
   if [[ $? -ne 0 ]] ; then
     echo "Cannot connect to the docker daemon - verify it is running and you have access"
     exit 1
@@ -108,8 +139,8 @@ prereqs() {
     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 ""
-    echo "If you wish to continue anyway using your existing unsupported filesystem"
+    echo
+    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
@@ -118,15 +149,13 @@ prereqs() {
   test=($($docker_path --version))  # Get docker version string
   test=${test[2]//,/}  # Get version alone and strip comma if exists
 
-  [[ "$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
-
-  # At least minimum version
+  # At least minimum docker version
   if compare_version "${docker_min_version}" "${test}"; then
     echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
     exit 1
   fi
 
-  # Recommend best version
+  # Recommend newer docker version
   if compare_version "${docker_rec_version}" "${test}"; then
     echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
   fi
@@ -141,10 +170,24 @@ prereqs() {
     echo
     echo "Please be patient"
     echo
+  fi
+
+  # 5. running recommended git version
+  test=($($git_path --version))  # Get git version string
+  test=${test[2]//,/}  # Get version alone and strip comma if exists
 
+  # At least minimum version
+  if compare_version "${git_min_version}" "${test}"; then
+    echo "ERROR: Git version ${test} not supported, please upgrade to at least ${git_min_version}, or recommended ${git_rec_version}"
+    exit 1
   fi
 
-  # 5. able to attach stderr / out / tty
+  # Recommend best version
+  if compare_version "${git_rec_version}" "${test}"; then
+    echo "WARNING: Git version ${test} deprecated, recommend upgrade to ${git_rec_version} or newer."
+  fi
+
+  # 6. able to attach stderr / out / tty
   test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
   if [[ "$test" =~ "working" ]] ; then : ; else
     echo "Your Docker installation is not working correctly"
@@ -155,112 +198,11 @@ prereqs() {
 
 }
 
-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
-  prereqs
+if [ -z "$SKIP_PREREQS" ] ; then
+  check_prereqs
 fi
 
-if [ "$opt" == "--docker-args" ] ; then
-  user_args=$4
-else
-  user_args=""
-fi
-
-get_ssh_pub_key() {
-  local ${ssh_key_locations}
-  ssh_key_locations=(
-    ~/.ssh/id_ed25519.pub
-    ~/.ssh/id_ecdsa.pub
-    ~/.ssh/id_rsa.pub
-    ~/.ssh/id_dsa.pub
-    ~core/.ssh/authorized_keys
-  )
-
-  local $keyfile
-  for keyfile in "${ssh_key_locations[@]}"; do
-    if [[ -e ${keyfile} ]] ; then
-      ssh_pub_key="$(cat ${keyfile})"
-      return 0
-    fi
-  done
-
-  return 0
-}
-
-
-install_docker() {
-
-  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 "If you are running Ubuntu Trusty or later, you can try the following:"
-  echo
-
-  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 docker-engine"
-
-  exit 1
-}
-
 host_run() {
   read -r -d '' env_ruby << 'RUBY'
   require 'yaml'
@@ -302,18 +244,36 @@ set_links() {
         "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
 }
 
-set_template_info() {
-
-    templates=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
+find_templates() {
+    local templates=`cat $1 | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
       "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
 
+    local arrTemplates=${templates// / }
+
+    if [ ! -z "$templates" ]; then
+      for template in "${arrTemplates[@]}"
+      do
+        local nested_templates=$(find_templates $template)
+
+        if [ ! -z "$nested_templates" ]; then
+          templates="$templates $nested_templates"
+        fi
+      done
+
+      echo $templates
+    else
+      echo ""
+    fi
+}
+
+set_template_info() {
+    templates=$(find_templates $config_file)
 
     arrTemplates=(${templates// / })
     config_data=$(cat $config_file)
 
     input="hack: true"
 
-
     for template in "${arrTemplates[@]}"
     do
       [ ! -z $template ] && {
@@ -359,14 +319,53 @@ RUBY
 
     if [ "$ok" -ne 1 ]; then
       echo "${env[@]}"
-      echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
+      echo "YAML syntax error. Please check your containers/*.yml config files."
+      exit 1
+    fi
+
+    read -r -d '' labels_ruby << 'RUBY'
+    require 'yaml'
+
+    input=STDIN.readlines.join
+    # default to UTF-8 for the dbs sake
+    labels = {}
+    input.split('_FILE_SEPERATOR_').each do |yml|
+       yml.strip!
+       begin
+         labels.merge!(YAML.load(yml)['labels'] || {})
+       rescue Psych::SyntaxError => e
+        puts e
+        puts "*ERROR."
+       rescue => e
+        puts yml
+        p e
+       end
+    end
+    puts labels.map{|k,v| "-l\n#{k}=#{v}" }.join("\n")
+RUBY
+
+    raw=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$labels_ruby"`
+
+    labels=()
+    ok=1
+    while read i; do
+      if [ "$i" == "*ERROR." ]; then
+        ok=0
+      elif [ -n "$i" ]; then
+        labels[${#labels[@]}]=$i
+      fi
+    done <<< "$raw"
+
+    if [ "$ok" -ne 1 ]; then
+      echo "${labels[@]}"
+      echo "YAML syntax error. Please check your containers/*.yml config files."
       exit 1
     fi
 }
 
-[ -z $docker_path ] && {
+if [ -z $docker_path ]; then
   install_docker
-}
+fi
 
 [ "$command" == "cleanup" ] && {
   echo
@@ -391,34 +390,28 @@ RUBY
   exit 0
 }
 
-[ $# -lt 2 ] && {
+if [ -z "$command" -a -z "$config" ]; then
   usage
-}
+fi
 
-if [ ! -e $config_file ]
-  then
+if [ ! "$command" == "setup" ]; then
+  if [[ ! -e $config_file ]]; then
     echo "Config file was not found, ensure $config_file exists"
-    echo ""
+    echo
     echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
     exit 1
+  fi
 fi
 
-
 docker_version=($($docker_path --version))
 docker_version=${test[2]//,/}
-
-if compare_version "1.2.0" "$docker_version"; then
-  echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
-  restart_policy=""
-else
-  restart_policy=${restart_policy:---restart=always}
-fi
+restart_policy=${restart_policy:---restart=always}
 
 set_existing_container(){
   existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
 }
 
-run_stop(){
+run_stop() {
 
   set_existing_container
 
@@ -458,14 +451,14 @@ set_boot_command() {
   fi
 }
 
-run_start(){
+run_start() {
 
    existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
    echo $existing
    if [ ! -z $existing ]
    then
      echo "Nothing to do, your container has already started!"
-     exit 1
+     exit 0
    fi
 
    existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
@@ -480,19 +473,9 @@ run_start(){
    fi
 
    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}\"}.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
+   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}\"}.join(' ')"`
 
    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']"`
@@ -515,10 +498,6 @@ run_start(){
 
    (
      hostname=`hostname -s`
-     # 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/_/-}
      # overwrite hostname
      if [ "$DOCKER_USE_HOSTNAME" = "true" ]
      then
@@ -527,25 +506,32 @@ run_start(){
        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//_/-}
+
+
+     if [ -z "$SKIP_MAC_ADDRESS" ] ; then
+      mac_address="--mac-address $($docker_path run $user_args -i --rm -a stdout -a stderr $image /bin/sh -c "echo $hostname | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'")"
+     fi
+
      set -x
-     $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
+     $docker_path run $links $attach_on_run $restart_policy "${env[@]}" "${labels[@]}" -h "$hostname" \
+        -e DOCKER_HOST_IP="$docker_ip" --name $config -t $ports $volumes $mac_address $docker_args $user_args \
+        $run_image $boot_command
 
    )
    exit 0
 
 }
 
-run_bootstrap(){
 
-  if [ "$opt" != "--skip-prereqs" ] ; then
-    check_resources
-  fi
+run_bootstrap() {
 
+  # I got no frigging clue what this does, ask Sam Saffron. It RUNS STUFF ON THE HOST I GUESS?
   host_run
 
-  get_ssh_pub_key
-
   # Is the image available?
   # If not, pull it here so the user is aware what's happening.
   $docker_path history $image >/dev/null 2>&1 || $docker_path pull $image
@@ -575,13 +561,31 @@ run_bootstrap(){
 
   echo $run_command
 
-  env=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
+  unset ERR
+  (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 \
+    /bin/bash -c "$run_command") || ERR=$?
+
+  unset FAILED
+  # magic exit code that indicates a retry
+  if [[ "$ERR" == 77 ]]; then
+    $docker_path rm `cat $cidbootstrap`
+    rm $cidbootstrap
+    exit 77
+  elif [[ "$ERR" > 0 ]]; then
+    FAILED=TRUE
+  fi
 
-  (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 \
-     /bin/bash -c "$run_command") \
-     || ($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
+  if [[ $FAILED = "TRUE" ]]; then
+    if [[ ! -z "$DEBUG" ]]; then
+      $docker_path commit `cat $cidbootstrap` $local_discourse/$config-debug || echo 'FAILED TO COMMIT'
+      echo "** DEBUG ** Maintaining image for diagnostics $local_discourse/$config-debug"
+    fi
 
-  [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
+    $docker_path rm `cat $cidbootstrap`
+    rm $cidbootstrap
+    echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one"
+    exit 1
+  fi
 
   sleep 5
 
@@ -602,19 +606,6 @@ case "$command" in
       exec $docker_path exec -it $config /bin/bash --login
       ;;
 
-  ssh)
-       existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
-
-       if [[ ! -z $existing ]]; then
-         address="`$docker_path port $config 22`"
-         split=(${address//:/ })
-         exec ssh -o StrictHostKeyChecking=no root@${split[0]} -p ${split[1]}
-       else
-         echo "$config is not running!"
-         exit 1
-       fi
-      ;;
-
   stop)
       run_stop
       exit 0
@@ -639,7 +630,7 @@ case "$command" in
 
   rebuild)
       if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
-        echo "Ensuring discourse docker is up to date"
+        echo "Ensuring launcher is up to date"
 
         git remote update
 
@@ -648,18 +639,23 @@ case "$command" in
         BASE=$(git merge-base @ @{u})
 
         if [ $LOCAL = $REMOTE ]; then
-          echo "Discourse Docker is up-to-date"
+          echo "Launcher is up-to-date"
 
         elif [ $LOCAL = $BASE ]; then
-          echo "Updating Discourse Docker"
+          echo "Updating Launcher"
           git pull || (echo 'failed to update' && exit 1)
-          exec /bin/bash $0 $@
+          
+          for (( i=${#BASH_ARGV[@]}-1,j=0; i>=0,j<${#BASH_ARGV[@]}; i--,j++ ))
+          do
+            args[$j]=${BASH_ARGV[$i]}
+          done
+          exec /bin/bash $0 "${args[@]}" # $@ is empty, because of shift at the beginning. Use BASH_ARGV instead.
 
         elif [ $REMOTE = $BASE ]; then
-          echo "Your version of Discourse Docker is ahead of origin"
+          echo "Your version of Launcher is ahead of origin"
 
         else
-          echo "Discourse Docker has diverged source, this is only expected in Dev mode"
+          echo "Launcher has diverged source, this is only expected in Dev mode"
         fi
 
       fi