#!/bin/sh

if [ -n "${NO_VM}" ]; then

  shared_dir="."
  # On first boot, copy scripts to a read-only dir
  if [ ! -d vm-manager ]; then
    tar xzf vm-manager.tar.gz
  fi

else

  shared_dir="shared"
  # On first boot, copy scripts to a read-only dir
  if [ ! -d scripts ]; then
    mkdir scripts
    cp -r shared/main.js shared/proxy.js shared/node_modules scripts
  fi

  cd /root

  # Set system DNS server for docker pull support
  echo 'nameserver 172.17.0.1' > /etc/resolv.conf

  mkdir -p scratch/vm/ca/certs
  mkdir -p scratch/vm/ca/newcerts
  mkdir -p scratch/vm/ca/private

  cp shared/openssl.cnf scratch/vm/ca

  # Set up the local CA for certificate management
  cd scratch/vm/ca
  if [ ! -f certs/ca.cert.pem ]; then
    openssl genrsa -out private/ca.key.pem 4096
    chmod 444 private/ca.key.pem
    openssl req -config openssl.cnf -key private/ca.key.pem -new -x509 -days 365 -sha256 -extensions v3_ca -out certs/ca.cert.pem -subj "/CN=*.charityengine.com/O=Proxy/C=US/ST=Massachusetts"
    chmod 444 certs/ca.cert.pem
  fi
  cp certs/ca.cert.pem /usr/local/share/ca-certificates/
  ln -s /usr/local/share/ca-certificates/ca.cert.pem /etc/ssl/certs/
  cd /etc/ssl/certs
  ln -s ca.cert.pem `openssl x509 -noout -hash -in /usr/local/share/ca-certificates/ca.cert.pem`.0
  cp ca-certificates.crt ca-certificates.crt.backup
  cat /usr/local/share/ca-certificates/ca.cert.pem >> ca-certificates.crt

  cd /root

  # Restart the docker daemon... no containers should be running yet!
  echo "restarting docker service"
  /etc/init.d/docker stop

  # Slow down docker pull commands to prevent timeouts
  cat > /var/lib/boot2docker/profile << EOL
  EXTRA_ARGS="--max-concurrent-downloads 1"
EOL

  /etc/init.d/docker start

  sleep 5

fi

if [ ! -d start ]; then
  mkdir start
fi

# Generate a unique dir as multiple containers can run in parallel
tmp_dir=$(mktemp -d -p /ce-client/ CE-XXXXXXXX)
mkdir ${tmp_dir}/input
mkdir ${tmp_dir}/output

if [ -z "${NO_VM}" ]; then
  # VM runs this as root, so make tmp_dir writeable
  chmod -R +rwx ${tmp_dir}
fi

# Set up the docker image cache, if needed
arch=$(uname -m)
mkdir -p scratch/${arch}

echo "launching vm-manager"
if [ -n "${NO_VM}" ]; then
  cd vm-manager
  node main.js --data-folder ${tmp_dir} --job-folder ../start/ --shared-folder ../ --proxy false > ../vm-manager.log 2>&1 &
  cd ..
else
  if [ "$(docker ps -aq -f status=exited -f name=primary)" ]; then
    # Remove the primary container if it already exists
    docker rm primary
  fi
  docker load < scratch/${arch}/node-12-alpine.docker
  docker run -td -p 172.17.0.1:53:53/udp -p 172.17.0.1:80:3646 -p 172.17.0.1:443:3647 -v /root/scratch:/root/scratch -v /root/shared:/root/shared -v /root/scripts:/root/scripts:ro -v ${tmp_dir}:/local -v /root/start:/root/start --name='primary' node:12-alpine
  docker exec -d primary sh -c 'cd /root/scripts; node main.js > /root/shared/main.js.log 2>&1'
fi

#
# Monitor the "start" directory for new containers to run
#

# Set args that will be passed to every container that is run
docker_args="-v ${tmp_dir}:/local"
if [ -n "${NO_VM}" ]; then
  docker_args="${docker_args} --runtime=nvidia"
  if [ -n "${NVIDIA_VISIBLE_DEVICES}" ]; then
    docker_args="${docker_args} -e NVIDIA_VISIBLE_DEVICES=${NVIDIA_VISIBLE_DEVICES}"
  fi
else
  docker_args="${docker_args} --dns=172.17.0.1 -v /etc/ssl/certs:/etc/ssl/certs:ro -v /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro"
fi
# Check the start directory every "sleeptime" seconds
sleeptime=5
# The config file that contains runtime for the VM
config_file="${shared_dir}/state.default.json"

