#!/usr/bin/env ruby
#
# Wrapping script to launch both nginx and unicorn for Helix Web Services.
#

require 'hws_settings'
require 'socket'
require 'fileutils'

#=============================================================================
# Primary Helper Methods
#=============================================================================

# If the nginx server is not running, start it.
#
# Files created /var/run/perforce/helix-web-services/:
#
# - nginx.pid
# - nginx.conf
#
def start_nginx
  create_nginx_config
  spawn_nginx
end

# If the unicorn server is not running, start it.
#
# This should block until unicorn is ready to respond.
def start_unicorn
  create_unicorn_config
  spawn_unicorn
end

def stop_nginx
  if process_running?(nginx_pid)
    Process.kill('QUIT', nginx_pid)
  end
end

def stop_unicorn
  if process_running?(unicorn_pid)
    Process.kill('QUIT', unicorn_pid)
  end
end

# This should let you know what processes we're running nginx and unicorn at.
# Or if they're shut down.
def check_status
  if process_running?(nginx_pid)
    puts 'nginx running'
  else
    puts 'nginx not running'
  end
  if process_running?(unicorn_pid)
    puts 'unicorn running'
  else
    puts 'unicorn not running'
  end
end

# This will pause until the local nginx server is capable of returning
# responses from the web application.
def wait_for_response
  attempts = 150 # wait up to 15 seconds
  connected = false
  while !(connected = connect_to_server(HWSSettings.system.NGINX_PORT))
    sleep(0.1)
  end
  connected
end

def connect_to_server(port)
  begin
    s = TCPSocket.new 'localhost', port
    s.close
    return true
  rescue Exception => e
    return false
  end
end

#=============================================================================
# Secondary Helper Methods
#=============================================================================
# These are methods used by the primary methods but aren't really the main API

def spawn_nginx
  return if process_running?(nginx_pid)

  # Note: this variation of Process.spawn() does *not* execute within a
  # standard shell
  nginx_pid = Process.spawn(HWSSettings.system.NGINX_COMMAND,
                            '-c',
                            HWSSettings.system.NGINX_CONFIG_PATH)

  # Note: We set the pid file via config, so this PID may not be the master
  # process ID
  Process.detach(nginx_pid)
end

def spawn_unicorn
  # Note: this launch PID is *not* the unicorn master pid, which is set in
  # the config file process.
  launch_pid = Process.spawn(HWSSettings.system.UNICORN_COMMAND,
                             '-D',
                             '-c',
                             HWSSettings.system.UNICORN_CONFIG_PATH,
                             HWSSettings.system.RACKUP_CONFIG)

  Process.detach(launch_pid)
end

def nginx_pid
  read_pid(nginx_pid_file)
end

def nginx_pid_file
  File.join(HWSSettings.system.RUN_DIR, 'nginx.pid')
end

def unicorn_pid
  read_pid(unicorn_pid_file)
end

def read_pid(path)
  if File.exist?(path)
    IO.read(path).strip.to_i
  else
    nil
  end
end

def unicorn_pid_file
  File.join(HWSSettings.system.RUN_DIR, 'unicorn.pid')
end

# Note: sending signal 0 basically doesn't do anything, but validates that the
# process exists (and we have access to it)
def process_running?(pid)
  return false if pid.nil?
  begin
    Process.kill(0, pid)
    true
  rescue Errno::ESRCH
    false
  end
end

#=============================================================================
# Local Config File Generation
#=============================================================================
# Both Unicorn and Nginx configuration are tuned for HWS only usage. We do
# expose some parameters through the system configuration file.

def create_nginx_config
  IO.write(HWSSettings.system.NGINX_CONFIG_PATH, <<-END.gsub(/^[ ]{4}/, '')
    pid #{nginx_pid_file};
    error_log #{HWSSettings.system.LOG_DIR}/nginx-error.log info;
    worker_processes 10;
    events {
      worker_connections #{HWSSettings.system.NGINX_WORKER_CONNECTIONS};
    }
    http {
      log_format   main '$remote_addr - $remote_user [$time_local]  $status '
          '"$request" $body_bytes_sent "$http_referer" '
          '"$http_user_agent" "$http_x_forwarded_for"';
      access_log #{HWSSettings.system.LOG_DIR}/nginx-access.log;
      upstream unicorn_server {
        server #{HWSSettings.system.UNICORN_CONNECTION};
      }
      server {
        listen #{HWSSettings.system.NGINX_PORT};
        #{nginx_ssl_settings}
        location / {
          proxy_pass http://unicorn_server;
        }
      }
    }
  END
  )
end

def nginx_ssl_settings
  if HWSSettings.system.ENABLE_HTTPS
    <<-END.gsub(/^[ ]{4}/, '')
      ssl on;
      ssl_certificate #{HWSSettings.system.SSL_CERTIFICATE_PATH};
      ssl_certificate_key #{HWSSettings.system.SSL_CERTIFICATE_KEY_PATH};
    END
  else
    ''
  end
end

def create_unicorn_config
  IO.write(HWSSettings.system.UNICORN_CONFIG_PATH, <<-END.gsub(/^[ ]{4}/, '')

    listen "#{HWSSettings.system.UNICORN_CONNECTION}"

    pid "#{unicorn_pid_file}"

    stdout_path "#{HWSSettings.system.LOG_DIR}/unicorn.log"
    stderr_path "#{HWSSettings.system.LOG_DIR}/unicorn.log"

    timeout #{HWSSettings.system.UNICORN_TIMEOUT}

    user "#{HWSSettings.system.SYSTEM_USER}"

    worker_processes #{HWSSettings.system.UNICORN_WORKER_PROCESSES}
  END
  )
  FileUtils.chown(HWSSettings.system.SYSTEM_USER,
                  HWSSettings.system.SYSTEM_GROUP,
                  HWSSettings.system.UNICORN_CONFIG_PATH
  )
end

#=============================================================================
# Parse the command line and start
#=============================================================================

require 'thor'

class CLI < Thor
  desc 'hws_launch start', 'Launch both nginx and unicorn'

  def start
    start_unicorn
    start_nginx
    wait_for_response
  end

  desc 'hws_launch stop', 'Stop nginx and unicorn'

  def stop
    stop_unicorn
    stop_nginx
  end

  desc 'hws_launch restart', 'Restart nginx and unicorn'

  def restart
    stop
    start
  end

  desc 'hws_launch status', 'Report status of nginx and unicorn'

  def status
    check_status
  end
end

CLI.start(ARGV)