Skip to content

Instantly share code, notes, and snippets.

@lcrilly
Last active November 25, 2022 17:45
Show Gist options
  • Save lcrilly/2c2ed15bf2f7895d354a8e96be05b71d to your computer and use it in GitHub Desktop.
Save lcrilly/2c2ed15bf2f7895d354a8e96be05b71d to your computer and use it in GitHub Desktop.
unitc - a curl wrapper for configuring NGINX Unit
#!/bin/bash
# unitc - a curl wrapper for configuring NGINX Unit
# NGINX, Inc. (c) 2022
# Defaults
#
ERROR_LOG=/dev/null
REMOTE=0
QUIET=0
METHOD=PUT
SHOW_LOG=1
URI=""
while [ $# -gt 0 ]; do
OPTION=$(echo $1 | tr '[a-z]' '[A-Z]')
case $OPTION in
"-H" | "--HELP")
shift
;;
"-Q" | "--QUIET")
QUIET=1
shift
;;
"GET" | "PUT" | "POST" | "DELETE" | "INSERT")
METHOD=$OPTION
shift
;;
"HEAD" | "PATCH" | "PURGE" | "OPTIONS")
echo "${0##*/}: ERROR: Invalid HTTP method ($OPTION)"
exit 1
;;
*)
if [ "${1:0:1}" = "/" ] || [ "${1:0:4}" = "http" ]; then
URI=$1
shift
else
echo "${0##*/}: ERROR: Invalid option ($1)"
exit 1
fi
;;
esac
done
if [ "$URI" = "" ]; then
echo "${0##*/} - a curl wrapper for configuring NGINX Unit"
echo ""
echo "USAGE: ${0##*/} [--quiet] [HTTP method] URI"
echo ""
echo " URI is for the Unit configuration API, e.g. /config"
echo " URI with protocol (e.g. http://...) can be used for remote configuration"
echo " --quiet (-q) Will not monitor the error log after config changes"
echo ""
echo " • Environment variable \$UNIT_CTRL may specify remote host"
echo " • Configuration JSON is read from stdin"
echo " • Default HTTP method is PUT for config changes, else GET"
echo " • Virtual method INSERT adds to beginning of array"
echo " • Options can appear in any position, e.g. method after URI"
echo " • The control socket is automatically detected for local installations"
echo ""
exit 1
fi
# Figure out if we're running on the Unit host, or remotely
#
if [ "$UNIT_CTRL" = "" ]; then
if [ "${URI:0:4}" = "http" ]; then
REMOTE=1
UNIT_CTRL=$(echo "$URI" | cut -f1-3 -d/)
URI=/$(echo "$URI" | cut -f4- -d/)
fi
elif [ "${URI:0:1}" = "/" ]; then
REMOTE=1
fi
if [ $REMOTE -eq 0 ]; then
# Check if Unit is running, find the main process
#
PID=($(ps ax | grep unit:\ main | grep -v \ grep | awk '{print $1}'))
if [ ${#PID[@]} -eq 0 ]; then
echo "${0##*/}: ERROR: unitd not running"
exit 1
elif [ ${#PID[@]} -gt 1 ]; then
echo "${0##*/}: ERROR: multiple unitd processes detected (${PID[@]})"
exit 1
fi
# Read the significant unitd conifuration from cache file (or create it)
#
if [ -r /tmp/${0##*/}.$PID.env ]; then
source /tmp/${0##*/}.$PID.env
else
# Check we have all the tools we need in $PATH
#
MISSING=$(hash curl ps grep tr sed tail sleep 2>&1 | cut -f4 -d: | tr -d '\n')
if [ "$MISSING" != "" ]; then
echo "${0##*/}: ERROR: cannot find$MISSING: please install or add to \$PATH"
exit 1
fi
PARAMS=$(ps $PID | grep unitd | cut -f2- -dv | tr '[]' ' ' | cut -f4- -d ' ' | sed -e 's/ --/\n--/g')
# Get control address
#
CTRL_ADDR=$(echo "$PARAMS" | grep '\--control' | cut -f2 -d' ')
if [ "$CTRL_ADDR" = "" ]; then
CTRL_ADDR=$(unitd --help | grep -A1 '\--control' | tail -1 | cut -f2 -d\")
fi
# Prepare for network or Unix socket addressing
#
if [ $(echo $CTRL_ADDR | grep -c ^unix:) -eq 1 ]; then
SOCK_FILE=$(echo $CTRL_ADDR | cut -f2- -d:)
if [ -r $SOCK_FILE ]; then
UNIT_CTRL="--unix-socket $SOCK_FILE _"
else
echo "${0##*/}: ERROR: cannot read unitd control socket: $SOCK_FILE"
ls -l $SOCK_FILE
exit 2
fi
else
UNIT_CTRL="http://$CTRL_ADDR"
fi
# Get error log filename
#
ERROR_LOG=$(echo "$PARAMS" | grep '\--log' | cut -f2 -d' ')
if [ "$ERROR_LOG" = "" ]; then
ERROR_LOG=$(unitd --help | grep -A1 '\--log' | tail -1 | cut -f2 -d\")
fi
# Cache the discovery for this unit PID (and cleanup any old files)
#
rm /tmp/${0##*/}.* 2> /dev/null
echo UNIT_CTRL=\"${UNIT_CTRL}\" > /tmp/${0##*/}.$PID.env
echo ERROR_LOG=${ERROR_LOG} >> /tmp/${0##*/}.$PID.env
fi
fi
# Test for jq
#
if hash jq 2> /dev/null; then
PRETTY="jq"
else
PRETTY="cat"
fi
# Get current length of error log before we make any changes
#
if [ -f $ERROR_LOG ] && [ -r $ERROR_LOG ]; then
LOG_LEN=$(wc -l < $ERROR_LOG)
else
QUIET=1
fi
# Adjust HTTP method and curl params based on presence of stdin payload
#
if [ -t 0 ]; then
if [ "$METHOD" = "DELETE" ]; then
curl -X $METHOD $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $PRETTY
else
SHOW_LOG=$(echo $URI | grep -c ^/control/)
curl $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $PRETTY
fi
else
if [ "$METHOD" = "INSERT" ]; then
if [ "$PRETTY" != "jq" ]; then
echo "${0##*/}: ERROR: jq(1) is required to use INSERT method, install at <https://stedolan.github.io/jq/>"
exit 1
fi
CURR_VAL=$(curl -s $UNIT_CTRL$URI)
if [ "$(echo $CURR_VAL | jq -r type)" = "array" ]; then
SUFFIX=$(cat)
echo $CURR_VAL | jq ". |= [$SUFFIX] + ." | curl -X PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $PRETTY
else
echo "${0##*/}: ERROR: INSERT method must be used with an array"
exit 3
fi
else
echo "$(cat)" | curl -X $METHOD --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $PRETTY
fi
fi
CURL_STATUS=${PIPESTATUS[0]}
if [ $CURL_STATUS -ne 0 ]; then
echo "${0##*/}: ERROR: curl(1) exited with an error ($CURL_STATUS)"
if [ $CURL_STATUS -eq 7 ]; then
echo "${0##*/}: Check that you have permission to access the Unit control socket, or try again with sudo(8)"
else
echo "${0##*/}: Trying to access $UNIT_CTRL$URI"
cat /tmp/${0##*/}.$$ && rm /tmp/${0##*/}.$$
fi
exit 4
fi
rm /tmp/${0##*/}.$$ 2> /dev/null
if [ $SHOW_LOG -gt 0 ] && [ $QUIET -eq 0 ]; then
echo -n "${0##*/}: Waiting for log..."
sleep $SHOW_LOG
echo ""
sed -n $((LOG_LEN+1)),\$p $ERROR_LOG
fi
# vim: syntax=bash
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment