From fae818d8506d61a390d5a00f923b572a9c85a2bd Mon Sep 17 00:00:00 2001 From: Jason Jones <43227214+Jasonej@users.noreply.github.com> Date: Thu, 31 Oct 2024 14:25:34 -0400 Subject: [PATCH] Add hop proxy command (#58) * update: add hop proxy command * update: output a proxy URL link * fix: option entry not shifting past value * fix: uncomment actual proxy command * style: fix some lint issues * style: fix more lint issues * style: fix more lint issues * style: fix more lint issues * update: allow selection of specific host when multiple domains are defined in VIRTUAL_HOST --- bin/_helpers | 116 ++++++++++++++++++++++++ bin/hop | 76 ++-------------- bin/proxy | 250 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 375 insertions(+), 67 deletions(-) create mode 100644 bin/_helpers create mode 100755 bin/proxy diff --git a/bin/_helpers b/bin/_helpers new file mode 100644 index 0000000..33caa48 --- /dev/null +++ b/bin/_helpers @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +# +# Script Output +# + +# Determine width of the program +ACTUAL_WIDTH=$(($(tput cols) - 2)) +MAX_WIDTH=100 + +if test "$ACTUAL_WIDTH" -gt "$MAX_WIDTH"; then + WIDTH=$MAX_WIDTH +else + WIDTH=$ACTUAL_WIDTH +fi + +declare BG_R +declare BG_G +declare BG_Y +declare BG_B +declare BG_M +declare BG_C +declare BG_N + +declare FG_R +declare FG_G +declare FG_Y +declare FG_B +declare FG_M +declare FG_C +declare FG_N + +declare BOLD +declare RESET + +# Setup Color Outputs +if test -t 1; then + ncolors=$(tput colors) + + if test -n "$ncolors" && test "$ncolors" -ge 8; then + COLOR_R=1 # color code - RED + COLOR_G=2 # color code - GREEN + COLOR_Y=3 # color code - YELLOW + COLOR_B=4 # color code - BLUE + COLOR_M=5 # color code - MAGENTA + COLOR_C=6 # color code - CYAN + COLOR_N=246 # color code - NEUTRAL (GRAY) + + BG_R="$(tput setab $COLOR_R)" # Background - RED + BG_G="$(tput setab $COLOR_G)" # Background - GREEN + BG_Y="$(tput setab $COLOR_Y)" # Background - YELLOW + BG_B="$(tput setab $COLOR_B)" # Background - BLUE + BG_M="$(tput setab $COLOR_M)" # Background - MAGENTA + BG_C="$(tput setab $COLOR_C)" # Background - CYAN + BG_N="$(tput setab $COLOR_N)" # Background - NEUTRAL (GRAY) + + FG_R="$(tput setaf $COLOR_R)" # Foreground - RED + FG_G="$(tput setaf $COLOR_G)" # Foreground - GREEN + FG_Y="$(tput setaf $COLOR_Y)" # Foreground - YELLOW + FG_B="$(tput setaf $COLOR_B)" # Foreground - BLUE + FG_M="$(tput setaf $COLOR_M)" # Foreground - MAGENTA + FG_C="$(tput setaf $COLOR_C)" # Foreground - CYAN + FG_N="$(tput setaf $COLOR_N)" # Foreground - NEUTRAL (GRAY) + + BOLD="$(tput bold)" + RESET="$(tput sgr0)" + fi +fi + +function join { + local IFS="$1" + shift + echo "$*" +} + +function tag { + local STYLE=$1 + local LABEL=$2 + shift 2 + + echo "$@" "${STYLE} ${LABEL} ${RESET} " +} + +function output_tagged_string() { + tag "$1" "$2" -n + echo "$3" +} + +function output_error { + output_tagged_string "$BG_R" "ERROR" "$1" +} + +function output_info { + output_tagged_string "$BG_B" "INFO" "$1" +} + +export BG_R +export BG_G +export BG_Y +export BG_B +export BG_M +export BG_C +export BG_N + +export FG_R +export FG_G +export FG_Y +export FG_B +export FG_M +export FG_C +export FG_N + +export BOLD +export RESET + +export WIDTH diff --git a/bin/hop b/bin/hop index 2701bdd..4d9e59d 100755 --- a/bin/hop +++ b/bin/hop @@ -1,61 +1,9 @@ #!/usr/bin/env bash -HOP_PATH=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +HOP_PATH=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd) TOOLKIT_PATH=$(dirname "$HOP_PATH") -# -# Script Output -# - -# Determine width of the program -ACTUAL_WIDTH=$(($(tput cols) - 2)) -MAX_WIDTH=100 - -if test "$ACTUAL_WIDTH" -gt "$MAX_WIDTH"; then - WIDTH=$MAX_WIDTH -else - WIDTH=$ACTUAL_WIDTH -fi - -# Setup Color Outputs -if test -t 1; then - ncolors=$(tput colors) - - if test -n "$ncolors" && test "$ncolors" -ge 8; then - COLOR_R=1 # color code - RED - COLOR_G=2 # color code - GREEN - COLOR_Y=3 # color code - YELLOW - COLOR_B=4 # color code - BLUE - COLOR_M=5 # color code - MAGENTA - COLOR_C=6 # color code - CYAN - COLOR_N=246 # color code - NEUTRAL (GRAY) - - BG_R="$(tput setab $COLOR_R)" # Background - RED - BG_G="$(tput setab $COLOR_G)" # Background - GREEN - BG_Y="$(tput setab $COLOR_Y)" # Background - YELLOW - BG_B="$(tput setab $COLOR_B)" # Background - BLUE - #BG_M="$(tput setab $COLOR_M)" # Background - MAGENTA - #BG_C="$(tput setab $COLOR_C)" # Background - CYAN - #BG_N="$(tput setab $COLOR_N)" # Background - NEUTRAL (GRAY) - - #FG_R="$(tput setaf $COLOR_R)" # Foreground - RED - #FG_G="$(tput setaf $COLOR_G)" # Foreground - GREEN - #FG_Y="$(tput setaf $COLOR_Y)" # Foreground - YELLOW - #FG_B="$(tput setaf $COLOR_B)" # Foreground - BLUE - FG_M="$(tput setaf $COLOR_M)" # Foreground - MAGENTA - FG_C="$(tput setaf $COLOR_C)" # Foreground - CYAN - FG_N="$(tput setaf $COLOR_N)" # Foreground - NEUTRAL (GRAY) - - BOLD="$(tput bold)" - RESET="$(tput sgr0)" - fi -fi - -function join { - local IFS="$1" - shift - echo "$*" -} +source "$TOOLKIT_PATH/bin/_helpers" function describe_command { local COMMAND_NAME=$1 @@ -79,19 +27,6 @@ function describe_command { join " " "${SEGMENTS[@]}" "${RESET}" } -function tag { - local STYLE=$1 - local LABEL=$2 - shift 2 - - echo "$@" "${STYLE} ${LABEL} ${RESET} " -} - -function output_error { - tag "$BG_R" "ERROR" -n - echo "$1" -} - EXPLAIN_COUNT=0 function explain { EXPLAIN_COUNT=$((EXPLAIN_COUNT + 1)) @@ -159,6 +94,9 @@ function display_help { describe_command "seed" "hop artisan db:seed" describe_command "test" "hop artisan test" describe_command "tinker" "hop artisan tinker" + echo + tag "$BG_Y$BOLD" "Commands" + describe_command "proxy" "\$@" "Start a proxy for this project." } if [ $# -lt 1 ] || [ "$1" == "help" ] || [ "$1" == "-h" ] || [ "$1" == "-help" ] || [ "$1" == "--help" ]; then @@ -411,6 +349,10 @@ case $1 in shift 1 run_exec_command php vendor/bin/pint ;; + "proxy") + shift 1 + bash "$TOOLKIT_PATH/bin/proxy" "$@" + ;; "ps"|"status") shift 1 run_command ps "$@" diff --git a/bin/proxy b/bin/proxy new file mode 100755 index 0000000..f1015ba --- /dev/null +++ b/bin/proxy @@ -0,0 +1,250 @@ +#!/usr/bin/env bash + +HOP_PATH=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd) +TOOLKIT_PATH=$(dirname "$HOP_PATH") + +LOCAL_DOMAIN= +LOCAL_PORT= +PROXY_DOMAIN= +PROXY_PORT= + +PORT_GEN_MAX=65535 +PORT_GEN_MIN=10002 + +source "$TOOLKIT_PATH/bin/_helpers" + +function describe { + local COMMAND_NAME=$1 + local ARGS_LIST=${*:2:(($# - 2))} + local DESCRIPTION=${!#} + local CONTENT_LENGTH=$((${#COMMAND_NAME} + ${#ARGS_LIST} + ${#DESCRIPTION})) + + local SEGMENTS=() + SEGMENTS+=("${FG_C}${COMMAND_NAME}") + + if test $# -gt 2; then + SEGMENTS+=("${FG_M}${ARGS_LIST}") + fi + + local DOT_COUNT=$((WIDTH - CONTENT_LENGTH - ${#SEGMENTS[@]} - 3 - 1)) # 3 for "hop", 1 for additional space for last segment + local DOTS + DOTS=$(printf '%*s' "$DOT_COUNT" "" | tr ' ' '.') + SEGMENTS+=("${FG_N}${DOTS}") + SEGMENTS+=("${FG_N}${DESCRIPTION}") + + join " " "${SEGMENTS[@]}" "${RESET}" +} + +function display_help() { + tag "$BG_G$BOLD" "Sourcetoad Proxy" + echo "A script that makes it easy to proxy your projects via hop." + echo + tag "$BG_Y$BOLD" "Options" + describe "-h, --help" "Display this help message." + describe "--local-domain" "Overrides the automatically determined local domain." + describe "--local-port" "Overrides the default local port of 80." + describe "--proxy-port" "Specify the port to use for the proxy. Required." + describe "--proxy-domain" "The subdomain to use for the proxy." +} + +function parse_arg() { + local key=$1 + local value=$2 + + if [ "$value" ]; then + echo "$value" + else + output_error "'${key}' requires a non-empty argument." + exit 1 + fi +} + +while :; do + option=${1%%=*} + parsed_value= + + case $1 in + -h|--help) + display_help + exit + ;; + --local-domain|--local-port|--proxy-port|--proxy-domain) + if ! parsed_value=$(parse_arg "$@"); then + echo "$parsed_value" + exit 1 + fi + ;; + --local-domain=?*|--local-port=?*|--proxy-port=?*|--proxy-domain=?*) + if ! parsed_value=$(parse_arg "${1%%=*}" "${1#*=}"); then + echo "$parsed_value" + exit 1 + fi + ;; + --local-domain=|--local-port=|--proxy-port=|--proxy-domain=) + if ! parsed_value=$(parse_arg "${1%%=*}"); then + echo "$parsed_value" + exit 1 + fi + ;; + *) + ;; + esac + + case $option in + --local-domain) + LOCAL_DOMAIN=$parsed_value + ;; + --local-port) + LOCAL_PORT=$parsed_value + ;; + --proxy-domain) + PROXY_DOMAIN=$parsed_value + ;; + --proxy-port) + PROXY_PORT=$parsed_value + ;; + *) + break + ;; + esac + + if [ "$1" == "$option" ]; then + shift + fi + shift +done + +function locate_docker_file { + local POTENTIAL_FILES=("docker/docker-compose.yml" "docker-compose.yml") + for file in "${POTENTIAL_FILES[@]}"; do + if [ -f "$file" ]; then + echo "$file" + return + fi + done +} +DOCKER_FILE=$(locate_docker_file) + +function locate_docker_file_override { + local POTENTIAL_FILES=("docker/docker-compose.override.yml" "docker-compose.override.yml") + for file in "${POTENTIAL_FILES[@]}"; do + if [ -f "$file" ]; then + echo "$file" + return + fi + done +} +DOCKER_FILE_OVERRIDE=$(locate_docker_file_override) + +function pick_one_virtual_host_from_file() { + local FILE=$1 + local EXCEPT=$2 + + local HOST_STRING + local HOSTS=() + local HOST + local HOST_OPTIONS=() + + HOST_STRING=$(sed -nr 's/.*VIRTUAL_HOST=(.*)/\1/p' "$FILE") + + IFS="," read -ra HOSTS <<< "$HOST_STRING" + + for HOST_ENTRY in "${HOSTS[@]}"; do + if [ "$HOST_ENTRY" != "$EXCEPT" ]; then + HOST_OPTIONS+=("$HOST_ENTRY") + fi + done + + if [ "${#HOST_OPTIONS[@]}" -eq "0" ]; then + output_error "No VIRTUAL_HOST overrides found in $FILE." + exit 1 + elif [ "${#HOST_OPTIONS[@]}" -gt "1" ]; then + PS3="Select a Proxy URL (or quit to exit): " + while [ -z "$HOST" ]; do + select opt in "${HOST_OPTIONS[@]}"; do + if [ -z "$opt" ]; then + if [ "$REPLY" == "quit" ]; then + output_info "Exiting..." + exit 1 + fi + >&2 output_error "Invalid selection: $REPLY. Please enter the number of the option you wish to select." + continue + fi + + HOST=$opt + break + done + done + else + HOST=${HOST_OPTIONS[0]} + fi + + echo "$HOST" +} + +function determine_virtual_host() { + if [ -z "$DOCKER_FILE" ]; then + output_error "No docker-compose file found." + exit 1 + fi + + local HOST + + if ! HOST=$(pick_one_virtual_host_from_file "$DOCKER_FILE"); then + echo "$HOST" + exit 1 + fi + + echo "$HOST" +} + +function determine_virtual_host_override() { + if [ -z "$1" ]; then + output_error "No local host provided." + exit 1 + fi + + if [ -z "$DOCKER_FILE_OVERRIDE" ]; then + output_error "No docker-compose override file found. It is recommended that you create a docker-compose.override.yml file with an override for your VIRTUAL_HOST environment variable." + exit 1 + fi + + local HOST + + if ! HOST=$(pick_one_virtual_host_from_file "$DOCKER_FILE_OVERRIDE" "$1"); then + echo "$HOST" + exit 1 + fi + + echo "$HOST" +} + +if [ -z "$LOCAL_DOMAIN" ]; then + if ! LOCAL_DOMAIN=$(determine_virtual_host); then + echo "$LOCAL_DOMAIN" + exit 1 + fi +fi + +if [ -z "$LOCAL_PORT" ]; then + LOCAL_PORT=80 +fi + +if [ -z "$PROXY_DOMAIN" ]; then + if ! PROXY_DOMAIN=$(determine_virtual_host_override "$LOCAL_DOMAIN"); then + echo "$PROXY_DOMAIN" + exit 1 + fi +fi + +if [ -z "$PROXY_PORT" ]; then + PROXY_PORT=$((RANDOM%(PORT_GEN_MIN-PORT_GEN_MAX+1)+PORT_GEN_MIN)) + output_info "No proxy port specified. Using random port: ${PROXY_PORT}" +fi + +echo +output_info "Starting proxy... $FG_C${LOCAL_DOMAIN}$FG_N:$FG_M${LOCAL_PORT}$RESET -> $FG_C${PROXY_DOMAIN}$FG_N:$FG_M${PROXY_PORT}$RESET" +echo +output_tagged_string "$BG_N" "Proxy URL" "https://$PROXY_DOMAIN" +echo +ssh -tR "${PROXY_PORT}:${LOCAL_DOMAIN}:${LOCAL_PORT}" proxy@local.sourcetoadtest.com sirtunnel.py "${PROXY_DOMAIN}" "${PROXY_PORT}"