From 9ad207057c620f234f01483bd3a39176945ec202 Mon Sep 17 00:00:00 2001 From: Liam Crilly Date: Tue, 3 Oct 2023 14:57:17 +0100 Subject: Tools: unitc quiet mode fix for macOS. head -c 0 does not work on macOS (invalid byte count) but tail(1) is happy to accept zero bytes, and does not have a performance penalty. --- tools/unitc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/unitc b/tools/unitc index 877e11d4..1db59faa 100755 --- a/tools/unitc +++ b/tools/unitc @@ -186,7 +186,7 @@ fi # Choose presentation style # if [ $QUIET -eq 1 ]; then - OUTPUT="head -c 0" # Equivalent to >/dev/null + OUTPUT="tail -c 0" # Equivalent to >/dev/null elif hash jq 2> /dev/null; then OUTPUT="jq" else -- cgit From 599b035a544ea27e9fe76cb79f7d672ef114c2d2 Mon Sep 17 00:00:00 2001 From: Liam Crilly Date: Tue, 10 Oct 2023 15:06:36 +0100 Subject: Tools: unitc YAML mode. Added --format option to manage configuration in other formats. Initially, YAML is the only supported conversion format. JSON/YAML conversion is performed with yq(1). Suggested by: Torstein Krause Johansen Closes: #958 --- tools/README.md | 4 ++++ tools/unitc | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 55 insertions(+), 7 deletions(-) (limited to 'tools') diff --git a/tools/README.md b/tools/README.md index 1a631e10..0bb80985 100644 --- a/tools/README.md +++ b/tools/README.md @@ -37,12 +37,16 @@ web page with NGINX Unit. | _HTTP method_ | It is usually not required to specify a HTTP method. `GET` is used to read the configuration. `PUT` is used when making configuration changes unless a specific method is provided. | `edit` | Opens **URI** in the default editor for interactive configuration. The [jq](https://stedolan.github.io/jq/) tool is required for this option. | `INSERT` | A _virtual_ HTTP method that prepends data when the URI specifies an existing array. The [jq](https://stedolan.github.io/jq/) tool is required for this option. +| `-f` \| `--format YAML` | Convert configuration data to/from YAML format. The [yq](https://github.com/mikefarah/yq) tool is required for this option. | `-q` \| `--quiet` | No output to stdout. Options are case insensitive and can appear in any order. For example, a redundant part of the configuration can be identified by its URI, and followed by `delete` in a subsequent command. +Options may be combined. For example, `edit -f yaml` will open the +configuration URI in a text editor, in YAML format. + ### Local Configuration For local instances of Unit, the control socket is automatically detected. The error log is monitored; when changes occur, new log entries are shown. diff --git a/tools/unitc b/tools/unitc index 1db59faa..e0e725ec 100755 --- a/tools/unitc +++ b/tools/unitc @@ -10,6 +10,7 @@ REMOTE=0 SHOW_LOG=1 NOLOG=0 QUIET=0 +CONVERT=0 URI="" SSH_CMD="" METHOD=PUT @@ -18,6 +19,30 @@ CONF_FILES=() while [ $# -gt 0 ]; do OPTION=$(echo $1 | tr '[a-z]' '[A-Z]') case $OPTION in + "-F" | "--FORMAT") + case $(echo $2 | tr '[a-z]' '[A-Z]') in + "YAML") + CONVERT=1 + if hash yq 2> /dev/null; then + CONVERT_TO_JSON="yq eval -P --output-format=json" + CONVERT_FROM_JSON="yq eval -P --output-format=yaml" + else + echo "${0##*/}: ERROR: yq(1) is required to use YAML format; install at " + exit 1 + fi + ;; + "") + echo "${0##*/}: ERROR: Must specify configuration format" + exit 1 + ;; + *) + echo "${0##*/}: ERROR: Invalid format ($2)" + exit 1 + ;; + esac + shift; shift + ;; + "-H" | "--HELP") shift ;; @@ -45,15 +70,22 @@ while [ $# -gt 0 ]; do *) if [ -f $1 ] && [ -r $1 ]; then CONF_FILES+=($1) + if [ "${1##*.}" = "yaml" ]; then + echo "${0##*/}: INFO: converting $1 to JSON" + shift; set -- "--format" "yaml" "$@" # Apply the command line option + else + shift + fi elif [ "${1:0:1}" = "/" ] || [ "${1:0:4}" = "http" ] && [ "$URI" = "" ]; then URI=$1 + shift elif [ "${1:0:6}" = "ssh://" ]; then UNIT_CTRL=$1 + shift else echo "${0##*/}: ERROR: Invalid option ($1)" exit 1 fi - shift ;; esac done @@ -67,16 +99,18 @@ USAGE: ${0##*/} [options] URI • URI is for Unit's control API target, e.g. /config • A local Unit control socket is detected unless a remote one is specified. • Configuration data is read from stdin. +• All options are case-insensitive (excluding filenames and URIs). General options - filename … # Read configuration data from files instead of stdin - HTTP method # Default=GET, or PUT with config data (case-insensitive) - EDIT # Opens the URI contents in \$EDITOR - INSERT # Virtual HTTP method to prepend data to an existing array - -q | --quiet # No output to stdout + filename … # Read configuration data from files instead of stdin + HTTP method # Default=GET, or PUT when config data is present + EDIT # Opens the URI contents in \$EDITOR + INSERT # Virtual HTTP method; prepend data to an array + -f | --format YAML # Convert configuration data to/from YAML format + -q | --quiet # No output to stdout Local options - -l | --nolog # Do not monitor the error log after applying config changes + -l | --nolog # Do not monitor the Unit log file after config changes Remote options ssh://[user@]remote_host[:port]/path/to/control.socket # Remote Unix socket @@ -187,6 +221,8 @@ fi # if [ $QUIET -eq 1 ]; then OUTPUT="tail -c 0" # Equivalent to >/dev/null +elif [ $CONVERT -eq 1 ]; then + OUTPUT=$CONVERT_FROM_JSON elif hash jq 2> /dev/null; then OUTPUT="jq" else @@ -224,6 +260,10 @@ if [ -t 0 ] && [ ${#CONF_FILES[@]} -eq 0 ]; then $SSH_CMD curl -fsSX DELETE $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ && \ printf "%s" "$(< $EDIT_FILENAME.js)" | $SSH_CMD curl -fX PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ && \ $SSH_CMD curl -X PUT --data-binary @/tmp/${0##*/}.$$_js_module $UNIT_CTRL/config/settings/js_module 2> /tmp/${0##*/}.$$ + elif [ $CONVERT -eq 1 ]; then + $CONVERT_FROM_JSON < $EDIT_FILENAME > $EDIT_FILENAME.yaml + $EDITOR $EDIT_FILENAME.yaml || exit 2 + $CONVERT_TO_JSON < $EDIT_FILENAME.yaml | $SSH_CMD curl -X PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT else tr -d '\r' < $EDIT_FILENAME > $EDIT_FILENAME.json # Remove carriage-return from newlines $EDITOR $EDIT_FILENAME.json || exit 2 @@ -249,6 +289,10 @@ else exit 3 fi else + if [ $CONVERT -eq 1 ]; then + cat ${CONF_FILES[@]} | $CONVERT_TO_JSON > /tmp/${0##*/}.$$_json + CONF_FILES=(/tmp/${0##*/}.$$_json) + fi cat ${CONF_FILES[@]} | $SSH_CMD curl -X $METHOD --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT fi fi -- cgit From 43f140dfd318378f330ff019bb2a37c94d8f885c Mon Sep 17 00:00:00 2001 From: Liam Crilly Date: Mon, 16 Oct 2023 10:32:19 +0100 Subject: Tools: unitc Docker mode. Introduces a new remote host scheme docker:// that specifies a local container ID. By default, the control socket is assumed to be in the default location, as per the Docker Official Images for Unit. If not, the path to the control socket can be appended to the container ID. --- tools/README.md | 18 ++++++++++++++---- tools/unitc | 46 +++++++++++++++++++++++++++++----------------- 2 files changed, 43 insertions(+), 21 deletions(-) (limited to 'tools') diff --git a/tools/README.md b/tools/README.md index 0bb80985..883bc107 100644 --- a/tools/README.md +++ b/tools/README.md @@ -55,10 +55,9 @@ The error log is monitored; when changes occur, new log entries are shown. |---------|-| | `-l` \| `--nolog` | Do not monitor the error log after configuration changes. -#### Examples +#### Local Examples ```shell unitc /config -unitc /control/applications/my_app/restart unitc /config < unitconf.json echo '{"*:8080": {"pass": "routes"}}' | unitc /config/listeners unitc /config/applications/my_app DELETE @@ -68,10 +67,12 @@ unitc /certificates/bundle cert.pem key.pem ### Remote Configuration For remote instances of NGINX Unit, the control socket on the remote host can be set with the `$UNIT_CTRL` environment variable. The remote control socket -can be accessed over TCP or SSH, depending on the type of control socket: +can be accessed over TCP, SSH, or Docker containers on the host, depending on +the type of control socket: * `ssh://[user@]remote_host[:ssh_port]/path/to/control.socket` * `http://remote_host:unit_control_port` + * `docker://container_ID[/path/to/control.socket]` > **Note:** SSH is recommended for remote confguration. Consider the > [security implications](https://unit.nginx.org/howto/security/#secure-socket-and-state) @@ -81,8 +82,9 @@ can be accessed over TCP or SSH, depending on the type of control socket: |---------|-| | `ssh://…` | Specify the remote Unix control socket on the command line. | `http://…`*URI* | For remote TCP control sockets, the URI may include the protocol, hostname, and port. +| `docker://…` | Specify the local container ID/name. The default Unix control socket can be overridden. -#### Examples +#### Remote Examples ```shell unitc http://192.168.0.1:8080/status UNIT_CTRL=http://192.168.0.1:8080 unitc /status @@ -93,4 +95,12 @@ cat catchall_route.json | unitc POST /config/routes echo '{"match":{"uri":"/wp-admin/*"},"action":{"return":403}}' | unitc INSERT /config/routes ``` +#### Docker Examples +```shell +unitc docker://d43251184c54 /config +echo '{"http": {"log_route": true}}' | unitc docker://d43251184c54 /settings +unitc docker://f4f3d9e918e6/root/unit.sock /control/applications/my_app/restart +UNIT_CTRL=docker://4d0431488982 unitc /status/requests/total +``` + --- diff --git a/tools/unitc b/tools/unitc index e0e725ec..e671f384 100755 --- a/tools/unitc +++ b/tools/unitc @@ -12,7 +12,7 @@ NOLOG=0 QUIET=0 CONVERT=0 URI="" -SSH_CMD="" +RPC_CMD="" METHOD=PUT CONF_FILES=() @@ -82,6 +82,9 @@ while [ $# -gt 0 ]; do elif [ "${1:0:6}" = "ssh://" ]; then UNIT_CTRL=$1 shift + elif [ "${1:0:9}" = "docker://" ]; then + UNIT_CTRL=$1 + shift else echo "${0##*/}: ERROR: Invalid option ($1)" exit 1 @@ -115,9 +118,10 @@ Local options Remote options ssh://[user@]remote_host[:port]/path/to/control.socket # Remote Unix socket http://remote_host:port/URI # Remote TCP socket + docker://container_ID[/non-default/control.socket] # Container on host - A remote Unit control socket may also be defined with the \$UNIT_CTRL - environment variable as http://remote_host:port -OR- ssh://… (as above) + A remote Unit instance may also be defined with the \$UNIT_CTRL environment + variable as http://remote_host:port or ssh://… or docker://… (as above). __EOF__ exit 1 @@ -133,8 +137,16 @@ if [ "$UNIT_CTRL" = "" ]; then fi elif [ "${UNIT_CTRL:0:6}" = "ssh://" ]; then REMOTE=1 - SSH_CMD="ssh $(echo $UNIT_CTRL | cut -f1-3 -d/)" + RPC_CMD="ssh $(echo $UNIT_CTRL | cut -f1-3 -d/)" UNIT_CTRL="--unix-socket /$(echo $UNIT_CTRL | cut -f4- -d/) _" +elif [ "${UNIT_CTRL:0:9}" = "docker://" ]; then + RPC_CMD="docker exec -i $(echo $UNIT_CTRL | cut -f3 -d/)" + DOCKSOCK=/$(echo "$UNIT_CTRL" | cut -f4- -d/) + if [ "$DOCKSOCK" = "/" ]; then + DOCKSOCK="/var/run/control.unit.sock" # Use default location if no path + fi + UNIT_CTRL="--unix-socket $DOCKSOCK _" + REMOTE=1 elif [ "${URI:0:1}" = "/" ]; then REMOTE=1 fi @@ -241,11 +253,11 @@ fi # if [ -t 0 ] && [ ${#CONF_FILES[@]} -eq 0 ]; then if [ "$METHOD" = "DELETE" ]; then - $SSH_CMD curl -X $METHOD $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT + $RPC_CMD curl -X $METHOD $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT elif [ "$METHOD" = "EDIT" ]; then EDITOR=$(test "$EDITOR" && printf '%s' "$EDITOR" || command -v editor || command -v vim || echo vi) EDIT_FILENAME=/tmp/${0##*/}.$$${URI//\//_} - $SSH_CMD curl -fsS $UNIT_CTRL$URI > $EDIT_FILENAME || exit 2 + $RPC_CMD curl -fsS $UNIT_CTRL$URI > $EDIT_FILENAME || exit 2 if [ "${URI:0:12}" = "/js_modules/" ]; then if ! hash jq 2> /dev/null; then echo "${0##*/}: ERROR: jq(1) is required to edit JavaScript modules; install at " @@ -255,23 +267,23 @@ if [ -t 0 ] && [ ${#CONF_FILES[@]} -eq 0 ]; then EDIT_FILE=$EDIT_FILENAME.js $EDITOR $EDIT_FILENAME.js || exit 2 # Remove the references, delete old config, push new config+reference - $SSH_CMD curl -fsS $UNIT_CTRL/config/settings/js_module > /tmp/${0##*/}.$$_js_module && \ - $SSH_CMD curl -X DELETE $UNIT_CTRL/config/settings/js_module && \ - $SSH_CMD curl -fsSX DELETE $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ && \ - printf "%s" "$(< $EDIT_FILENAME.js)" | $SSH_CMD curl -fX PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ && \ - $SSH_CMD curl -X PUT --data-binary @/tmp/${0##*/}.$$_js_module $UNIT_CTRL/config/settings/js_module 2> /tmp/${0##*/}.$$ + $RPC_CMD curl -fsS $UNIT_CTRL/config/settings/js_module > /tmp/${0##*/}.$$_js_module && \ + $RPC_CMD curl -X DELETE $UNIT_CTRL/config/settings/js_module && \ + $RPC_CMD curl -fsSX DELETE $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ && \ + printf "%s" "$(< $EDIT_FILENAME.js)" | $RPC_CMD curl -fX PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ && \ + $RPC_CMD curl -X PUT --data-binary @/tmp/${0##*/}.$$_js_module $UNIT_CTRL/config/settings/js_module 2> /tmp/${0##*/}.$$ elif [ $CONVERT -eq 1 ]; then $CONVERT_FROM_JSON < $EDIT_FILENAME > $EDIT_FILENAME.yaml $EDITOR $EDIT_FILENAME.yaml || exit 2 - $CONVERT_TO_JSON < $EDIT_FILENAME.yaml | $SSH_CMD curl -X PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT + $CONVERT_TO_JSON < $EDIT_FILENAME.yaml | $RPC_CMD curl -X PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT else tr -d '\r' < $EDIT_FILENAME > $EDIT_FILENAME.json # Remove carriage-return from newlines $EDITOR $EDIT_FILENAME.json || exit 2 - $SSH_CMD curl -X PUT --data-binary @$EDIT_FILENAME.json $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT + $RPC_CMD curl -X PUT --data-binary @$EDIT_FILENAME.json $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT fi else SHOW_LOG=$(echo $URI | grep -c ^/control/) - $SSH_CMD curl $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT + $RPC_CMD curl $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT fi else if [ "$METHOD" = "INSERT" ]; then @@ -281,9 +293,9 @@ else fi NEW_ELEMENT=$(cat ${CONF_FILES[@]}) echo $NEW_ELEMENT | jq > /dev/null || exit $? # Test the input is valid JSON before proceeding - OLD_ARRAY=$($SSH_CMD curl -s $UNIT_CTRL$URI) + OLD_ARRAY=$($RPC_CMD curl -s $UNIT_CTRL$URI) if [ "$(echo $OLD_ARRAY | jq -r type)" = "array" ]; then - echo $OLD_ARRAY | jq ". |= [$NEW_ELEMENT] + ." | $SSH_CMD curl -X PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT + echo $OLD_ARRAY | jq ". |= [$NEW_ELEMENT] + ." | $RPC_CMD curl -X PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT else echo "${0##*/}: ERROR: the INSERT method expects an array" exit 3 @@ -293,7 +305,7 @@ else cat ${CONF_FILES[@]} | $CONVERT_TO_JSON > /tmp/${0##*/}.$$_json CONF_FILES=(/tmp/${0##*/}.$$_json) fi - cat ${CONF_FILES[@]} | $SSH_CMD curl -X $METHOD --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT + cat ${CONF_FILES[@]} | $RPC_CMD curl -X $METHOD --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT fi fi -- cgit From d51f7def1442ff7519e14263559e3483a9a28f93 Mon Sep 17 00:00:00 2001 From: Liam Crilly Date: Wed, 18 Oct 2023 22:26:13 +0100 Subject: Tools: unitc remote mode edit fix. Previously, the edit method created a temporary file that was then sent to curl(1) as --data-binary @filename.tmp. This did not work with remote instances because the temporary file is not on the remote host. The edit method now passes the configuration to curl(1) using stdin, the same way as for all other configuration changes. --- tools/unitc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/unitc b/tools/unitc index e671f384..4ab5f663 100755 --- a/tools/unitc +++ b/tools/unitc @@ -271,7 +271,7 @@ if [ -t 0 ] && [ ${#CONF_FILES[@]} -eq 0 ]; then $RPC_CMD curl -X DELETE $UNIT_CTRL/config/settings/js_module && \ $RPC_CMD curl -fsSX DELETE $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ && \ printf "%s" "$(< $EDIT_FILENAME.js)" | $RPC_CMD curl -fX PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ && \ - $RPC_CMD curl -X PUT --data-binary @/tmp/${0##*/}.$$_js_module $UNIT_CTRL/config/settings/js_module 2> /tmp/${0##*/}.$$ + cat /tmp/${0##*/}.$$_js_module | $RPC_CMD curl -X PUT --data-binary @- $UNIT_CTRL/config/settings/js_module 2> /tmp/${0##*/}.$$ elif [ $CONVERT -eq 1 ]; then $CONVERT_FROM_JSON < $EDIT_FILENAME > $EDIT_FILENAME.yaml $EDITOR $EDIT_FILENAME.yaml || exit 2 @@ -279,7 +279,7 @@ if [ -t 0 ] && [ ${#CONF_FILES[@]} -eq 0 ]; then else tr -d '\r' < $EDIT_FILENAME > $EDIT_FILENAME.json # Remove carriage-return from newlines $EDITOR $EDIT_FILENAME.json || exit 2 - $RPC_CMD curl -X PUT --data-binary @$EDIT_FILENAME.json $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT + cat $EDIT_FILENAME.json | $RPC_CMD curl -X PUT --data-binary @- $UNIT_CTRL$URI 2> /tmp/${0##*/}.$$ | $OUTPUT fi else SHOW_LOG=$(echo $URI | grep -c ^/control/) -- cgit