Update base image to Ruby 2.6.2 based image
[discourse_docker.git] / launcher
index 983d02707144b9de679c05105e5773d15d9ba4ce..4e615927503187c33d970fc27142d7ad0238e080 100755 (executable)
--- a/launcher
+++ b/launcher
@@ -1,81 +1,89 @@
 #!/bin/bash
 
 usage () {
-  echo "Usage: launcher COMMAND CONFIG [--skip-prereqs] [--skip-discourse-prereqs] [--docker-args STRING]"
+  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:      Use nsenter to enter a 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 "    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 "    run:         Run the given command with the config in the context of the last bootstrapped image"
+  echo "    rebuild:     Rebuild a container (destroy old, bootstrap, start new)"
+  echo "    cleanup:     Remove all containers that have stopped for > 24 hours"
+  echo "    start-cmd:   Generate docker command used to start container"
   echo
   echo "Options:"
   echo "    --skip-prereqs             Don't check launcher prerequisites"
-  echo "    --skip-discourse-prereqs   Don't check prerequisites specifiy to Discourse"
   echo "    --docker-args              Extra arguments to pass when running docker"
+  echo "    --skip-mac-address         Don't assign a mac address"
+  echo "    --run-image                Override the image used for running the container"
   exit 1
 }
 
-[ $# -lt 2 ] && {
-  usage
-}
-
 command=$1
 config=$2
-shift 2
 user_args=""
+user_run_image=""
+
+if [[ $command == "run" ]]; then
+  run_command=$3
+fi
 
 while [ ${#} -gt 0 ]; do
   case "${1}" in
-
+  --debug)
+    DEBUG="1"
+    ;;
   --skip-prereqs)
-    SKIP_PREREQ="1"
+    SKIP_PREREQS="1"
     ;;
-  --skip-discourse-prereqs)
-    SKIP_DISCOURSE_PREREQS="1"
+  --skip-mac-address)
+    SKIP_MAC_ADDRESS="1"
     ;;
   --docker-args)
     user_args="$2"
     shift
     ;;
-  *)
-    echo "Unknown options '${1}'"
-    usage
+  --run-image)
+    user_run_image="$2"
+    shift
     ;;
   esac
 
   shift 1
 done
 
+if [ -z "$command" -o -z "$config" -a "$command" != "cleanup" ]; then
+  usage
+  exit 1
+fi
+
 # 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/ !@#$%^&*()+~`=]'
+re='[[:upper:]/ !@#$%^&*()+~`=]'
 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 "ERROR: Config name '$config' 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.6.0'
-docker_rec_version='1.6.0'
+docker_min_version='17.03.1'
+docker_rec_version='17.06.2'
 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
-docker_path=`which docker.io || which docker`
+image="discourse/base:2.0.20190321-0122"
+docker_path=`which docker.io 2> /dev/null || which docker`
 git_path=`which git`
-template_path=samples/standalone.yml
-changelog=/tmp/changelog # used to test whether sed did anything
 
 if [ "${SUPERVISED}" = "true" ]; then
   restart_policy="--restart=no"
