summaryrefslogblamecommitdiffhomepage
path: root/tools/setup-unit
blob: 286eef87ed98a8e9e4a94eb899e75cac61fb6ecd (plain) (tree)
1
2
3
4
5
6
7
8
9
                   



                                                                     
                                   


                                                                     















                                                                               

               

                                      
 
             
 








                                                               
 























                                                                              

 




                                         
 













                                                               
 


                                                         
 
                                                                         
 

                                                      
 
























































                                                                               
        


                  
 
 




































                                                                   


 














                                                                       
 

                                                           
 

                                                                           
 


                                
 



                                                                              
 



                                                                              
 















                                                                               

 
 

          
 


                                    
 













































                                                                         
 
 




                                                                       
 


























                                                                              

 
 


























































































                                                                             
 
       




















                                                   

              
                  
              


              
 



                                                   
 



































                                                                       

 
 


















                                                                            
 
       



















                                               
          

















































































































































































                                                                                                                                                                           
       
 
 
 























































































                                                                    

 
 























                                                                             
 
       
 
 
 








































                                                 


 

















                                                                                
 





















                                                                     

      






























                                                                                                                         

 
 




















                                                                           

 
 













































                                                

 
 




                                                                        
 


                                                                       
 


                                                                           
 


                                                   
 

                                                                             
 


                                                                              
 


                                
 






























































































































                                                                                                                                                                                                          
          








                                                                         
          

                                                                          
          



































































                                                                                  
          


                          

          
                                            
          


         
 




                                        
 




































































                                                                                 

 
 
























































































































                                                                       
#!/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) is not 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 you run this script with';
    >&2 echo 'another shell, like for example 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
                Creates an initial configuration to serve a welcome web page
                for NGINX Unit.

OPTIONS
        -h, --help
                Print this help.

        --help-more
                Print help for more commands.  They are experimental.  This is
                not 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 through its control 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
                This script probes the OS, and prints details about the
                version.

        ps      List unitd(8) processes.

        repo-config
                Configure your package manager with the NGINX Unit
                repository for later installation

        sock    Print the address of the API control socket.

        welcome
                Creates an initial configuration to serve a welcome web page
                for NGINX Unit.

OPTIONS
        -h, --help
                Print the 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 instances of unitd(8).

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 socket.

        Run '$program_name ctl SUBCOMMAND -h' for more information on a
        subcommand.

SUBCOMMANDS
        http    Send an HTTP request to the control socket.

        insert  Insert an element into a specified index in an array in the
                JSON configuration.

OPTIONS
        -h, --help
                Print this help.

        -s, --sock SOCK
                Use SOCK as the API control socket address.  If not specified,
                the script will try to find it.  This will be used by
                subcommands.

                The socket can be a tcp(7) socket or a unix(7) socket, and in
                the case of a unix(7) socket, it can be local, or it can be in
                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; you should
                have a look at:
                <https://unit.nginx.org/howto/security/#secure-socket-and-stat>

ENVIRONMENT
        Options take precedence over their equivalent environment variables,
        so if both are specified, the option will be 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.  It can be used multiple times, which will be
                appended (and also appended to the contents of
                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 into a specified position (INDEX) in the JSON array
        in the unitd(8) configuration API at PATH.

        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 instalation 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 instea 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 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 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.  If you are sure you want';
                err  'to overwrite its current configuration, run 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 where to insert the element.

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 details about the version.  It
        prints three fields, delimited by ':'; the first is the package manager,
        the second is the OS name, and 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 your OS, and works accordingly.
        However, in case the 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, but for
                debian derivatives, the codename should be used.

OPTIONS
        -h, --help
                Print this help.

        -n, --dry-run
                Dry run.  Print the commands to be run instea 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 address of the control API socket of running instances of
        unitd(8).

        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 to run other commands, such as curl(1)
                or ssh(1).

        find    Find and print the address of the control API socket of
                running instances of unitd(8).

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 to run 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 address of the control API socket of running
        instances of unitd(8).

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;