summaryrefslogtreecommitdiffhomepage
path: root/tools/setup-unit
diff options
context:
space:
mode:
Diffstat (limited to 'tools/setup-unit')
-rwxr-xr-xtools/setup-unit1497
1 files changed, 1497 insertions, 0 deletions
diff --git a/tools/setup-unit b/tools/setup-unit
new file mode 100755
index 00000000..79dab850
--- /dev/null
+++ b/tools/setup-unit
@@ -0,0 +1,1497 @@
+#!/usr/bin/env bash
+
+#####################################################################
+#
+# Copyright (C) NGINX, Inc.
+# Author: NGINX Unit Team, F5 Inc.
+#
+#####################################################################
+
+
+if test -n ${BASH_VERSION} && test "${BASH_VERSINFO[0]}" -eq 3; then
+ >&2 echo 'Your version of bash(1) isn't supported by this script.';
+ >&2 echo "You're probably running on macOS. We recommend that you either";
+ >&2 echo 'install a newer version of bash(1) or run this script with';
+ >&2 echo 'another shell, such as zsh(1):';
+ >&2 echo " $ zsh ${SUDO_USER:+sudo }$0 ...";
+ exit 1;
+fi;
+
+
+set -Eefuo pipefail;
+
+test -v BASH_VERSION \
+&& shopt -s lastpipe;
+
+export LC_ALL=C
+
+program_name="$0";
+prog_name="$(basename $program_name)";
+
+dry_run='no';
+
+help_unit()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name [-h] COMMAND [ARGS]
+
+ Subcommands
+ +-- repo-config [-hn] [PKG-MANAGER OS-NAME OS-VERSION]
+ +-- welcome [-hn]
+
+DESCRIPTION
+ This script simplifies installing and configuring an NGINX Unit server
+ for first-time users.
+
+ Run '$program_name COMMAND -h' for more information on a command.
+
+COMMANDS
+ repo-config
+ Configure your package manager with the NGINX Unit repository
+ for later installation.
+
+ welcome
+ Create an initial configuration to serve a welcome web page
+ with NGINX Unit.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+ --help-more
+ Print help for more commands. They are experimental. Using
+ these isn't recommended, unless you know what you're doing.
+
+__EOF__
+}
+
+help_more_unit()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name [-h] COMMAND [ARGS]
+
+ Subcommands
+ +-- cmd [-h]
+ +-- ctl [-h] [-s SOCK] SUBCOMMAND [ARGS]
+ | +-- http [-h] [-c CURLOPT] METHOD PATH
+ | +-- insert [-h] PATH INDEX
+ +-- freeport [-h]
+ +-- json-ins [-hn] JSON INDEX
+ +-- os-probe [-h]
+ +-- ps [-h] [-t TYPE]
+ +-- repo-config [-hn] [PKG-MANAGER OS-NAME OS-VERSION]
+ +-- sock [-h] SUBCOMMAND [ARGS]
+ | +-- filter [-chs]
+ | +-- find [-h]
+ +-- welcome [-hn]
+
+DESCRIPTION
+ This script simplifies installing and configuring
+ an NGINX Unit server for first-time users.
+
+ Run '$program_name COMMAND -h' for more information on a command.
+
+COMMANDS
+ cmd Print the invocation line of unitd(8).
+
+ ctl Control a running unitd(8) instance via its control API socket.
+
+ freeport
+ Print an available TCP port.
+
+ json-ins
+ Insert a JSON element read from standard input into a JSON
+ array read from a file at a given INDEX.
+
+ os-probe
+ Probe the OS and print details about its version.
+
+ ps List unitd(8) processes.
+
+ repo-config
+ Configure your package manager with the NGINX Unit
+ repository for later installation.
+
+ sock Print the control API socket address.
+
+ welcome
+ Create an initial configuration to serve a welcome web page
+ with NGINX Unit.
+
+OPTIONS
+ -h, --help
+ Print basic help (some commands are hidden).
+
+ --help-more
+ Print the hidden help with more commands.
+
+__EOF__
+}
+
+warn()
+{
+ >&2 echo "$prog_name: error: $*";
+}
+
+err()
+{
+ >&2 echo "$prog_name: error: $*";
+ exit 1;
+}
+
+dry_run_echo()
+{
+ if test "$dry_run" = "yes"; then
+ echo "$*";
+ fi;
+}
+
+dry_run_eval()
+{
+ if test "$dry_run" = "yes"; then
+ echo " $*";
+ else
+ eval "$*";
+ fi;
+}
+
+
+help_unit_cmd()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name cmd [-h]
+
+DESCRIPTION
+ Print the invocation line of running unitd(8) instances.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+__EOF__
+}
+
+
+unit_cmd()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_cmd;
+ exit 0;
+ ;;
+ -*)
+ err "cmd: $1: Unknown option.";
+ ;;
+ *)
+ err "cmd: $1: Unknown argument.";
+ ;;
+ esac;
+ shift;
+ done;
+
+ unit_ps -t m \
+ | sed 's/.*\[\(.*\)].*/\1/';
+}
+
+
+help_unit_ctl()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name ctl [-h] [-s SOCK] SUBCOMMAND [ARGS]
+
+ Subcommands
+ +-- http [-h] [-c CURLOPT] METHOD PATH
+ +-- insert [-h] PATH INDEX
+
+DESCRIPTION
+ Control a running unitd(8) instance through its control API socket.
+
+ Run '$program_name ctl SUBCOMMAND -h' for more information on a
+ subcommand.
+
+SUBCOMMANDS
+ http Send an HTTP request to the control API socket.
+
+ insert Insert an element at the specified index into an array in the
+ JSON configuration.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+ -s, --sock SOCK
+ Use SOCK as the control API socket address. If not specified,
+ the script tries to find it. This value is used by subcommands.
+
+ The socket can be a tcp(7) socket or a unix(7) socket; in
+ the case of a unix(7) socket, it can exist locally or on
+ a remote machine, accessed through ssh(1). Accepted syntax
+ for SOCK:
+
+ unix:/path/to/control.sock
+ ssh://[user@]host[:port]/path/to/control.sock
+ [http[s]://]host[:port]
+
+ The last form is less secure than the first two; have a look:
+ <https://unit.nginx.org/howto/security/#secure-socket-and-stat>
+
+ENVIRONMENT
+ Options take precedence over their equivalent environment variables;
+ if both are specified, the command-line option is used.
+
+ UNIT_CTL_SOCK
+ Equivalent to the option -s (--sock).
+
+__EOF__
+}
+
+
+unit_ctl()
+{
+
+ if test -v UNIT_CTL_SOCK; then
+ local sock="$UNIT_CTL_SOCK";
+ fi;
+
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_ctl;
+ exit 0;
+ ;;
+ -s | --sock)
+ if ! test $# -ge 2; then
+ err "ctl: $1: Missing argument.";
+ fi;
+ local sock="$2";
+ shift;
+ ;;
+ -*)
+ err "ctl: $1: Unknown option.";
+ ;;
+ *)
+ break;
+ ;;
+ esac;
+ shift;
+ done;
+
+ if test ! $# -ge 1; then
+ err 'ctl: Missing subcommand.';
+ fi;
+
+ if test -v sock && echo $sock | grep '^ssh://' >/dev/null; then
+ local remote="$(echo $sock | sed 's,\(ssh://[^/]*\).*,\1,')";
+ local sock="$(echo $sock | sed 's,ssh://[^/]*\(.*\),unix:\1,')";
+ fi;
+
+ case $1 in
+ http)
+ shift;
+ unit_ctl_http ${remote:+ ---r $remote} ${sock:+ ---s $sock} $@;
+ ;;
+ insert)
+ shift;
+ unit_ctl_insert ${remote:+ ---r $remote} ${sock:+ ---s $sock} $@;
+ ;;
+ *)
+ err "ctl: $1: Unknown argument.";
+ ;;
+ esac;
+}
+
+
+help_unit_ctl_http()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name ctl [CTL-OPTS] http [-h] [-c CURLOPT] METHOD PATH
+
+DESCRIPTION
+ Send an HTTP request to the unitd(8) control API socket.
+
+ The payload is read from standard input.
+
+OPTIONS
+ -c, --curl CURLOPT
+ Pass CURLOPT as an option to curl. This script is implemented
+ in terms of curl(1), so it's useful to be able to tweak its
+ behavior. The option can be cumulatively used multiple times
+ (the result is also appended to UNIT_CTL_HTTP_CURLOPTS).
+
+ -h, --help
+ Print this help.
+
+ENVIRONMENT
+ UNIT_CTL_HTTP_CURLOPTS
+ Equivalent to the option -c (--curl).
+
+EXAMPLES
+ $program_name ctl http -c --no-progress-meter GET /config >tmp;
+
+SEE ALSO
+ <https://unit.nginx.org/controlapi/#api-manipulation>
+
+__EOF__
+}
+
+
+unit_ctl_http()
+{
+ local curl_options="${UNIT_CTL_HTTP_CURLOPTS:-}";
+
+ while test $# -ge 1; do
+ case "$1" in
+ -c | --curl)
+ if ! test $# -ge 2; then
+ err "ctl: http: $1: Missing argument.";
+ fi;
+ curl_options="$curl_options $2";
+ shift;
+ ;;
+ -h | --help)
+ help_unit_ctl_http;
+ exit 0;
+ ;;
+ ---r | ----remote)
+ local remote="$2";
+ shift;
+ ;;
+ ---s | ----sock)
+ local sock="$2";
+ shift;
+ ;;
+ -*)
+ err "ctl: http: $1: Unknown option.";
+ ;;
+ *)
+ break;
+ ;;
+ esac;
+ shift;
+ done;
+
+ if ! test $# -ge 1; then
+ err 'ctl: http: METHOD: Missing argument.';
+ fi;
+ local method="$1";
+
+ if ! test $# -ge 2; then
+ err 'ctl: http: PATH: Missing argument.';
+ fi;
+ local req_path="$2";
+
+ if test -v remote; then
+ local remote_sock="$(echo "$sock" | unit_sock_filter -s)";
+ local local_sock="$(mktemp -u -p /var/run/unit/)";
+ local ssh_ctrl="$(mktemp -u -p /var/run/unit/)";
+
+ mkdir -p /var/run/unit/;
+
+ ssh -fMNnT -S "$ssh_ctrl" \
+ -o 'ExitOnForwardFailure yes' \
+ -L "$local_sock:$remote_sock" "$remote";
+
+ sock="unix:$local_sock";
+
+ elif ! test -v sock; then
+ local sock="$(unit_sock_find)";
+ fi;
+
+ curl $curl_options -X $method -d@- \
+ $(echo "$sock" | unit_sock_filter -c)${req_path} \
+ ||:;
+
+ if test -v remote; then
+ ssh -S "$ssh_ctrl" -O exit "$remote" 2>/dev/null;
+ unlink "$local_sock";
+ fi;
+}
+
+
+help_unit_ctl_insert()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name ctl [CTL-OPTS] insert [-h] PATH INDEX
+
+DESCRIPTION
+ Insert an element at the specified position (INDEX) into the JSON array
+ located at PATH in unitd(8) control API.
+
+ The new element is read from standard input.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+SEE ALSO
+ $program_name ctl http -h;
+
+__EOF__
+}
+
+
+unit_ctl_insert()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_ctl_insert;
+ exit 0;
+ ;;
+ ---r | ----remote)
+ local remote="$2";
+ shift;
+ ;;
+ ---s | ----sock)
+ local sock="$2";
+ shift;
+ ;;
+ -*)
+ err "ctl: insert: $1: Unknown option.";
+ ;;
+ *)
+ break;
+ ;;
+ esac;
+ shift;
+ done;
+
+ if ! test $# -ge 1; then
+ err 'ctl: insert: PATH: Missing argument.';
+ fi;
+ local req_path="$1";
+
+ if ! test $# -ge 2; then
+ err 'ctl: insert: INDEX: Missing argument.';
+ fi;
+ local idx="$2";
+
+ if test -v remote; then
+ local remote_sock="$(echo "$sock" | unit_sock_filter -s)";
+ local local_sock="$(mktemp -u -p /var/run/unit/)";
+ local ssh_ctrl="$(mktemp -u -p /var/run/unit/)";
+
+ mkdir -p /var/run/unit/;
+
+ ssh -fMNnT -S "$ssh_ctrl" \
+ -o 'ExitOnForwardFailure yes' \
+ -L "$local_sock:$remote_sock" "$remote";
+
+ sock="unix:$local_sock";
+
+ elif ! test -v sock; then
+ local sock="$(unit_sock_find)";
+ fi;
+
+ local old="$(mktemp ||:)";
+
+ unit_ctl_http ---s "$sock" -c --no-progress-meter GET "$req_path" \
+ </dev/null >"$old" \
+ ||:;
+
+ unit_json_ins "$old" "$idx" \
+ | unit_ctl_http ---s "$sock" PUT "$req_path" \
+ ||:;
+
+ if test -v remote; then
+ ssh -S "$ssh_ctrl" -O exit "$remote" 2>/dev/null;
+ unlink "$local_sock";
+ fi;
+}
+
+
+help_unit_ctl_welcome()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name welcome [-hn]
+
+DESCRIPTION
+ This script tests an NGINX Unit installation by creating an initial
+ configuration and serving a welcome web page. Recommended for
+ first-time users.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+ -n, --dry-run
+ Dry run. Print the commands to be run instead of actually
+ running them. Each command is preceded by a line explaining
+ what it does.
+
+__EOF__
+}
+
+
+unit_ctl_welcome()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -f | --force)
+ local force='yes';
+ ;;
+ -h | --help)
+ help_unit_ctl_welcome;
+ exit 0;
+ ;;
+ -n | --dry-run)
+ dry_run='yes';
+ ;;
+ -*)
+ err "welcome: $1: Unknown option.";
+ ;;
+ *)
+ err "welcome: $1: Unknown argument.";
+ ;;
+ esac;
+ shift;
+ done;
+
+ id -u \
+ | xargs test 0 -ne \
+ && err 'welcome: This script requires root privileges to run.';
+
+ command -v curl >/dev/null \
+ || err 'welcome: curl(1) not found in PATH. It must be installed to run this script.';
+
+ www='/srv/www/unit/index.html';
+ if test -e "$www" && ! test -v force || ! test -w /srv; then
+ www="$(mktemp)";
+ mv "$www" "$www.html";
+ www="$www.html"
+ fi;
+
+ unit_ps -t m \
+ | wc -l \
+ | read -r nprocs \
+ ||:
+
+ if test 0 -eq "$nprocs"; then
+ warn "welcome: NGINX Unit isn't running.";
+ warn 'For help with starting NGINX Unit, see:';
+ err " <https://unit.nginx.org/installation/#startup-and-shutdown>";
+ elif test 1 -ne "$nprocs"; then
+ err 'welcome: Only one NGINX Unit instance should be running.';
+ fi;
+
+ local sock="$(unit_sock_find)";
+ local curl_opt="$(unit_sock_find | unit_sock_filter -c)";
+
+ curl $curl_opt/ >/dev/null 2>&1 \
+ || err "welcome: Can't reach the control API socket.";
+
+ if ! test -v force; then
+ unit_cmd \
+ | read -r cmd;
+
+ # Check unitd is not configured already.
+ echo "$cmd" \
+ | if grep '\--state' >/dev/null; then
+ echo "$cmd" \
+ | sed 's/ --/\n--/g' \
+ | grep '\--state' \
+ | cut -d' ' -f2;
+ else
+ $cmd --help \
+ | sed -n '/\--state/,+1p' \
+ | grep 'default:' \
+ | sed 's/ *default: "\(.*\)"/\1/';
+ fi \
+ | sed 's,$,/conf.json,' \
+ | read -r conffile \
+ ||:;
+
+ if test -e $conffile; then
+ if ! unit_ctl_http ---s "$sock" 'GET' '/config' </dev/null 2>/dev/null | grep -q '^{}.\?$'; # The '.\?' is for the possible carriage return.
+ then
+ warn 'welcome: NGINX Unit is already configured. To overwrite';
+ err 'its current configuration, run the script again with --force.';
+ fi;
+ fi;
+ fi;
+
+ (
+ unit_freeport \
+ || err "welcome: Can't find an available port.";
+ ) \
+ | read -r port;
+
+ dry_run_echo 'Create a file to serve:';
+ dry_run_eval "mkdir -p $(dirname $www);";
+ dry_run_eval "cat >'$www'"' <<__EOF__;
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>Welcome to NGINX Unit</title>
+ <style type="text/css">
+ body { background: white; color: black; font-family: sans-serif; margin: 2em; line-height: 1.5; }
+ h1,h2 { color: #00974d; }
+ li { margin-bottom: 0.5em; }
+ pre { background-color: beige; padding: 0.4em; }
+ hr { margin-top: 2em; border: 1px solid #00974d; }
+ .indent { margin-left: 1.5em; }
+ </style>
+ </head>
+ <body>
+ <h1>Welcome to NGINX Unit</h1>
+ <p>Congratulations! NGINX Unit is installed and running.</p>
+ <h3>Useful Links</h3>
+ <ul>
+ <li><b><a href="https://unit.nginx.org/configuration/?referer=welcome">https://unit.nginx.org/configuration/</a></b><br>
+ To get started with Unit, see the <em>Configuration</em> docs, starting with
+ the <em>Quick Start</em> guide.</li>
+ <li><b><a href="https://github.com/nginx/unit">https://github.com/nginx/unit</a></b><br>
+ See our GitHub repo to browse the code, contribute, or seek help from the
+ <a href="https://github.com/nginx/unit#community">community</a>.</li>
+ </ul>
+
+ <h2>Next steps</h2>
+
+ <h3>Check Current Configuration</h3>
+ <div class="indent">
+ <p>Unit'"'"'s control API is currently listening for configuration changes
+ on the '"$(unit_sock_find | grep -q '^unix:' && echo '<a href="https://en.wikipedia.org/wiki/Unix_domain_socket">Unix socket</a>' || echo 'socket')"' at
+ <b>'"$(unit_sock_find)"'</b><br>
+ To see the current configuration:</p>
+ <pre>'"${SUDO_USER:+sudo }"'curl '"$curl_opt"'/config</pre>
+ </div>
+
+ <h3>Change Listener Port</h3>
+ <div class="indent">
+ <p>This page is served over a random TCP high port. To choose the default HTTP port (80),
+ replace the <b>"listeners"</b> object:</p>
+ <pre>echo '"'"'{"*:80": {"pass": "routes"}}'"'"' | '"${SUDO_USER:+sudo }"'curl -X PUT -d@- '"$curl_opt"'/config/listeners</pre>
+ Then remove the port number from the address bar and reload the page.
+ </div>
+
+ <hr>
+ <p><a href="https://unit.nginx.org/?referer=welcome">NGINX Unit &mdash; the universal web app server</a><br>
+ NGINX, Inc. &copy; 2022</p>
+ </body>
+ </html>
+__EOF__';
+ dry_run_echo;
+ dry_run_echo 'Give it appropriate permissions:';
+ dry_run_eval "chmod 644 '$www';";
+ dry_run_echo;
+
+ dry_run_echo 'Configure unitd:'
+ dry_run_eval "cat <<__EOF__ \\
+ | sed 's/8080/$port/' \\
+ | curl -X PUT -d@- $curl_opt/config;
+ {
+ \"listeners\": {
+ \"*:8080\": {
+ \"pass\": \"routes\"
+ }
+ },
+ \"routes\": [{
+ \"action\": {
+ \"share\": \"$www\"
+ }
+ }]
+ }
+__EOF__";
+
+ dry_run_echo;
+
+ echo;
+ echo 'You may want to try the following commands now:';
+ echo;
+ echo 'Check out current unitd configuration:';
+ echo " ${SUDO_USER:+sudo} curl $curl_opt/config";
+ echo;
+ echo 'Browse the welcome page:';
+ echo " curl http://localhost:$port/";
+}
+
+
+help_unit_freeport()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name freeport [-h]
+
+DESCRIPTION
+ Print an available TCP port.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+__EOF__
+}
+
+
+unit_freeport()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_freeport;
+ exit 0;
+ ;;
+ -*)
+ err "freeport: $1: Unknown option.";
+ ;;
+ *)
+ err "freeport: $1: Unknown argument.";
+ ;;
+ esac;
+ shift;
+ done;
+
+ freeport="$(mktemp -t freeport-XXXXXX)";
+
+ cat <<__EOF__ \
+ | cc -x c -o $freeport -;
+ #include <netinet/in.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <strings.h>
+ #include <sys/socket.h>
+ #include <unistd.h>
+
+
+ int32_t get_free_port(void);
+
+
+ int
+ main(void)
+ {
+ int32_t port;
+
+ port = get_free_port();
+ if (port == -1)
+ exit(EXIT_FAILURE);
+
+ printf("%d\n", port);
+ exit(EXIT_SUCCESS);
+ }
+
+
+ int32_t
+ get_free_port(void)
+ {
+ int sfd;
+ int32_t port;
+ socklen_t len;
+ struct sockaddr_in addr;
+
+ port = -1;
+
+ sfd = socket(PF_INET, SOCK_STREAM, 0);
+ if (sfd == -1) {
+ perror("socket()");
+ return -1;
+ }
+
+ bzero(&addr, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr.sin_port = htons(0); // random port
+
+ len = sizeof(addr);
+ if (bind(sfd, (struct sockaddr *) &addr, len)) {
+ perror("bind()");
+ goto fail;
+ }
+
+ if (getsockname(sfd, (struct sockaddr *) &addr, &len)) {
+ perror("getsockname()");
+ goto fail;
+ }
+
+ port = ntohs(addr.sin_port);
+
+ fail:
+ close(sfd);
+ return port;
+ }
+__EOF__
+
+ $freeport;
+}
+
+
+help_unit_json_ins()
+{
+cat <<__EOF__ ;
+SYNOPSIS
+ $program_name json-ins [-hn] JSON INDEX
+
+ARGUMENTS
+ JSON Path to a JSON file containing a top-level array.
+
+ INDEX Position in the array to insert the element at.
+
+DESCRIPTION
+ Insert a JSON element read from standard input into a JSON array read
+ from a file at a given INDEX.
+
+ The resulting array is printed to standard output.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+ -n, --dry-run
+ Dry run. Print the command to be run instead of actually
+ running it.
+
+__EOF__
+}
+
+
+unit_json_ins()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_json_ins;
+ exit 0;
+ ;;
+ -n | --dry-run)
+ dry_run='yes';
+ ;;
+ -*)
+ err "json-ins: $1: Unknown option.";
+ ;;
+ *)
+ break;
+ ;;
+ esac;
+ shift;
+ done;
+
+ if ! test $# -ge 1; then
+ err 'json-ins: JSON: Missing argument.';
+ fi;
+ local arr=$1;
+
+ if ! test $# -ge 2; then
+ err 'json-ins: INDEX: Missing argument.';
+ fi;
+ local idx=$2;
+
+ dry_run_eval "(
+ jq '.[0:$idx]' <'$arr';
+ echo '[';
+ jq .;
+ echo ']';
+ jq '.[$idx:]' <'$arr';
+ ) \\
+ | sed '/^\[]$/d' \\
+ | sed '/^]$/{N;s/^]\n\[$/,/}' \\
+ | jq .;"
+}
+
+
+help_unit_os_probe()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name os-probe [-h]
+
+DESCRIPTION
+ This script probes the OS and prints three fields, delimited by ':';
+ the first is the package manager, the second is the OS name, the third
+ is the OS version.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+__EOF__
+}
+
+
+unit_os_probe()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_os_probe;
+ exit 0;
+ ;;
+ -*)
+ err "os-probe: $1: Unknown option.";
+ ;;
+ *)
+ err "os-probe: $1: Unknown argument.";
+ ;;
+ esac;
+ shift;
+ done;
+
+ local os=$(uname | tr '[:upper:]' '[:lower:]')
+
+ if [ "$os" != 'linux' ] && [ "$os" != 'freebsd' ]; then
+ err "os-probe: The OS isn't Linux or FreeBSD; can't proceed."
+ fi
+
+ if [ "$os" = 'linux' ]; then
+ if command -v apt-get >/dev/null; then
+ local pkgMngr='apt';
+ elif command -v dnf >/dev/null; then
+ local pkgMngr='dnf';
+ elif command -v yum >/dev/null; then
+ local pkgMngr='yum';
+ else
+ local pkgMngr='';
+ fi;
+
+ local osRelease='/etc/os-release';
+
+ if [ -f "$osRelease" ]; then
+ # The value for the ID and VERSION_ID may or may not be in quotes
+ local osName=$(grep "^ID=" "$osRelease" | sed s/\"//g | awk -F= '{ print $2 }' ||:)
+ local osVersion=$(grep '^VERSION_ID=' "$osRelease" | sed s/\"//g | awk -F= '{ print $2 }' || lsb_release -cs)
+ else
+ err "os-probe: Unable to determine OS and version, or the OS isn't supported."
+ fi
+ else
+ local pkgMngr='pkg';
+ local osName=$os
+ local osVersion=$(uname -rs | awk -F '[ -]' '{print $2}' ||:)
+ if [ -z "$osVersion" ]; then
+ err 'os-probe: Unable to get the FreeBSD version.'
+ fi
+ fi
+
+ osName=$(echo "$osName" | tr '[:upper:]' '[:lower:]')
+ echo "$pkgMngr:$osName:$osVersion"
+}
+
+
+help_unit_ps()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name ps [-h] [-t TYPE]
+
+DESCRIPTION
+ List unitd(8) processes.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+ -t, --type TYPE
+ List only processes of type TYPE. The available types are:
+
+ - controller (c)
+ - main (m)
+ - router (r)
+
+__EOF__
+}
+
+
+unit_ps()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_ps;
+ exit 0;
+ ;;
+ -t | --type)
+ if ! test $# -ge 2; then
+ err "ps: $1: Missing argument.";
+ fi;
+ local type=;
+ case "$2" in
+ c | controller)
+ local type_c='c';
+ ;;
+ m | main)
+ local type_m='m';
+ ;;
+ r | router)
+ local type_r='r';
+ ;;
+ esac;
+ shift;
+ ;;
+ -*)
+ err "ps: $1: Unknown option.";
+ ;;
+ *)
+ err "ps: $1: Unknown argument.";
+ ;;
+ esac;
+ shift;
+ done;
+
+ ps ax \
+ | if test -v type; then
+ grep ${type_c:+-e 'unit: controller'} \
+ ${type_m:+-e 'unit: main'} \
+ ${type_r:+-e 'unit: router'};
+ else
+ grep 'unit: ';
+ fi \
+ | grep -v grep \
+ ||:
+}
+
+
+help_unit_repo_config()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name repo-config [-hn] [PKG-MANAGER OS-NAME OS-VERSION]
+
+DESCRIPTION
+ This script configures the NGINX Unit repository for the system
+ package manager.
+
+ The script automatically detects the OS and proceeds accordingly.
+ However, if this automatic selection fails, you may specify the
+ package manager and the OS name and version.
+
+ARGUMENTS
+ PKG-MANAGER
+ Supported: 'apt', 'dnf', and 'yum'.
+
+ OS-NAME
+ Supported: 'debian', 'ubuntu', 'fedora', 'rhel', and 'amzn2'.
+
+ OS-VERSION
+ For most distributions, this should be a numeric value; for
+ Debian derivatives, use the codename instead.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+ -n, --dry-run
+ Dry run. Print the commands to be run instead of actually
+ running them. Each command is preceded by a line explaining
+ what it does.
+
+EXAMPLES
+ $ $prog_name repo-config apt debian bullseye;
+ $ $prog_name repo-config apt ubuntu jammy;
+ $ $prog_name repo-config dnf fedora 36;
+ $ $prog_name repo-config dnf rhel 9;
+ $ $prog_name repo-config yum amzn2 2;
+
+__EOF__
+}
+
+
+unit_repo_config()
+{
+ installAPT ()
+ {
+ local os_name="$2";
+
+ dry_run_echo "Install on $os_name";
+ dry_run_echo;
+ dry_run_eval 'curl --output /usr/share/keyrings/nginx-keyring.gpg https://unit.nginx.org/keys/nginx-keyring.gpg;';
+ dry_run_echo;
+ dry_run_eval 'apt-get install -y apt-transport-https lsb-release ca-certificates;';
+
+ if test $# -ge 3; then
+ local os_version="$3";
+ else
+ local os_version='$(lsb_release -cs)';
+ fi;
+
+ dry_run_echo;
+ dry_run_eval "printf 'deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/$os_name/ %s unit\n' \"$os_version\" | tee /etc/apt/sources.list.d/unit.list;";
+ dry_run_eval "printf 'deb-src [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/$os_name/ %s unit\n' \"$os_version\" | tee -a /etc/apt/sources.list.d/unit.list;";
+ dry_run_echo;
+ dry_run_eval 'apt-get update;';
+ }
+
+ installYumDnf ()
+ {
+ local pkg_mngr="$1";
+ local os_name="$2";
+
+ if test $# -ge 3; then
+ local os_version="$3";
+ else
+ local os_version='\$releasever';
+ fi;
+
+ dry_run_echo "Install on $os_name";
+ dry_run_echo;
+
+ dry_run_eval "cat >/etc/yum.repos.d/unit.repo <<__EOF__
+[unit]
+name=unit repo
+baseurl=https://packages.nginx.org/unit/$os_name/$os_version/\\\$basearch/
+gpgcheck=0
+enabled=1
+__EOF__";
+
+ dry_run_echo;
+ dry_run_eval "$pkg_mngr makecache;";
+ }
+
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_repo_config;
+ exit 0;
+ ;;
+ -n | --dry-run)
+ dry_run='yes';
+ ;;
+ -*)
+ err "repo-config: $1: Unknown option.";
+ ;;
+ *)
+ break;
+ ;;
+ esac;
+ shift;
+ done;
+
+ if test $# -ge 1; then
+ local pkg_mngr="$1";
+
+ if ! test $# -ge 2; then
+ err "repo-config: OS-NAME: Missing argument.";
+ fi;
+ local os_name="$2";
+
+ if ! test $# -ge 3; then
+ err "repo-config: OS-VERSION: Missing argument.";
+ fi;
+ local os_version="$3";
+ fi;
+
+ command -v curl >/dev/null \
+ || err 'repo-config: curl(1) not found in PATH. It must be installed to run this script.';
+
+ id -u \
+ | xargs test 0 -ne \
+ && err 'repo-config: This script requires root privileges to run.';
+
+ echo 'This script sets up the NGINX Unit repository';
+
+ if ! test $# -ge 3; then
+ local os_pkg_name_version=$(unit_os_probe || warn "On macOS, try 'brew install nginx/unit/unit'.")
+ local pkg_mngr=$(echo "$os_pkg_name_version" | awk -F: '{print $1}')
+ local os_name=$(echo "$os_pkg_name_version" | awk -F: '{print $2}')
+ local os_version=$(echo "$os_pkg_name_version" | awk -F: '{print $3}')
+ fi;
+
+ # Call the appropriate installation function
+ case "$pkg_mngr" in
+ apt)
+ case "$os_name" in
+ debian | ubuntu)
+ installAPT "$pkg_mngr" "$os_name" ${3:+$os_version};
+ ;;
+ *)
+ err "repo-config: $os_name: The OS isn't supported.";
+ ;;
+ esac
+ ;;
+ yum | dnf)
+ case "$os_name" in
+ rhel | amzn | fedora)
+ installYumDnf "$pkg_mngr" "$os_name" "$os_version" ${3:+ovr};
+ ;;
+ *)
+ err "repo-config: $os_name: The OS isn't supported.";
+ ;;
+ esac;
+ ;;
+ *)
+ err "repo-config: $pkg_mngr: The package manager isn't supported.";
+ ;;
+ esac;
+
+ echo
+ echo 'All done; the NGINX Unit repository is set up.';
+ echo "Configured with '$pkg_mngr' on '$os_name' '$os_version'.";
+ echo 'Further steps: <https://unit.nginx.org/installation/#official-packages>'
+}
+
+
+help_unit_sock()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name sock [-h] SUBCOMMAND [ARGS]
+
+ Subcommands
+ +-- filter [-ch]
+ +-- find [-h]
+
+DESCRIPTION
+ Print the control API socket address of running unitd(8)
+ instances.
+
+ Run '$program_name sock SUBCOMMAND -h' for more information on a
+ subcommand.
+
+SUBCOMMANDS
+ filter Filter the output of the 'find' subcommand and transform it
+ to something suitable for running other commands, such as
+ curl(1) or ssh(1).
+
+ find Find and print the control API socket address of running
+ unitd(8) instances.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+__EOF__
+}
+
+
+unit_sock()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_sock;
+ exit 0;
+ ;;
+ -*)
+ err "sock: $1: Unknown option.";
+ ;;
+ *)
+ break;
+ ;;
+ esac;
+ shift;
+ done;
+
+ if ! test $# -ge 1; then
+ err 'sock: Missing subcommand.';
+ fi;
+
+ case $1 in
+ filter)
+ shift;
+ unit_sock_filter $@;
+ ;;
+ find)
+ shift;
+ unit_sock_find $@;
+ ;;
+ *)
+ err "sock: $1: Unknown subcommand.";
+ ;;
+ esac;
+}
+
+
+help_unit_sock_filter()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name sock filter [-chs]
+
+DESCRIPTION
+ Filter the output of the 'sock find' command and transform it to
+ something suitable for running other commands, such as
+ curl(1) or ssh(1).
+
+OPTIONS
+ -c, --curl
+ Print an argument suitable for curl(1).
+
+ -h, --help
+ Print this help.
+
+ -s, --ssh
+ Print a socket address suitable for use in an ssh(1) tunnel.
+
+__EOF__
+}
+
+
+unit_sock_filter()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -c | --curl)
+ if test -v ssh_flag; then
+ err "sock: filter: $1: Missing argument.";
+ fi;
+ local curl_flag='yes';
+ ;;
+ -h | --help)
+ help_unit_sock_filter;
+ exit 0;
+ ;;
+ -s | --ssh)
+ if test -v curl_flag; then
+ err "sock: filter: $1: Missing argument.";
+ fi;
+ local ssh_flag='yes';
+ ;;
+ -*)
+ err "sock: filter: $1: Unknown option.";
+ ;;
+ *)
+ err "sock: filter: $1: Unknown argument.";
+ ;;
+ esac;
+ shift;
+ done;
+
+ while read -r control; do
+
+ if test -v curl_flag; then
+ if echo "$control" | grep '^unix:' >/dev/null; then
+ unix_socket="$(echo "$control" | sed 's/unix:/--unix-socket /')";
+ host='http://localhost';
+ else
+ unix_socket='';
+ host="$control";
+ fi;
+
+ echo "$unix_socket $host";
+
+ elif test -v ssh_flag; then
+ echo "$control" \
+ | sed -E 's,^(unix:|http://|https://),,';
+
+ else
+ echo "$control";
+ fi;
+ done;
+}
+
+
+help_unit_sock_find()
+{
+ cat <<__EOF__ ;
+SYNOPSIS
+ $program_name sock find [-h]
+
+DESCRIPTION
+ Find and print the control API socket address of running
+ unitd(8) instances.
+
+OPTIONS
+ -h, --help
+ Print this help.
+
+__EOF__
+}
+
+
+unit_sock_find()
+{
+ while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit_sock_find;
+ exit 0;
+ ;;
+ -*)
+ err "sock: find: $1: Unknown option.";
+ ;;
+ *)
+ err "sock: find: $1: Unknown argument.";
+ ;;
+ esac;
+ shift;
+ done;
+
+ unit_cmd \
+ | while read -r cmd; do
+ if echo "$cmd" | grep '\--control' >/dev/null; then
+ echo "$cmd" \
+ | sed 's/ --/\n--/g' \
+ | grep '\--control' \
+ | cut -d' ' -f2;
+ else
+ if ! command -v $cmd >/dev/null; then
+ local cmd='unitd';
+ fi;
+ $cmd --help \
+ | sed -n '/\--control/,+1p' \
+ | grep 'default:' \
+ | sed 's/ *default: "\(.*\)"/\1/';
+ fi;
+ done;
+}
+
+
+while test $# -ge 1; do
+ case "$1" in
+ -h | --help)
+ help_unit;
+ exit 0;
+ ;;
+ --help-more)
+ help_more_unit;
+ exit 0;
+ ;;
+ -*)
+ err "$1: Unknown option.";
+ ;;
+ *)
+ break;
+ ;;
+ esac;
+ shift;
+done;
+
+if ! test $# -ge 1; then
+ err "Missing command.";
+fi;
+
+case $1 in
+cmd)
+ shift;
+ unit_cmd $@;
+ ;;
+ctl)
+ shift;
+ unit_ctl $@;
+ ;;
+freeport)
+ shift;
+ unit_freeport $@;
+ ;;
+json-ins)
+ shift;
+ unit_json_ins $@;
+ ;;
+os-probe)
+ shift;
+ unit_os_probe $@;
+ ;;
+ps)
+ shift;
+ unit_ps $@;
+ ;;
+repo-config)
+ shift;
+ unit_repo_config $@;
+ ;;
+sock)
+ shift;
+ unit_sock $@;
+ ;;
+welcome)
+ shift;
+ unit_ctl_welcome $@;
+ ;;
+*)
+ err "$1: Unknown command.";
+ ;;
+esac;