@@ -99,40 +107,37 @@ else
                   awk -F: '{ print $3 }';`
 fi
 
+# From https://stackoverflow.com/a/44660519/702738
 compare_version() {
-    declare -a ver_a
-    declare -a ver_b
-    IFS=. read -a ver_a <<< "$1"
-    IFS=. read -a ver_b <<< "$2"
-
-    while [[ -n $ver_a ]]; do
-        if (( ver_a > ver_b )); then
-            return 0
-        elif (( ver_b > ver_a )); then
+    if [[ $1 == $2 ]]; then
+        return 1
+    fi
+    local IFS=.
+    local i a=(${1%%[^0-9.]*}) b=(${2%%[^0-9.]*})
+    local arem=${1#${1%%[^0-9.]*}} brem=${2#${2%%[^0-9.]*}}
+    for ((i=0; i<${#a[@]} || i<${#b[@]}; i++)); do
+        if ((10#${a[i]:-0} < 10#${b[i]:-0})); then
             return 1
-        else
-            unset ver_a[0]
-            ver_a=("${ver_a[@]}")
-            unset ver_b[0]
-            ver_b=("${ver_b[@]}")
+        elif ((10#${a[i]:-0} > 10#${b[i]:-0})); then
+            return 0
         fi
     done
-    return 1  # They are equal
+    if [ "$arem" '<' "$brem" ]; then
+        return 1
+    elif [ "$arem" '>' "$brem" ]; then
+        return 0
+    fi
+    return 1
 }
 
 
 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 a recent Ubuntu Server, try the following:"
-  echo "sudo apt-get install docker-engine"
-
+  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
 }
 
-prereqs() {
+check_prereqs() {
 
   if [ -z $docker_path ]; then
     install_docker
@@ -147,15 +152,15 @@ prereqs() {
     exit 1
   fi
 
-  # 2. running aufs or btrfs
-  test=`$docker_path info 2> /dev/null | grep 'Driver: '`
-  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"
+  # 2. running an approved storage driver?
+  if ! $docker_path info 2> /dev/null | egrep -q '^Storage Driver: (aufs|btrfs|zfs|overlay|overlay2)$'; then
+    echo "Your Docker installation is not using a supported storage driver.  If we were to proceed you may have a broken install."
+    echo "aufs is the recommended storage driver, although zfs/btrfs/overlay and overlay2 may work as well."
+    echo "Other storage drivers are known to be problematic."
+    echo "You can tell what filesystem you are using by running \"docker info\" and looking at the 'Storage Driver' line."
     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."
+    echo "If you wish to continue anyway using your existing unsupported storage driver,"
+    echo "read the source code of launcher and figure out how to bypass this check."
     exit 1
   fi
 
@@ -163,13 +168,13 @@ prereqs() {
   test=($($docker_path --version))  # Get docker version string
   test=${test[2]//,/}  # Get version alone and strip comma if exists
 
-  # 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
@@ -184,7 +189,6 @@ prereqs() {
     echo
     echo "Please be patient"
     echo
-
   fi
 
   # 5. running recommended git version
@@ -211,72 +215,28 @@ prereqs() {
     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."
+  # 7. enough space for the bootstrap on docker folder
+  folder=`$docker_path info --format '{{.DockerRootDir}}'`
+  safe_folder=${folder:-/var/lib/docker}
+  test=$(($(stat -f --format="%a*%S" $safe_folder)/1024**3 < 5))
+  if [[ $test -ne 0 ]] ; then
+    echo "You have less than 5GB of free space on the disk where $safe_folder is located. You will need more space to continue"
+    df -h $safe_folder
     echo
-    echo "Your system may not work properly, or future upgrades of Discourse may"
-    echo "not complete successfully."
+    read -p "Would you like to attempt to recover space by cleaning docker images and containers in the system?(y/N)" -n 1 -r
     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"
+    if [[ $REPLY =~ ^[Yy]$ ]]
+    then
+      $docker_path system prune -af
+      echo "If the cleanup was successful, you may try again now"
     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."
-    echo
-    echo "Run \`apt-get autoremove && apt-get autoclean\` to clean up unused packages and \`./launcher cleanup\` to remove stale Docker containers."
     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 [ -z "$SKIP_PREREQS" ] ; then
-  prereqs
+if [ -z "$SKIP_PREREQS" ] && [ "$command" != "cleanup" ]; then
+  check_prereqs
 fi
 
 host_run() {
@@ -320,11 +280,31 @@ 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)
 
@@ -361,7 +341,12 @@ set_template_info() {
     puts env.map{|k,v| "-e\n#{k}=#{v}" }.join("\n")
 RUBY
 
-    raw=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
+    tmp_input_file=$(mktemp)
+
+    echo "$input" > "$tmp_input_file"
+    raw=`exec cat "$tmp_input_file" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
+
+    rm -f "$tmp_input_file"
 
     env=()
     ok=1
@@ -369,15 +354,105 @@ RUBY
       if [ "$i" == "*ERROR." ]; then
         ok=0
       elif [ -n "$i" ]; then