# Attempt to read from config the total time (seconds) to continue looping
runtime=$(sed -nE 's/.*"timeLimit":([0-9]*).*/\1/p' ${config_file})
if [ "${runtime}" = "" ]; then
  runtime=7200
fi

timeout=$((runtime/sleeptime))
echo "ready - checking start directory every ${sleeptime} seconds for new jobs"
while [ ${timeout} -gt 0 ]
do
  if [ -f completion_trigger_file ]; then
    echo "vm-manager exited"
    rm completion_trigger_file
    break
  fi

  sleep "${sleeptime}"
  for file in start/*; do
    container=""
    url=""
    imagefile=""
    command=""
    name=""
    image_exists=""
    exists_now=""
    [ -e ${file} ] || continue
    echo "new start file found: ${file}"
    while read -r line || [ -n "${line}" ]; do
      if [ "${container}" = "" ]; then
        container=$( echo "${line}" | cut -d " " -f 1 )
        url=$( echo "${line}" | cut -s -d " " -f 2 )
        imagefile=$( echo "${container}" | sed -r 's/\:/\-/g' | sed -r 's/\//__/g' ).docker
        image_exists=$( test -f scratch/${arch}/${imagefile} && echo "true" || echo "" )
        if [ "${url}" != "" ]; then
          # Use curl to determine if a file needs to be downloaded
          if [ "${image_exists}" = "" ]; then
            echo "downloading ${container} from ${url} and loading into docker..."
            curl --silent -o "scratch/${arch}/${imagefile}" "${url}"
            exists_now=$( test -f scratch/${arch}/${imagefile} && echo "true" || echo "" )
          else
            local_mod_time=$(date -r "scratch/${arch}/${imagefile}" +%s)
            remote_mod_time=$(date -D "%a, %d %b %Y %H:%M:%S %Z" -d "$(curl --silent -I ${url} | grep -i Last-Modified | sed -r 's/Last\-Modified: //gi')" +%s)
            time_diff=$((${remote_mod_time} - ${local_mod_time}))
            if [ "${time_diff}" -gt 0 ]; then
              echo "updating ${container} from ${url} and loading into docker..."
              curl --silent -o "scratch/${arch}/${imagefile}" "${url}"
            else
              echo "${container} is up-to-date; loading cached image into docker..."
            fi
          fi
          docker load < "scratch/${arch}/${imagefile}"
        else
          # Retrieve from dockerhub, if missing
          if [ "${image_exists}" = "" ]; then
            echo "image not found locally"
            echo "pulling ${container} from dockerhub..."
            docker pull ${container}
            exists_now=$( docker images --quiet ${container} )
            if [ "${exists_now}" != "" ]; then
              echo "adding ${container} to persistent local docker image cache"
              docker save -o "scratch/${arch}/${imagefile}" "${container}"
              #tar -czf scratch/vm/docker-cache.tar.gz -C /var/lib/docker .
            else
              echo "failed to load ${container}!"
            fi
          fi
        fi
      elif [ "${command}" = "" ]; then
        command="${line}"
      fi
    done < "${file}"
    if [ "${container}" != "" ]; then
      if [ "${command}" != "" ]; then
        if [ "${image_exists}" != "" ] || [ "${exists_now}" != "" ]; then
          name=${file##start\/}
          logname=${name}
          name=${name}$(dd if=/dev/urandom count=1 2> /dev/null | cksum | cut -f1 -d" ")
          # Check for an Entrypoint; if not found, use /bin/sh to launch the
          # command in the container
#          if tar --wildcards -Ox "*.json" -f "scratch/${arch}/${imagefile}" | grep -q ENTRYPOINT || \
#             tar --wildcards -Ox "*.json" -f "scratch/${arch}/${imagefile}" | grep -q CMD; then
#            echo "launching image: docker run --rm ${docker_args} --name='${name}' ${container} \"${command}\""
#            docker run --rm ${docker_args} --name=${name} ${container} "${command}" >> ${shared_dir}/${logname}.log 2>&1
#          else
            echo "launching image: docker run --rm ${docker_args} --name='${name}' ${container} /bin/sh -c \"${command}\""
            docker run --rm ${docker_args} --name=${name} ${container} /bin/sh -c "${command}" >> ${shared_dir}/${logname}.log 2>&1
            #echo "executing command: docker exec -it ${name} ${command}"
            #docker exec -it ${name} ${command}
#          fi
          #docker rm ${name}
          rm "${file}"
        else
          echo "no container found to run; retrying ${file}"
        fi
      else
        echo "could not parse command; aborting ${file}"
        rm "${file}"
      fi
    else
      echo "could not parse container name; aborting ${file}"
      rm "${file}"
    fi
  done
  timeout=$((timeout-1))
done
