Files
nginx-unit/tools/setup-unit
Alejandro Colomar 5a37171f73 Added default values for pathnames.
This allows one to simply run `./configure` and expect it to
produce sane defaults for an install.

Previously, without specifying `--prefix=...`, `make install`
would simply fail, recommending to set `--prefix` or `DESTDIR`,
but that recommendation was incomplete at best, since it didn't
set many of the subdirs needed for a good organization.

Setting `DESTDIR` was even worse, since that shouldn't even affect
an installation (it is required to be transparent to the
installation).

/usr/local is the historic Unix standard path to use for
installations from source made manually by the admin of the
system.  Some package managers (Homebrew, I'm looking specifically
at you) have abused that path to install their things, but 1) it's
not our fault that someone else incorrectly abuses that path (and
they seem to be fixing it for newer archs; e.g., they started
using /opt/homebrew for Apple Silicon), 2) there's no better path
than /usr/local, 3) we still allow changing it for systems where
this might not be the desired path (MacOS Intel with hombrew), and
4) it's _the standard_.

See a related conversation with Ingo (OpenBSD maintainer):

On 7/27/22 16:16, Ingo Schwarze wrote:
> Hi Alejandro,
[...]
>
> Alejandro Colomar wrote on Sun, Jul 24, 2022 at 07:07:18PM +0200:
>> On 7/24/22 16:57, Ingo Schwarze wrote:
>>> Alejandro Colomar wrote on Sun, Jul 24, 2022 at 01:20:46PM +0200:
>
>>>> /usr/local is for sysadmins to build from source;
>
>>> Doing that is *very* strongly discouraged on OpenBSD.
>
>> I guess that's why the directory was reused in the BSDs to install ports
>> (probably ports were installed by the sysadmin there, and by extension,
>> ports are now always installed there, but that's just a guess).
>
> Maybe.  In any case, the practice of using /usr/local for packages
> created from ports is significantly older than the recommendation
> to refrain from using upstream "make install" outside the ports
> framework.
>
>   * The FreeBSD ports framework was started by Jordan Hubbard in 1993.
>   * The ports framework was ported from FreeBSD to OpenBSD
>     by Niklas Hallqvist in 1996.
>   * NetBSD pkgsrc was forked from FreeBSD ports by Alistair G. Crooks
>     and Hubert Feyrer in 1997.
>
> I failed to quickly find Jordan's original version, but rev. 1.1
> of /usr/ports/infrastructure/mk/bsd.port.mk in OpenBSD (dated Jun 3
> 22:47:10 1996 UTC) already said
>
>    LOCALBASE ?= /usr/local
>    PREFIX    ?= ${LOCALBASE}
>
[...]
>> I had a discussion in NGINX Unit about it, and
>> the decission for now has been: "support prefix=/usr/local for default
>> manual installation through the Makefile, and let BSD users adjust to
>> their preferred path".
>
> That's an *excellent* solution for the task, thanks for doing it
> the right way.  By setting PREFIX=/usr/local by default in the
> upstream Makefile, you are minimizing the work for *BSD porters.
>
> The BSD ports frameworks will typically run the upstreak "make install"
> with the variable DESTDIR set to a custom value, for example
>
>    DESTDIR=/usr/ports/pobj/groff-1.23.0/fake-amd64
>
> so if the upstream Makefile sets PREFIX=/usr/local ,
> that's perfect, everything gets installed to the right place
> without an intervention by the person doing the porting.
>
> Of course, if the upstream Makefile would use some other PREFIX,
> that would not be a huge obstacle.  All we have to do in that case
> is pass the option --prefix=/usr/local to the ./configure script,
> or something equivalent if the software isn't using GNU configure.
>
>> We were concerned that we might get collisions
>> with the BSD port also installing in /usr/local, but that's the least
>> evil (and considering BSD users don't typically run `make install`, it's
>> not so bad).
>
> It's not bad at all.  It's perfect.
>
> Of course, if a user wants to install *without* the ports framework,
> they have to provide their own --prefix.  But that's not an issue
> because it is easy to do, and installing without a port is discouraged
> anyway.

===

Directory variables should never contain a trailing slash (I've
learned that the hard way, where some things would break
unexpectedly).  Especially, make(1) is likely to have problems
when things have double slashes or a trailing slash, since it
treats filenames as text strings.  I've removed the trailing slash
from the prefix, and added it to the derivate variables just after
the prefix.  pkg-config(1) also expects directory variables to have
no trailing slash.

===

I also removed the code that would set variables as depending on
the prefix if they didn't start with a slash, because that is a
rather non-obvious behavior, and things should not always depend
on prefix, but other dirs such as $(runstatedir), so if we keep
a similar behavior it would be very unreliable.  Better keep
variables intact if set, or use the default if unset.

===

Print the real defaults for ./configure --help, rather than the actual
values.

===

I used a subdirectory under the standard /var/lib for NXT_STATE,
instead of a homemade "state" dir that does the same thing.

===

Modified the Makefile to create some dirs that weren't being
created, and also remove those that weren't being removed in
uninstall, probably because someone forgot to add them.

===

Add new options for setting the new variables, and rename some to be
consistent with the standard names.  Keep the old ones at configuration
time for compatibility, but mark them as deprecated.  Don't keep the old
ones at exec time.

===

A summary of the default config is:

Unit configuration summary:

  bin directory: ............. "/usr/local/bin"
  sbin directory: ............ "/usr/local/sbin"
  lib directory: ............. "/usr/local/lib"
  include directory: ......... "/usr/local/include"
  man pages directory: ....... "/usr/local/share/man"
  modules directory: ......... "/usr/local/lib/unit/modules"
  state directory: ........... "/usr/local/var/lib/unit"
  tmp directory: ............. "/tmp"

  pid file: .................. "/usr/local/var/run/unit/unit.pid"
  log file: .................. "/usr/local/var/log/unit/unit.log"

  control API socket: ........ "unix:/usr/local/var/run/unit/control.unit.sock"

Link: <https://www.gnu.org/prep/standards/html_node/Directory-Variables.html>
Link: <https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html>
Reviewed-by: Artem Konev <a.konev@f5.com>
Reviewed-by: Andrew Clayton <a.clayton@nginx.com>
Tested-by: Andrew Clayton <a.clayton@nginx.com>
Reviewed-by: Konstantin Pavlov <thresh@nginx.com>
Signed-off-by: Alejandro Colomar <alx@nginx.com>
2023-01-31 23:47:53 +01:00

1498 lines
35 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
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;
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 '\--libstatedir' >/dev/null; then
echo "$cmd" \
| sed 's/ --/\n--/g' \
| grep '\--libstatedir' \
| cut -d' ' -f2;
else
$cmd --help \
| sed -n '/\--libstatedir/,+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; 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.';
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;