-        env[${#env[@]}]=$i
+        env[${#env[@]}]="${i//\{\{config\}\}/${config}}"
       fi
     done <<< "$raw"
 
     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
+
+    # labels
+    read -r -d '' labels_ruby << 'RUBY'
+    require 'yaml'
+
+    input=STDIN.readlines.join
+    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
+
+    tmp_input_file=$(mktemp)
+
+    echo "$input" > "$tmp_input_file"
+    raw=`exec cat "$tmp_input_file" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$labels_ruby"`
+
+    rm -f "$tmp_input_file"
+
+    labels=()
+    ok=1
+    while read i; do
+      if [ "$i" == "*ERROR." ]; then
+        ok=0
+      elif [ -n "$i" ]; then
+        labels[${#labels[@]}]=$(echo $i | sed s/{{config}}/${config}/g)
+      fi
+    done <<< "$raw"
+
+    if [ "$ok" -ne 1 ]; then
+      echo "${labels[@]}"
+      echo "YAML syntax error. Please check your containers/*.yml config files."
+      exit 1
+    fi
+
+    # expose
+    read -r -d '' ports_ruby << 'RUBY'
+    require 'yaml'
+
+    input=STDIN.readlines.join
+    ports = []
+    input.split('_FILE_SEPERATOR_').each do |yml|
+       yml.strip!
+       begin
+         ports += (YAML.load(yml)['expose'] || [])
+       rescue Psych::SyntaxError => e
+        puts e
+        puts "*ERROR."
+       rescue => e
+        puts yml
+        p e
+       end
+    end
+    puts ports.map { |p| p.to_s.include?(':') ? "-p\n#{p}" : "--expose\n#{p}" }.join("\n")
+RUBY
+
+    tmp_input_file=$(mktemp)
+
+    echo "$input" > "$tmp_input_file"
+    raw=`exec cat "$tmp_input_file" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$ports_ruby"`
+
+    rm -f "$tmp_input_file"
+
+    ports=()
+    ok=1
+    while read i; do
+      if [ "$i" == "*ERROR." ]; then
+        ok=0
+      elif [ -n "$i" ]; then
+        ports[${#ports[@]}]=$i
+      fi
+    done <<< "$raw"
+
+    if [ "$ok" -ne 1 ]; then
+      echo "${ports[@]}"
+      echo "YAML syntax error. Please check your containers/*.yml config files."
+      exit 1
+    fi
+
+   merge_user_args
 }
 
 if [ -z $docker_path ]; then
@@ -385,34 +460,31 @@ if [ -z $docker_path ]; then
 fi
 
 [ "$command" == "cleanup" ] && {
-  echo
-  echo "The following command will"
-  echo "- Delete all docker images for old containers"
-  echo "- Delete all stopped and orphan containers"
-  echo
-  read -p "Are you sure (Y/n): " -n 1 -r && echo
-  if [[ $REPLY =~ ^[Yy]$ || ! $REPLY ]]
-    then
-      space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
-      echo "Starting Cleanup (bytes free $space)"
-
-      STATE_DIR=./.gc-state scripts/docker-gc
+  $docker_path system prune -a
 
-      space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
-      echo "Finished Cleanup (bytes free $space)"
+  if [ -d /var/discourse/shared/standalone/postgres_data_old ]; then
+    echo
+    echo "Old PostgreSQL backup data cluster detected taking up $(du -hs /var/discourse/shared/standalone/postgres_data_old | awk '{print $1}') detected"
+    read -p "Would you like to remove it? (Y/n): " -n 1 -r && echo
 
+    if [[ $REPLY =~ ^[Yy]$ ]]; then
+      echo "removing old PostgreSQL data cluster at /var/discourse/shared/standalone/postgres_data_old..."
+      rm -rf /var/discourse/shared/standalone/postgres_data_old
     else
       exit 1
+    fi
   fi
+
   exit 0
 }
 
-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 "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
     exit 1
+  fi
 fi
 
 docker_version=($($docker_path --version))
@@ -443,7 +515,9 @@ 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
+  if [ -n "$user_run_image" ]; then
+    run_image=$user_run_image
+  elif [ -z "$run_image" ]; then
     run_image="$local_discourse/$config"
   fi
 }
@@ -463,96 +537,43 @@ set_boot_command() {
   fi
 }
 
-scale_ram_and_cpu() {
-
-  # grab info about total system ram and physical (NOT LOGICAL!) CPU cores
-  avail_mem="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
-  avail_gb=$(( $avail_mem / 950 ))
-  avail_cores=`cat /proc/cpuinfo | grep "cpu cores" | uniq | awk '{print $4}'`
-  echo "Found ${avail_gb}GB of memory and $avail_cores physical CPU cores"
-
-  # db_shared_buffers: 128MB for 1GB, 256MB for 2GB, or 256MB * GB, max 4096MB
-  if [ "$avail_gb" -eq "1" ]
-  then
-    db_shared_buffers=128
-  else
-    if [ "$avail_gb" -eq "2" ]
-    then
-      db_shared_buffers=256
-    else
-      db_shared_buffers=$(( 256 * $avail_gb ))
-    fi
-  fi
-  db_shared_buffers=$(( db_shared_buffers < 4096 ? db_shared_buffers : 4096 ))
-
-  sed -i -e "s/^  #db_shared_buffers:.*/  db_shared_buffers: \"${db_shared_buffers}MB\"/w $changelog" $config_file
-  if [ -s $changelog ]
-  then
-    echo "setting db_shared_buffers = ${db_shared_buffers}MB based on detected CPU/RAM"
-    rm $changelog
-  fi
-
+merge_user_args() {
+  local docker_args
 
-  # UNICORN_WORKERS: 2 * GB for 2GB or less, or 2 * CPU, max 8
-  if [ "$avail_gb" -le "2" ]
-  then
-    unicorn_workers=$(( 2 * $avail_gb ))
-  else
-    unicorn_workers=$(( 2 * $avail_cores ))
-  fi
-  unicorn_workers=$(( unicorn_workers < 8 ? unicorn_workers : 8 ))
+  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']"`
 
-  sed -i -e "s/^  #UNICORN_WORKERS:.*/  UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $config_file
-  if [ -s $changelog ]
-  then
-      echo "setting UNICORN_WORKERS = ${unicorn_workers} based on detected CPU/RAM"
-      rm $changelog
+  if [[ -n "$docker_args" ]]; then
+    user_args="$user_args $docker_args"
   fi
-
 }
 
 run_start() {
 
-   existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
-   echo $existing
-   if [ ! -z $existing ]
+   if [ -z "$START_CMD_ONLY" ]
    then
-     echo "Nothing to do, your container has already started!"
-     exit 0
-   fi
+     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 0
+     fi
 
-   existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
-   if [ ! -z $existing ]
-   then
-     echo "starting up existing container"
-     (
-       set -x
-       $docker_path start $config
-     )
-     exit 0
+     existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
+     if [ ! -z $existing ]
+     then
+       echo "starting up existing container"
+       (
+         set -x
+         $docker_path start $config
+       )
+       exit 0
+     fi
    fi
 
    host_run
 
-  if [ -z "$SKIP_DISCOURSE_PREREQS" ] ; then
-    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
-  fi
-
-   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
@@ -582,13 +603,21 @@ run_start() {
      # 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/_/-}
+     hostname=${hostname//_/-}
 
-     mac_address="--mac-address $(echo $hostname | $docker_path run $user_args -i --rm -a stdout -a stderr $image /bin/sh -c "md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'")"
+
+     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
+
+     if [ ! -z "$START_CMD_ONLY" ] ; then
+       docker_path="true"
+     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 $mac_address $docker_args \
+
+     $docker_path run --shm-size=512m $links $attach_on_run $restart_policy "${env[@]}" "${labels[@]}" -h "$hostname" \
+        -e DOCKER_HOST_IP="$docker_ip" --name $config -t "${ports[@]}" $volumes $mac_address $user_args \
         $run_image $boot_command
 
    )
@@ -596,46 +625,22 @@ run_start() {
 
 }
 
-valid_config_check() {
+run_run() {
+  set_template_info
+  set_volumes
+  set_links
+  set_run_image
 
-  valid_config="y"
-  for x in DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD \
-           DISCOURSE_DEVELOPER_EMAILS DISCOURSE_HOSTNAME
-  do
-    mail_var=`grep "^  $x:" $config_file`
-    local result=$?
-    local default="example.com"
-    if (( result == 0 ))
-    then
-      if [[ $mail_var = *"$default"* ]]
-      then
-        echo "Warning: $x left at incorrect default of example.com"
-        valid_config="n"
-      fi
-    else
-      echo "Warning: $x not configured"
-      valid_config="n"
-    fi
-  done
-  if [ -t 0 ] && [ "$valid_config" != "y" ]; then
-    echo
-    read -p "Press Ctrl-C to exit and edit $config_file or ENTER to continue"
+  unset ERR
+  (exec $docker_path run --rm --shm-size=512m $user_args $links "${env[@]}" -e DOCKER_HOST_IP="$docker_ip" -i -a stdin -a stdout -a stderr $volumes $run_image \
+    /bin/bash -c "$run_command") || ERR=$?
+
+  if [[ $ERR > 0 ]]; then
+    exit 1
   fi
 }
 
 run_bootstrap() {
-  if [ -z "$SKIP_DISCOURSE_PREREQS" ] ; then
-    # Does your system meet the minimum requirements?
-    check_resources
-
-    # is our configuration file valid?
-    valid_config_check
-
-    # make minor scaling adjustments for RAM and CPU
-    scale_ram_and_cpu
-  fi
-
-  # I got no frigging clue what this does, ask Sam Saffron. It RUNS STUFF ON THE HOST I GUESS?
   host_run
 
   # Is the image available?
@@ -667,11 +672,37 @@ run_bootstrap() {
 
   echo $run_command
 
-  (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)
+  unset ERR
+
+  tmp_input_file=$(mktemp)
+
+  echo "$input" > "$tmp_input_file"
+  (exec cat "$tmp_input_file" | $docker_path run --shm-size=512m $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=$?
+
+  rm -f "$tmp_input_file"
+
+  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
+
+  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
 
@@ -679,8 +710,6 @@ run_bootstrap() {
   $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
 }
 
-
-
 case "$command" in
   bootstrap)
       run_bootstrap
@@ -688,6 +717,11 @@ case "$command" in
       exit 0
       ;;
 
+  run)
+      run_run
+      exit 0
+      ;;
+
   enter)
       exec $docker_path exec -it $config /bin/bash --login
       ;;
@@ -709,6 +743,12 @@ case "$command" in
       exit 0
       ;;
 
+  start-cmd)
+    START_CMD_ONLY="1"
+    run_start
+    exit 0;
+    ;;
+
   start)
       run_start
       exit 0
@@ -716,7 +756,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
 
@@ -725,18 +765,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