unit/tools/setup-unit
Alejandro Colomar 543d478e12 Tools: setup-unit: ctl: added "edit" subcommand.
Almost equivalent to b42f6b1d ("Tools: unitc edit mode for interactive
configuration."), implemented by Liam in tools/unitc.

I chose to give preference to vi(1) over vim(1) because Debian has vi(1)
as part of update-alternatives(1), so that sysadmins can configure it to
be a symlink to their favourite vi(1) implementation or variant.

We're ignoring the errors of the commands due to having the SSH tunnel
open.  I should fix the script to use traps to close the tunnel on any
error, so we don't leak tunnels.  Then, we'll be able to not ignore
curl(1) or editor errors.  That will also probably allow moving the
tunneling code to the ctl command, thus deduplicating code.

Cc: Liam Crilly <liam@nginx.com>
Cc: Andrew Clayton <a.clayton@nginx.com>
Signed-off-by: Alejandro Colomar <alx@nginx.com>
2023-06-30 14:35:59 +02:00

1699 lines
40 KiB
Bash
Executable file

#!/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 cat <<__EOF__ ;
Your version of bash(1) isn't supported by this script. You're probably
running on macOS. We recommend that you either install a newer version
of bash(1) or run this script with another shell, such as zsh(1):
$ ${SUDO_USER:+sudo }zsh $0 ...
__EOF__
exit 1;
fi;
set -Eefuo pipefail;
test -v BASH_VERSION \
&& shopt -s lastpipe;
test -v ZSH_VERSION \
&& setopt sh_word_split;
export LC_ALL=C
dry_run='no';
help_unit()
{
cat <<__EOF__ ;
SYNOPSIS
$0 [-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 '$0 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
$0 [-h] COMMAND [ARGS]
Subcommands
├── cmd [-h]
├── ctl [-h] [-s SOCK] SUBCOMMAND [ARGS]
│   ├── edit [-h] PATH
│   ├── 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]
├── restart [-hls]
├── 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 '$0 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 "$(basename "$0"): error: $*";
}
err()
{
>&2 echo "$(basename "$0"): 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
$0 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
$0 ctl [-h] [-s SOCK] SUBCOMMAND [ARGS]
Subcommands
├── edit [-h] PATH
├── http [-h] [-c CURLOPT] METHOD PATH
└── insert [-h] PATH INDEX
DESCRIPTION
Control a running unitd(8) instance through its control API socket.
Run '$0 ctl SUBCOMMAND -h' for more information on a
subcommand.
SUBCOMMANDS
edit Edit the unitd(8) configuration with an editor.
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; then
local sock="$(unit_sock_find)";
fi;
if 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
edit)
shift;
unit_ctl_edit ${remote:+ ---r $remote} ---s "$sock" $@;
;;
http)
shift;
unit_ctl_http ${remote:+ ---r $remote} ---s "$sock" $@;
;;
insert)
shift;
unit_ctl_insert ${remote:+ ---r $remote} ---s "$sock" $@;
;;
*)
err "ctl: $1: Unknown argument.";
;;
esac;
}
help_unit_ctl_edit()
{
cat <<__EOF__ ;
SYNOPSIS
$0 ctl [CTL-OPTS] edit [-h] PATH
DESCRIPTION
Edit the JSON configuration with an editor. The current configuration
is downloaded into a temporary file, open with the editor, and then
sent back to the control API socket.
The following editors are tried in this order of preference: \$VISUAL,
\$EDITOR, editor(1), vi(1), vim(1), ed(1).
OPTIONS
-h, --help
Print this help.
ENVIRONMENT
VISUAL
EDITOR
See environ(7).
SEE ALSO
$0 ctl http -h;
update-alternatives(1)
__EOF__
}
unit_ctl_edit()
{
while test $# -ge 1; do
case "$1" in
-h | --help)
help_unit_ctl_edit;
exit 0;
;;
---r | ----remote)
local remote="$2";
shift;
;;
---s | ----sock)
local sock="$2";
shift;
;;
-*)
err "ctl: edit: $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 -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";
fi;
local tmp="$(mktemp ||:)";
unit_ctl_http ---s "$sock" -c --no-progress-meter GET "$req_path" \
</dev/null >"$tmp" \
||:;
$(
((test -v VISUAL && test -n "$VISUAL") && printf '%s\n' "$VISUAL") \
|| ((test -v EDITOR && test -n "$EDITOR") && printf '%s\n' "$EDITOR") \
|| command -v editor \
|| command -v vi \
|| command -v vim \
|| echo ed;
) "$tmp" \
||:;
unit_ctl_http ---s "$sock" PUT "$req_path" <"$tmp" \
||:;
if test -v remote; then
ssh -S "$ssh_ctrl" -O exit "$remote" 2>/dev/null;
unlink "$local_sock";
fi;
}
help_unit_ctl_http()
{
cat <<__EOF__ ;
SYNOPSIS
$0 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
$0 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";
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
$0 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
$0 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";
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
$0 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;
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="$HOME/srv/www/unit/index.html";
fi;
if test -e "$www" && ! test -v force; 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 '\--statedir' >/dev/null; then
echo "$cmd" \
| sed 's/ --/\n--/g' \
| grep '\--statedir' \
| cut -d' ' -f2;
else
$cmd --help \
| sed -n '/\--statedir/,+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 "tee '$www' >/dev/null"' <<__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; 2023</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
$0 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
$0 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
$0 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
$0 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 awwx \
| 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
$0 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
$ $(basename "$0") repo-config apt debian bullseye;
$ $(basename "$0") repo-config apt ubuntu jammy;
$ $(basename "$0") repo-config dnf fedora 36;
$ $(basename "$0") repo-config dnf rhel 9;
$ $(basename "$0") 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.';
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_restart()
{
cat <<__EOF__ ;
SYNOPSIS
$0 restart [-hls]
DESCRIPTION
Restart all running unitd(8) instances.
OPTIONS
-h, --help
Print this help.
-l, --log
Reset log file.
-s, --statedir
Reset \$statedir.
CAVEATS
This command will ask for confirmation before removing
directories; please review those prompts with care, as unknown
bugs in the command may attempt to wipe your file system.
__EOF__
}
unit_restart()
{
while test $# -ge 1; do
case "$1" in
-h | --help)
help_unit_restart;
exit 0;
;;
-l | --log)
local log_flag='yes';
;;
-s | --statedir)
local state_flag='yes';
;;
-*)
err "restart: $1: Unknown option.";
;;
*)
err "restart: $1: Unknown argument.";
;;
esac;
shift;
done;
local cmds="$(unit_cmd)";
pkill -e unitd;
printf '%s\n' "$cmds" \
| while read -r cmd; do
if test -v log_flag; then
(
echo "$cmd" \
| grep '\--log' \
| sed 's/.*--log \+\([^ ]\+\).*/\1/' \
|| eval $cmd --help \
| grep -A1 '\--log FILE' \
| grep 'default:' \
| sed 's/.*"\(.*\)".*/\1/';
) \
| xargs rm -f;
fi;
if test -v state_flag; then
(
echo "$cmd" \
| grep '\--statedir' \
| sed 's/.*--statedir \+\([^ ]\+\).*/\1/' \
|| eval $cmd --help \
| grep -A1 '\--statedir DIR' \
| grep 'default:' \
| sed 's/.*"\(.*\)".*/\1/';
) \
| xargs -I {} find {} -mindepth 1 -maxdepth 1 \
| xargs rm -rfi;
fi;
eval $cmd;
done;
}
help_unit_sock()
{
cat <<__EOF__ ;
SYNOPSIS
$0 sock [-h] SUBCOMMAND [ARGS]
Subcommands
├── filter [-ch]
└── find [-h]
DESCRIPTION
Print the control API socket address of running unitd(8)
instances.
Run '$0 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
$0 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
$0 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 $@;
;;
restart)
shift;
unit_restart $@;
;;
sock)
shift;
unit_sock $@;
;;
welcome)
shift;
unit_ctl_welcome $@;
;;
*)
err "$1: Unknown command.";
;;
esac;