mbland/custom-links

View on GitHub
scripts/serve

Summary

Maintainability
Test Coverage
#! /usr/local/env bash
#
# Runs a local Custom Links server
#
# Usage:
#   {{go}} {{cmd}} [<config-file>]
#
# Where:
#   <config-file>  configuration file; {{root}}/test-config.json by default
#
# Environment variables:
#   CUSTOM_LINKS_REDIS_PORT      Port on which redis-server will run/is running
#   CUSTOM_LINKS_REDIS_DIR       Directory in which redis-server will save files
#   CUSTOM_LINKS_REDIS_LOG_PATH  Log file for automatically-started redis-server
#   CUSTOM_LINKS_REDIS_TIMEOUT   Max seconds to wait for redis to start
#
# If redis-server is already running, it must be running on either the default
# port or on the port specified by CUSTOM_LINKS_REDIS_PORT (or by REDIS_PORT in
# the <config-file>; jq must be installed to read it from the <config-file>.)
#
# If redis-server isn't already running and CUSTOM_LINKS_REDIS_HOST is not set,
# redis-server will be started automatically, either on the default port or
# CUSTOM_LINKS_REDIS_PORT. redis-server log output will then stream to either
# CUSTOM_LINKS_REDIS_LOG_PATH or {{root}}/redis.log by default. (This variable
# cannot be defined in the <config-file>.)

export CUSTOM_LINKS_CONFIG_PATH="${CUSTOM_LINKS_CONFIG_PATH:-test-config.json}"
export CUSTOM_LINKS_REDIS_LOG_PATH="${CUSTOM_LINKS_REDIS_LOG_PATH:-redis.log}"

. "$_GO_USE_MODULES" 'log' 'config-json'

cl.serve_tab_completion() {
  local word_index="$1"
  shift
  local args=("$@")
  local word="${args[$word_index]}"

  if [[ "$word_index" -eq '0' ]]; then
    @go.compgen -f -- "$word"
  fi
}

cl.serve_ensure_redis_is_running() {
  local server_config="${1:-$CUSTOM_LINKS_CONFIG_PATH}"
  local redis_host
  local redis_port

  cl.get_config_variable "$server_config" 'REDIS_HOST' 'redis_host'
  cl.get_config_variable "$server_config" 'REDIS_PORT' 'redis_port' '6379'

  if [[ -n "$redis_host" ]] ||
    pgrep -f "redis-server .*:$redis_port" >/dev/null 2>&1; then
    cl.serve_wait_for_redis "$redis_host" "$redis_port"
  else
    cl.serve_launch_redis "$redis_port"
  fi
}

cl.serve_launch_redis() {
  local redis_port="$1"
  local redis_pid

  @go.log RUN Launching redis-server on port "$redis_port"
  args=('redis-server' '--port' "$redis_port" '--appendonly' 'yes')
  args+=('--dir' "${CUSTOM_LINKS_REDIS_DIR:-$_GO_ROOTDIR}")

  # Setting monitor mode launches redis-server in a separate process group,
  # preventing signals sent to this process from terminating it.
  set -m
  "${args[@]}" >>"$CUSTOM_LINKS_REDIS_LOG_PATH" 2>&1 &
  set +m
  redis_pid="$!"

  if ! cl.serve_wait_for_redis '' "$redis_pid"; then
    kill "$redis_pid" >/dev/null 2>&1
    @go.log FATAL Failed to launch redis-server
  fi
  trap "cl.serve_exit_trap $redis_pid $redis_port" EXIT
  @go.log INFO redis-server running as PID "$redis_pid"
}

cl.serve_wait_for_redis() {
  local redis_host="${1:-localhost}"
  local redis_pid="$2"
  local timeout="${CUSTOM_LINKS_REDIS_TIMEOUT:-5}"

  @go.log INFO "Waiting for Redis server at ${redis_host:-*}:${redis_port}"
  # Sleep for a quarter-second as this is enough for small installations.
  sleep 0.25

  while ! nc -z "$redis_host" "$redis_port" >/dev/null 2>&1; do
    if [[ "$((--timeout))" -lt '0' ]]; then
      return 1
    fi
    sleep 1
  done
}

cl.serve_exit_trap() {
  local redis_pid="$1"
  local redis_port="$2"

  @go.log RUN Shutting down redis-server
  redis-cli -p "$redis_port" shutdown save
  wait "$redis_pid"
  @go.log INFO redis-server shutdown complete
}

cl.serve_run_custom_links_server() {
  local server_pid

  @go.log RUN Launching custom-links server
  node "$_GO_ROOTDIR/index.js" "$server_config" &
  server_pid="$!"

  if ! kill -0 "$server_pid" 2>/dev/null; then
    @go.log FATAL Failed to launch custom-links server
  fi
  @go.log INFO custom-links server running as pid "$server_pid"

  # Inspired by: https://veithen.github.io/2014/11/16/sigterm-propagation.html
  trap "kill -TERM $server_pid" TERM INT HUP
  wait "$server_pid"
  trap - TERM INT HUP
  wait "$server_pid"
  @go.log INFO custom-links server shutdown complete
}

cl.serve() {
  local server_config="$1"

  case "$1" in
  --complete)
    # Tab completions
    shift
    cl.serve_tab_completion "$@"
    return
    ;;
  esac

  cl.serve_ensure_redis_is_running "$server_config"
  cl.serve_run_custom_links_server "$server_config"
}

cl.serve "$@"