1#!/usr/bin/env bash 2 3##################################################################### 4# 5# Copyright (C) NGINX, Inc. 6# Author: NGINX Unit Team, F5 Inc. 7# 8##################################################################### 9 10 11if test -n ${BASH_VERSION} && test "${BASH_VERSINFO[0]}" -eq 3; then 12 >&2 cat <<__EOF__ ; 13Your version of bash(1) isn't supported by this script. You're probably 14running on macOS. We recommend that you either install a newer version 15of bash(1) or run this script with another shell, such as zsh(1): 16 17 $ ${SUDO_USER:+sudo }zsh $0 ... 18__EOF__ 19 exit 1; 20fi; 21 22 23set -Eefuo pipefail; 24 25test -v BASH_VERSION \ 26&& shopt -s lastpipe; 27 28test -v ZSH_VERSION \ 29&& setopt sh_word_split; 30 31export LC_ALL=C 32 33dry_run='no'; 34 35help_unit() 36{ 37 cat <<__EOF__ ; 38SYNOPSIS 39 $0 [-h] COMMAND [ARGS] 40 41 Subcommands 42 ├── repo-config [-hn] [PKG-MANAGER OS-NAME OS-VERSION] 43 └── welcome [-hn] 44 45DESCRIPTION 46 This script simplifies installing and configuring an NGINX Unit server 47 for first-time users. 48 49 Run '$0 COMMAND -h' for more information on a command. 50 51COMMANDS 52 repo-config 53 Configure your package manager with the NGINX Unit repository 54 for later installation. 55 56 welcome 57 Create an initial configuration to serve a welcome web page 58 with NGINX Unit. 59 60OPTIONS 61 -h, --help 62 Print this help. 63 64 --help-more 65 Print help for more commands. They are experimental. Using 66 these isn't recommended, unless you know what you're doing. 67 68__EOF__ 69} 70 71help_more_unit() 72{ 73 cat <<__EOF__ ; 74SYNOPSIS 75 $0 [-h] COMMAND [ARGS] 76 77 Subcommands 78 ├── cmd [-h] 79 ├── ctl [-h] [-s SOCK] SUBCOMMAND [ARGS] 80 │ ├── edit [-h] PATH 81 │ ├── http [-h] [-c CURLOPT] METHOD PATH 82 │ └── insert [-h] PATH INDEX 83 ├── freeport [-h] 84 ├── json-ins [-hn] JSON INDEX 85 ├── os-probe [-h] 86 ├── ps [-h] [-t TYPE] 87 ├── repo-config [-hn] [PKG-MANAGER OS-NAME OS-VERSION] 88 ├── restart [-hls] 89 ├── sock [-h] SUBCOMMAND [ARGS] 90 │ ├── filter [-chs] 91 │ └── find [-h] 92 └── welcome [-hn] 93 94DESCRIPTION 95 This script simplifies installing and configuring 96 an NGINX Unit server for first-time users. 97 98 Run '$0 COMMAND -h' for more information on a command. 99 100COMMANDS 101 cmd Print the invocation line of unitd(8). 102 103 ctl Control a running unitd(8) instance via its control API socket. 104 105 freeport 106 Print an available TCP port. 107 108 json-ins 109 Insert a JSON element read from standard input into a JSON 110 array read from a file at a given INDEX. 111 112 os-probe 113 Probe the OS and print details about its version. 114 115 ps List unitd(8) processes. 116 117 repo-config 118 Configure your package manager with the NGINX Unit 119 repository for later installation. 120 121 sock Print the control API socket address. 122 123 welcome 124 Create an initial configuration to serve a welcome web page 125 with NGINX Unit. 126 127OPTIONS 128 -h, --help 129 Print basic help (some commands are hidden). 130 131 --help-more 132 Print the hidden help with more commands. 133 134__EOF__ 135} 136 137warn() 138{ 139 >&2 echo "$(basename "$0"): error: $*"; 140} 141 142err() 143{ 144 >&2 echo "$(basename "$0"): error: $*"; 145 exit 1; 146} 147 148dry_run_echo() 149{ 150 if test "$dry_run" = "yes"; then 151 echo "$*"; 152 fi; 153} 154 155dry_run_eval() 156{ 157 if test "$dry_run" = "yes"; then 158 echo " $*"; 159 else 160 eval "$*"; 161 fi; 162} 163 164 165help_unit_cmd() 166{ 167 cat <<__EOF__ ; 168SYNOPSIS 169 $0 cmd [-h] 170 171DESCRIPTION 172 Print the invocation line of running unitd(8) instances. 173 174OPTIONS 175 -h, --help 176 Print this help. 177 178__EOF__ 179} 180 181 182unit_cmd() 183{ 184 while test $# -ge 1; do 185 case "$1" in 186 -h | --help) 187 help_unit_cmd; 188 exit 0; 189 ;; 190 -*) 191 err "cmd: $1: Unknown option."; 192 ;; 193 *) 194 err "cmd: $1: Unknown argument."; 195 ;; 196 esac; 197 shift; 198 done; 199 200 unit_ps -t m \ 201 | sed 's/.*\[\(.*\)].*/\1/'; 202} 203 204 205help_unit_ctl() 206{ 207 cat <<__EOF__ ; 208SYNOPSIS 209 $0 ctl [-h] [-s SOCK] SUBCOMMAND [ARGS] 210 211 Subcommands 212 ├── edit [-h] PATH 213 ├── http [-h] [-c CURLOPT] METHOD PATH 214 └── insert [-h] PATH INDEX 215 216DESCRIPTION 217 Control a running unitd(8) instance through its control API socket. 218 219 Run '$0 ctl SUBCOMMAND -h' for more information on a 220 subcommand. 221 222SUBCOMMANDS 223 edit Edit the unitd(8) configuration with an editor. 224 225 http Send an HTTP request to the control API socket. 226 227 insert Insert an element at the specified index into an array in the 228 JSON configuration. 229 230OPTIONS 231 -h, --help 232 Print this help. 233 234 -s, --sock SOCK 235 Use SOCK as the control API socket address. If not specified, 236 the script tries to find it. This value is used by subcommands. 237 238 The socket can be a tcp(7) socket or a unix(7) socket; in 239 the case of a unix(7) socket, it can exist locally or on 240 a remote machine, accessed through ssh(1). Accepted syntax 241 for SOCK: 242 243 unix:/path/to/control.sock 244 ssh://[user@]host[:port]/path/to/control.sock 245 [http[s]://]host[:port] 246 247 The last form is less secure than the first two; have a look: 248 <https://unit.nginx.org/howto/security/#secure-socket-and-stat> 249 250ENVIRONMENT 251 Options take precedence over their equivalent environment variables; 252 if both are specified, the command-line option is used. 253 254 UNIT_CTL_SOCK 255 Equivalent to the option -s (--sock). 256 257__EOF__ 258} 259 260 261unit_ctl() 262{ 263 264 if test -v UNIT_CTL_SOCK; then 265 local sock="$UNIT_CTL_SOCK"; 266 fi; 267 268 while test $# -ge 1; do 269 case "$1" in 270 -h | --help) 271 help_unit_ctl; 272 exit 0; 273 ;; 274 -s | --sock) 275 if ! test $# -ge 2; then 276 err "ctl: $1: Missing argument."; 277 fi; 278 local sock="$2"; 279 shift; 280 ;; 281 -*) 282 err "ctl: $1: Unknown option."; 283 ;; 284 *) 285 break; 286 ;; 287 esac; 288 shift; 289 done; 290 291 if test ! $# -ge 1; then 292 err 'ctl: Missing subcommand.'; 293 fi; 294 295 if ! test -v sock; then 296 local sock="$(unit_sock_find)"; 297 fi; 298 299 if echo $sock | grep '^ssh://' >/dev/null; then 300 local remote="$(echo $sock | sed 's,\(ssh://[^/]*\).*,\1,')"; 301 local sock="$(echo $sock | sed 's,ssh://[^/]*\(.*\),unix:\1,')"; 302 fi; 303 304 case $1 in 305 edit) 306 shift; 307 unit_ctl_edit ${remote:+ ---r $remote} ---s "$sock" $@; 308 ;; 309 http) 310 shift; 311 unit_ctl_http ${remote:+ ---r $remote} ---s "$sock" $@; 312 ;; 313 insert) 314 shift; 315 unit_ctl_insert ${remote:+ ---r $remote} ---s "$sock" $@; 316 ;; 317 *) 318 err "ctl: $1: Unknown argument."; 319 ;; 320 esac; 321} 322 323 324help_unit_ctl_edit() 325{ 326 cat <<__EOF__ ; 327SYNOPSIS 328 $0 ctl [CTL-OPTS] edit [-h] PATH 329 330DESCRIPTION 331 Edit the JSON configuration with an editor. The current configuration 332 is downloaded into a temporary file, open with the editor, and then 333 sent back to the control API socket. 334 335 The following editors are tried in this order of preference: \$VISUAL, 336 \$EDITOR, editor(1), vi(1), vim(1), ed(1). 337 338 339OPTIONS 340 -h, --help 341 Print this help. 342 343ENVIRONMENT 344 VISUAL 345 EDITOR 346 See environ(7). 347 348SEE ALSO 349 $0 ctl http -h; 350 351 update-alternatives(1) 352 353__EOF__ 354} 355 356 357unit_ctl_edit() 358{ 359 while test $# -ge 1; do 360 case "$1" in 361 -h | --help) 362 help_unit_ctl_edit; 363 exit 0; 364 ;; 365 ---r | ----remote) 366 local remote="$2"; 367 shift; 368 ;; 369 ---s | ----sock) 370 local sock="$2"; 371 shift; 372 ;; 373 -*) 374 err "ctl: edit: $1: Unknown option."; 375 ;; 376 *) 377 break; 378 ;; 379 esac; 380 shift; 381 done; 382 383 if ! test $# -ge 1; then 384 err 'ctl: insert: PATH: Missing argument.'; 385 fi; 386 local req_path="$1"; 387 388 if test -v remote; then 389 local remote_sock="$(echo "$sock" | unit_sock_filter -s)"; 390 local local_sock="$(mktemp -u -p /var/run/unit/)"; 391 local ssh_ctrl="$(mktemp -u -p /var/run/unit/)"; 392 393 mkdir -p /var/run/unit/; 394 395 ssh -fMNnT -S "$ssh_ctrl" \ 396 -o 'ExitOnForwardFailure yes' \ 397 -L "$local_sock:$remote_sock" "$remote"; 398 399 sock="unix:$local_sock"; 400 fi; 401 402 local tmp="$(mktemp ||:)"; 403 404 unit_ctl_http ---s "$sock" -c --no-progress-meter GET "$req_path" \ 405 </dev/null >"$tmp" \ 406 ||:; 407 408 $( 409 ((test -v VISUAL && test -n "$VISUAL") && printf '%s\n' "$VISUAL") \ 410 || ((test -v EDITOR && test -n "$EDITOR") && printf '%s\n' "$EDITOR") \ 411 || command -v editor \ 412 || command -v vi \ 413 || command -v vim \ 414 || echo ed; 415 ) "$tmp" \ 416 ||:; 417 418 unit_ctl_http ---s "$sock" PUT "$req_path" <"$tmp" \ 419 ||:; 420 421 if test -v remote; then 422 ssh -S "$ssh_ctrl" -O exit "$remote" 2>/dev/null; 423 unlink "$local_sock"; 424 fi; 425} 426 427 428help_unit_ctl_http() 429{ 430 cat <<__EOF__ ; 431SYNOPSIS 432 $0 ctl [CTL-OPTS] http [-h] [-c CURLOPT] METHOD PATH 433 434DESCRIPTION 435 Send an HTTP request to the unitd(8) control API socket. 436 437 The payload is read from standard input. 438 439OPTIONS 440 -c, --curl CURLOPT 441 Pass CURLOPT as an option to curl. This script is implemented 442 in terms of curl(1), so it's useful to be able to tweak its 443 behavior. The option can be cumulatively used multiple times 444 (the result is also appended to UNIT_CTL_HTTP_CURLOPTS). 445 446 -h, --help 447 Print this help. 448 449ENVIRONMENT 450 UNIT_CTL_HTTP_CURLOPTS 451 Equivalent to the option -c (--curl). 452 453EXAMPLES 454 $0 ctl http -c --no-progress-meter GET /config >tmp; 455 456SEE ALSO 457 <https://unit.nginx.org/controlapi/#api-manipulation> 458 459__EOF__ 460} 461 462 463unit_ctl_http() 464{ 465 local curl_options="${UNIT_CTL_HTTP_CURLOPTS:-}"; 466 467 while test $# -ge 1; do 468 case "$1" in 469 -c | --curl) 470 if ! test $# -ge 2; then 471 err "ctl: http: $1: Missing argument."; 472 fi; 473 curl_options="$curl_options $2"; 474 shift; 475 ;; 476 -h | --help) 477 help_unit_ctl_http; 478 exit 0; 479 ;; 480 ---r | ----remote) 481 local remote="$2"; 482 shift; 483 ;; 484 ---s | ----sock) 485 local sock="$2"; 486 shift; 487 ;; 488 -*) 489 err "ctl: http: $1: Unknown option."; 490 ;; 491 *) 492 break; 493 ;; 494 esac; 495 shift; 496 done; 497 498 if ! test $# -ge 1; then 499 err 'ctl: http: METHOD: Missing argument.'; 500 fi; 501 local method="$1"; 502 503 if ! test $# -ge 2; then 504 err 'ctl: http: PATH: Missing argument.'; 505 fi; 506 local req_path="$2"; 507 508 if test -v remote; then 509 local remote_sock="$(echo "$sock" | unit_sock_filter -s)"; 510 local local_sock="$(mktemp -u -p /var/run/unit/)"; 511 local ssh_ctrl="$(mktemp -u -p /var/run/unit/)"; 512 513 mkdir -p /var/run/unit/; 514 515 ssh -fMNnT -S "$ssh_ctrl" \ 516 -o 'ExitOnForwardFailure yes' \ 517 -L "$local_sock:$remote_sock" "$remote"; 518 519 sock="unix:$local_sock"; 520 fi; 521 522 curl $curl_options -X $method -d@- \ 523 $(echo "$sock" | unit_sock_filter -c)${req_path} \ 524 ||:; 525 526 if test -v remote; then 527 ssh -S "$ssh_ctrl" -O exit "$remote" 2>/dev/null; 528 unlink "$local_sock"; 529 fi; 530} 531 532 533help_unit_ctl_insert() 534{ 535 cat <<__EOF__ ; 536SYNOPSIS 537 $0 ctl [CTL-OPTS] insert [-h] PATH INDEX 538 539DESCRIPTION 540 Insert an element at the specified position (INDEX) into the JSON array 541 located at PATH in unitd(8) control API. 542 543 The new element is read from standard input. 544 545OPTIONS 546 -h, --help 547 Print this help. 548 549SEE ALSO 550 $0 ctl http -h; 551 552__EOF__ 553} 554 555 556unit_ctl_insert() 557{ 558 while test $# -ge 1; do 559 case "$1" in 560 -h | --help) 561 help_unit_ctl_insert; 562 exit 0; 563 ;; 564 ---r | ----remote) 565 local remote="$2"; 566 shift; 567 ;; 568 ---s | ----sock) 569 local sock="$2"; 570 shift; 571 ;; 572 -*) 573 err "ctl: insert: $1: Unknown option."; 574 ;; 575 *) 576 break; 577 ;; 578 esac; 579 shift; 580 done; 581 582 if ! test $# -ge 1; then 583 err 'ctl: insert: PATH: Missing argument.'; 584 fi; 585 local req_path="$1"; 586 587 if ! test $# -ge 2; then 588 err 'ctl: insert: INDEX: Missing argument.'; 589 fi; 590 local idx="$2"; 591 592 if test -v remote; then 593 local remote_sock="$(echo "$sock" | unit_sock_filter -s)"; 594 local local_sock="$(mktemp -u -p /var/run/unit/)"; 595 local ssh_ctrl="$(mktemp -u -p /var/run/unit/)"; 596 597 mkdir -p /var/run/unit/; 598 599 ssh -fMNnT -S "$ssh_ctrl" \ 600 -o 'ExitOnForwardFailure yes' \ 601 -L "$local_sock:$remote_sock" "$remote"; 602 603 sock="unix:$local_sock"; 604 fi; 605 606 local old="$(mktemp ||:)"; 607 608 unit_ctl_http ---s "$sock" -c --no-progress-meter GET "$req_path" \ 609 </dev/null >"$old" \ 610 ||:; 611 612 unit_json_ins "$old" "$idx" \ 613 | unit_ctl_http ---s "$sock" PUT "$req_path" \ 614 ||:; 615 616 if test -v remote; then 617 ssh -S "$ssh_ctrl" -O exit "$remote" 2>/dev/null; 618 unlink "$local_sock"; 619 fi; 620} 621 622 623help_unit_ctl_welcome() 624{ 625 cat <<__EOF__ ; 626SYNOPSIS 627 $0 welcome [-hn] 628 629DESCRIPTION 630 This script tests an NGINX Unit installation by creating an initial 631 configuration and serving a welcome web page. Recommended for 632 first-time users. 633 634OPTIONS 635 -h, --help 636 Print this help. 637 638 -n, --dry-run 639 Dry run. Print the commands to be run instead of actually 640 running them. Each command is preceded by a line explaining 641 what it does. 642 643__EOF__ 644} 645 646 647unit_ctl_welcome() 648{ 649 while test $# -ge 1; do 650 case "$1" in 651 -f | --force) 652 local force='yes'; 653 ;; 654 -h | --help) 655 help_unit_ctl_welcome; 656 exit 0; 657 ;; 658 -n | --dry-run) 659 dry_run='yes'; 660 ;; 661 -*) 662 err "welcome: $1: Unknown option."; 663 ;; 664 *) 665 err "welcome: $1: Unknown argument."; 666 ;; 667 esac; 668 shift; 669 done; 670 671 command -v curl >/dev/null \ 672 || err 'welcome: curl(1) not found in PATH. It must be installed to run this script.'; 673 674 www='/srv/www/unit/index.html'; 675 if test -e "$www" && ! test -v force || ! test -w /srv; then 676 www="$HOME/srv/www/unit/index.html"; 677 fi; 678 if test -e "$www" && ! test -v force; then 679 www="$(mktemp)"; 680 mv "$www" "$www.html"; 681 www="$www.html" 682 fi; 683 684 unit_ps -t m \ 685 | wc -l \ 686 | read -r nprocs \ 687 ||: 688 689 if test 0 -eq "$nprocs"; then 690 warn "welcome: NGINX Unit isn't running."; 691 warn 'For help with starting NGINX Unit, see:'; 692 err " <https://unit.nginx.org/installation/#startup-and-shutdown>"; 693 elif test 1 -ne "$nprocs"; then 694 err 'welcome: Only one NGINX Unit instance should be running.'; 695 fi; 696 697 local sock="$(unit_sock_find)"; 698 local curl_opt="$(unit_sock_find | unit_sock_filter -c)"; 699 700 curl $curl_opt/ >/dev/null 2>&1 \ 701 || err "welcome: Can't reach the control API socket."; 702 703 if ! test -v force; then 704 unit_cmd \ 705 | read -r cmd; 706 707 # Check unitd is not configured already. 708 echo "$cmd" \ 709 | if grep '\--statedir' >/dev/null; then 710 echo "$cmd" \ 711 | sed 's/ --/\n--/g' \ 712 | grep '\--statedir' \ 713 | cut -d' ' -f2; 714 else 715 $cmd --help \ 716 | sed -n '/\--statedir/,+1p' \ 717 | grep 'default:' \ 718 | sed 's/ *default: "\(.*\)"/\1/'; 719 fi \ 720 | sed 's,$,/conf.json,' \ 721 | read -r conffile \ 722 ||:; 723 724 if test -e $conffile; then 725 if ! unit_ctl_http ---s "$sock" 'GET' '/config' </dev/null 2>/dev/null | grep -q '^{}.\?$'; # The '.\?' is for the possible carriage return. 726 then 727 warn 'welcome: NGINX Unit is already configured. To overwrite'; 728 err 'its current configuration, run the script again with --force.'; 729 fi; 730 fi; 731 fi; 732 733 ( 734 unit_freeport \ 735 || err "welcome: Can't find an available port."; 736 ) \ 737 | read -r port; 738 739 dry_run_echo 'Create a file to serve:'; 740 dry_run_eval "mkdir -p $(dirname $www);"; 741 dry_run_eval "tee '$www' >/dev/null"' <<__EOF__; 742 <!DOCTYPE html> 743 <html> 744 <head> 745 <title>Welcome to NGINX Unit</title> 746 <style type="text/css"> 747 body { background: white; color: black; font-family: sans-serif; margin: 2em; line-height: 1.5; } 748 h1,h2 { color: #00974d; } 749 li { margin-bottom: 0.5em; } 750 pre { background-color: beige; padding: 0.4em; } 751 hr { margin-top: 2em; border: 1px solid #00974d; } 752 .indent { margin-left: 1.5em; } 753 </style> 754 </head> 755 <body> 756 <h1>Welcome to NGINX Unit</h1> 757 <p>Congratulations! NGINX Unit is installed and running.</p> 758 <h3>Useful Links</h3> 759 <ul> 760 <li><b><a href="https://unit.nginx.org/configuration/?referer=welcome">https://unit.nginx.org/configuration/</a></b><br> 761 To get started with Unit, see the <em>Configuration</em> docs, starting with 762 the <em>Quick Start</em> guide.</li> 763 <li><b><a href="https://github.com/nginx/unit">https://github.com/nginx/unit</a></b><br> 764 See our GitHub repo to browse the code, contribute, or seek help from the 765 <a href="https://github.com/nginx/unit#community">community</a>.</li> 766 </ul> 767 768 <h2>Next steps</h2> 769 770 <h3>Check Current Configuration</h3> 771 <div class="indent"> 772 <p>Unit'"'"'s control API is currently listening for configuration changes 773 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 774 <b>'"$(unit_sock_find)"'</b><br> 775 To see the current configuration:</p> 776 <pre>'"${SUDO_USER:+sudo }"'curl '"$curl_opt"'/config</pre> 777 </div> 778 779 <h3>Change Listener Port</h3> 780 <div class="indent"> 781 <p>This page is served over a random TCP high port. To choose the default HTTP port (80), 782 replace the <b>"listeners"</b> object:</p> 783 <pre>echo '"'"'{"*:80": {"pass": "routes"}}'"'"' | '"${SUDO_USER:+sudo }"'curl -X PUT -d@- '"$curl_opt"'/config/listeners</pre> 784 Then remove the port number from the address bar and reload the page. 785 </div> 786 787 <hr> 788 <p><a href="https://unit.nginx.org/?referer=welcome">NGINX Unit — the universal web app server</a><br> 789 NGINX, Inc. © 2023</p> 790 </body> 791 </html> 792__EOF__'; 793 dry_run_echo; 794 dry_run_echo 'Give it appropriate permissions:'; 795 dry_run_eval "chmod 644 '$www';"; 796 dry_run_echo; 797 798 dry_run_echo 'Configure unitd:' 799 dry_run_eval "cat <<__EOF__ \\ 800 | sed 's/8080/$port/' \\ 801 | curl -X PUT -d@- $curl_opt/config; 802 { 803 \"listeners\": { 804 \"*:8080\": { 805 \"pass\": \"routes\" 806 } 807 }, 808 \"routes\": [{ 809 \"action\": { 810 \"share\": \"$www\" 811 } 812 }] 813 } 814__EOF__"; 815 816 dry_run_echo; 817 818 echo; 819 echo 'You may want to try the following commands now:'; 820 echo; 821 echo 'Check out current unitd configuration:'; 822 echo " ${SUDO_USER:+sudo} curl $curl_opt/config"; 823 echo; 824 echo 'Browse the welcome page:'; 825 echo " curl http://localhost:$port/"; 826} 827 828 829help_unit_freeport() 830{ 831 cat <<__EOF__ ; 832SYNOPSIS 833 $0 freeport [-h] 834 835DESCRIPTION 836 Print an available TCP port. 837 838OPTIONS 839 -h, --help 840 Print this help. 841 842__EOF__ 843} 844 845 846unit_freeport() 847{ 848 while test $# -ge 1; do 849 case "$1" in 850 -h | --help) 851 help_unit_freeport; 852 exit 0; 853 ;; 854 -*) 855 err "freeport: $1: Unknown option."; 856 ;; 857 *) 858 err "freeport: $1: Unknown argument."; 859 ;; 860 esac; 861 shift; 862 done; 863 864 freeport="$(mktemp -t freeport-XXXXXX)"; 865 866 cat <<__EOF__ \ 867 | cc -x c -o $freeport -; 868 #include <netinet/in.h> 869 #include <stdio.h> 870 #include <stdlib.h> 871 #include <strings.h> 872 #include <sys/socket.h> 873 #include <unistd.h> 874 875 876 int32_t get_free_port(void); 877 878 879 int 880 main(void) 881 { 882 int32_t port; 883 884 port = get_free_port(); 885 if (port == -1) 886 exit(EXIT_FAILURE); 887 888 printf("%d\n", port); 889 exit(EXIT_SUCCESS); 890 } 891 892 893 int32_t 894 get_free_port(void) 895 { 896 int sfd; 897 int32_t port; 898 socklen_t len; 899 struct sockaddr_in addr; 900 901 port = -1; 902 903 sfd = socket(PF_INET, SOCK_STREAM, 0); 904 if (sfd == -1) { 905 perror("socket()"); 906 return -1; 907 } 908 909 bzero(&addr, sizeof(addr)); 910 addr.sin_family = AF_INET; 911 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 912 addr.sin_port = htons(0); // random port 913 914 len = sizeof(addr); 915 if (bind(sfd, (struct sockaddr *) &addr, len)) { 916 perror("bind()"); 917 goto fail; 918 } 919 920 if (getsockname(sfd, (struct sockaddr *) &addr, &len)) { 921 perror("getsockname()"); 922 goto fail; 923 } 924 925 port = ntohs(addr.sin_port); 926 927 fail: 928 close(sfd); 929 return port; 930 } 931__EOF__ 932 933 $freeport; 934} 935 936 937help_unit_json_ins() 938{ 939cat <<__EOF__ ; 940SYNOPSIS 941 $0 json-ins [-hn] JSON INDEX 942 943ARGUMENTS 944 JSON Path to a JSON file containing a top-level array. 945 946 INDEX Position in the array to insert the element at. 947 948DESCRIPTION 949 Insert a JSON element read from standard input into a JSON array read 950 from a file at a given INDEX. 951 952 The resulting array is printed to standard output. 953 954OPTIONS 955 -h, --help 956 Print this help. 957 958 -n, --dry-run 959 Dry run. Print the command to be run instead of actually 960 running it. 961 962__EOF__ 963} 964 965 966unit_json_ins() 967{ 968 while test $# -ge 1; do 969 case "$1" in 970 -h | --help) 971 help_unit_json_ins; 972 exit 0; 973 ;; 974 -n | --dry-run) 975 dry_run='yes'; 976 ;; 977 -*) 978 err "json-ins: $1: Unknown option."; 979 ;; 980 *) 981 break; 982 ;; 983 esac; 984 shift; 985 done; 986 987 if ! test $# -ge 1; then 988 err 'json-ins: JSON: Missing argument.'; 989 fi; 990 local arr=$1; 991 992 if ! test $# -ge 2; then 993 err 'json-ins: INDEX: Missing argument.'; 994 fi; 995 local idx=$2; 996 997 dry_run_eval "( 998 jq '.[0:$idx]' <'$arr'; 999 echo '['; 1000 jq .; 1001 echo ']'; 1002 jq '.[$idx:]' <'$arr'; 1003 ) \\ 1004 | sed '/^\[]$/d' \\ 1005 | sed '/^]$/{N;s/^]\n\[$/,/}' \\ 1006 | jq .;" 1007} 1008 1009 1010help_unit_os_probe() 1011{ 1012 cat <<__EOF__ ; 1013SYNOPSIS 1014 $0 os-probe [-h] 1015 1016DESCRIPTION 1017 This script probes the OS and prints three fields, delimited by ':'; 1018 the first is the package manager, the second is the OS name, the third 1019 is the OS version. 1020 1021OPTIONS 1022 -h, --help 1023 Print this help. 1024 1025__EOF__ 1026} 1027 1028 1029unit_os_probe() 1030{ 1031 while test $# -ge 1; do 1032 case "$1" in 1033 -h | --help) 1034 help_unit_os_probe; 1035 exit 0; 1036 ;; 1037 -*) 1038 err "os-probe: $1: Unknown option."; 1039 ;; 1040 *) 1041 err "os-probe: $1: Unknown argument."; 1042 ;; 1043 esac; 1044 shift; 1045 done; 1046 1047 local os=$(uname | tr '[:upper:]' '[:lower:]') 1048 1049 if [ "$os" != 'linux' ] && [ "$os" != 'freebsd' ]; then 1050 err "os-probe: The OS isn't Linux or FreeBSD; can't proceed." 1051 fi 1052 1053 if [ "$os" = 'linux' ]; then 1054 if command -v apt-get >/dev/null; then 1055 local pkgMngr='apt'; 1056 elif command -v dnf >/dev/null; then 1057 local pkgMngr='dnf'; 1058 elif command -v yum >/dev/null; then 1059 local pkgMngr='yum'; 1060 else 1061 local pkgMngr=''; 1062 fi; 1063 1064 local osRelease='/etc/os-release'; 1065 1066 if [ -f "$osRelease" ]; then 1067 # The value for the ID and VERSION_ID may or may not be in quotes 1068 local osName=$(grep "^ID=" "$osRelease" | sed s/\"//g | awk -F= '{ print $2 }' ||:) 1069 local osVersion=$(grep '^VERSION_ID=' "$osRelease" | sed s/\"//g | awk -F= '{ print $2 }' || lsb_release -cs) 1070 else 1071 err "os-probe: Unable to determine OS and version, or the OS isn't supported." 1072 fi 1073 else 1074 local pkgMngr='pkg'; 1075 local osName=$os 1076 local osVersion=$(uname -rs | awk -F '[ -]' '{print $2}' ||:) 1077 if [ -z "$osVersion" ]; then 1078 err 'os-probe: Unable to get the FreeBSD version.' 1079 fi 1080 fi 1081 1082 osName=$(echo "$osName" | tr '[:upper:]' '[:lower:]') 1083 echo "$pkgMngr:$osName:$osVersion" 1084} 1085 1086 1087help_unit_ps() 1088{ 1089 cat <<__EOF__ ; 1090SYNOPSIS 1091 $0 ps [-h] [-t TYPE] 1092 1093DESCRIPTION 1094 List unitd(8) processes. 1095 1096OPTIONS 1097 -h, --help 1098 Print this help. 1099 1100 -t, --type TYPE 1101 List only processes of type TYPE. The available types are: 1102 1103 - controller (c) 1104 - main (m) 1105 - router (r) 1106 1107__EOF__ 1108} 1109 1110 1111unit_ps() 1112{ 1113 while test $# -ge 1; do 1114 case "$1" in 1115 -h | --help) 1116 help_unit_ps; 1117 exit 0; 1118 ;; 1119 -t | --type) 1120 if ! test $# -ge 2; then 1121 err "ps: $1: Missing argument."; 1122 fi; 1123 local type=; 1124 case "$2" in 1125 c | controller) 1126 local type_c='c'; 1127 ;; 1128 m | main) 1129 local type_m='m'; 1130 ;; 1131 r | router) 1132 local type_r='r'; 1133 ;; 1134 esac; 1135 shift; 1136 ;; 1137 -*) 1138 err "ps: $1: Unknown option."; 1139 ;; 1140 *) 1141 err "ps: $1: Unknown argument."; 1142 ;; 1143 esac; 1144 shift; 1145 done; 1146 1147 ps awwx \ 1148 | if test -v type; then 1149 grep ${type_c:+-e 'unit: controller'} \ 1150 ${type_m:+-e 'unit: main'} \ 1151 ${type_r:+-e 'unit: router'}; 1152 else 1153 grep 'unit: '; 1154 fi \ 1155 | grep -v grep \ 1156 ||: 1157} 1158 1159 1160help_unit_repo_config() 1161{ 1162 cat <<__EOF__ ; 1163SYNOPSIS 1164 $0 repo-config [-hn] [PKG-MANAGER OS-NAME OS-VERSION] 1165 1166DESCRIPTION 1167 This script configures the NGINX Unit repository for the system 1168 package manager. 1169 1170 The script automatically detects the OS and proceeds accordingly. 1171 However, if this automatic selection fails, you may specify the 1172 package manager and the OS name and version. 1173 1174ARGUMENTS 1175 PKG-MANAGER 1176 Supported: 'apt', 'dnf', and 'yum'. 1177 1178 OS-NAME 1179 Supported: 'debian', 'ubuntu', 'fedora', 'rhel', and 'amzn2'. 1180 1181 OS-VERSION 1182 For most distributions, this should be a numeric value; for 1183 Debian derivatives, use the codename instead. 1184 1185OPTIONS 1186 -h, --help 1187 Print this help. 1188 1189 -n, --dry-run 1190 Dry run. Print the commands to be run instead of actually 1191 running them. Each command is preceded by a line explaining 1192 what it does. 1193 1194EXAMPLES 1195 $ $(basename "$0") repo-config apt debian bullseye; 1196 $ $(basename "$0") repo-config apt ubuntu jammy; 1197 $ $(basename "$0") repo-config dnf fedora 36; 1198 $ $(basename "$0") repo-config dnf rhel 9; 1199 $ $(basename "$0") repo-config yum amzn2 2; 1200 1201__EOF__ 1202} 1203 1204 1205unit_repo_config() 1206{ 1207 installAPT () 1208 { 1209 local os_name="$2"; 1210 1211 dry_run_echo "Install on $os_name"; 1212 dry_run_echo; 1213 dry_run_eval 'curl --output /usr/share/keyrings/nginx-keyring.gpg https://unit.nginx.org/keys/nginx-keyring.gpg;'; 1214 dry_run_echo; 1215 dry_run_eval 'apt-get install -y apt-transport-https lsb-release ca-certificates;'; 1216 1217 if test $# -ge 3; then 1218 local os_version="$3"; 1219 else 1220 local os_version='$(lsb_release -cs)'; 1221 fi; 1222 1223 dry_run_echo; 1224 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;"; 1225 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;"; 1226 dry_run_echo; 1227 dry_run_eval 'apt-get update;'; 1228 } 1229 1230 installYumDnf () 1231 { 1232 local pkg_mngr="$1"; 1233 local os_name="$2"; 1234 1235 if test $# -ge 3; then 1236 local os_version="$3"; 1237 else 1238 local os_version='\$releasever'; 1239 fi; 1240 1241 dry_run_echo "Install on $os_name"; 1242 dry_run_echo; 1243 1244 dry_run_eval "cat >/etc/yum.repos.d/unit.repo <<__EOF__ 1245[unit] 1246name=unit repo 1247baseurl=https://packages.nginx.org/unit/$os_name/$os_version/\\\$basearch/ 1248gpgcheck=0 1249enabled=1 1250__EOF__"; 1251 1252 dry_run_echo; 1253 dry_run_eval "$pkg_mngr makecache;"; 1254 } 1255 1256 while test $# -ge 1; do 1257 case "$1" in 1258 -h | --help) 1259 help_unit_repo_config; 1260 exit 0; 1261 ;; 1262 -n | --dry-run) 1263 dry_run='yes'; 1264 ;; 1265 -*) 1266 err "repo-config: $1: Unknown option."; 1267 ;; 1268 *) 1269 break; 1270 ;; 1271 esac; 1272 shift; 1273 done; 1274 1275 if test $# -ge 1; then 1276 local pkg_mngr="$1"; 1277 1278 if ! test $# -ge 2; then 1279 err "repo-config: OS-NAME: Missing argument."; 1280 fi; 1281 local os_name="$2"; 1282 1283 if ! test $# -ge 3; then 1284 err "repo-config: OS-VERSION: Missing argument."; 1285 fi; 1286 local os_version="$3"; 1287 fi; 1288 1289 command -v curl >/dev/null \ 1290 || err 'repo-config: curl(1) not found in PATH. It must be installed to run this script.'; 1291 1292 echo 'This script sets up the NGINX Unit repository'; 1293 1294 if ! test $# -ge 3; then 1295 local os_pkg_name_version=$(unit_os_probe || warn "On macOS, try 'brew install nginx/unit/unit'.") 1296 local pkg_mngr=$(echo "$os_pkg_name_version" | awk -F: '{print $1}') 1297 local os_name=$(echo "$os_pkg_name_version" | awk -F: '{print $2}') 1298 local os_version=$(echo "$os_pkg_name_version" | awk -F: '{print $3}') 1299 fi; 1300 1301 # Call the appropriate installation function 1302 case "$pkg_mngr" in 1303 apt) 1304 case "$os_name" in 1305 debian | ubuntu) 1306 installAPT "$pkg_mngr" "$os_name" ${3:+$os_version}; 1307 ;; 1308 *) 1309 err "repo-config: $os_name: The OS isn't supported."; 1310 ;; 1311 esac 1312 ;; 1313 yum | dnf) 1314 case "$os_name" in 1315 rhel | amzn | fedora) 1316 installYumDnf "$pkg_mngr" "$os_name" "$os_version" ${3:+ovr}; 1317 ;; 1318 *) 1319 err "repo-config: $os_name: The OS isn't supported."; 1320 ;; 1321 esac; 1322 ;; 1323 *) 1324 err "repo-config: $pkg_mngr: The package manager isn't supported."; 1325 ;; 1326 esac; 1327 1328 echo 1329 echo 'All done; the NGINX Unit repository is set up.'; 1330 echo "Configured with '$pkg_mngr' on '$os_name' '$os_version'."; 1331 echo 'Further steps: <https://unit.nginx.org/installation/#official-packages>' 1332} 1333 1334 1335help_unit_restart() 1336{ 1337 cat <<__EOF__ ; 1338SYNOPSIS 1339 $0 restart [-hls] 1340 1341DESCRIPTION 1342 Restart all running unitd(8) instances. 1343 1344OPTIONS 1345 -h, --help 1346 Print this help. 1347 1348 -l, --log 1349 Reset log file. 1350 1351 -s, --statedir 1352 Reset \$statedir. 1353 1354CAVEATS 1355 This command will ask for confirmation before removing 1356 directories; please review those prompts with care, as unknown 1357 bugs in the command may attempt to wipe your file system. 1358 1359__EOF__ 1360} 1361 1362 1363unit_restart() 1364{ 1365 while test $# -ge 1; do 1366 case "$1" in 1367 -h | --help) 1368 help_unit_restart; 1369 exit 0; 1370 ;; 1371 -l | --log) 1372 local log_flag='yes'; 1373 ;; 1374 -s | --statedir) 1375 local state_flag='yes'; 1376 ;; 1377 -*) 1378 err "restart: $1: Unknown option."; 1379 ;; 1380 *) 1381 err "restart: $1: Unknown argument."; 1382 ;; 1383 esac; 1384 shift; 1385 done; 1386 1387 local cmds="$(unit_cmd)"; 1388 1389 pkill -e unitd; 1390 1391 printf '%s\n' "$cmds" \ 1392 | while read -r cmd; do 1393 if test -v log_flag; then 1394 ( 1395 echo "$cmd" \ 1396 | grep '\--log' \ 1397 | sed 's/.*--log \+\([^ ]\+\).*/\1/' \ 1398 || eval $cmd --help \ 1399 | grep -A1 '\--log FILE' \ 1400 | grep 'default:' \ 1401 | sed 's/.*"\(.*\)".*/\1/'; 1402 ) \ 1403 | xargs rm -f; 1404 fi; 1405 1406 if test -v state_flag; then 1407 ( 1408 echo "$cmd" \ 1409 | grep '\--statedir' \ 1410 | sed 's/.*--statedir \+\([^ ]\+\).*/\1/' \ 1411 || eval $cmd --help \ 1412 | grep -A1 '\--statedir DIR' \ 1413 | grep 'default:' \ 1414 | sed 's/.*"\(.*\)".*/\1/'; 1415 ) \ 1416 | xargs -I {} find {} -mindepth 1 -maxdepth 1 \ 1417 | xargs rm -rfi; 1418 fi; 1419 1420 eval $cmd; 1421 done; 1422} 1423 1424 1425help_unit_sock() 1426{ 1427 cat <<__EOF__ ; 1428SYNOPSIS 1429 $0 sock [-h] SUBCOMMAND [ARGS] 1430 1431 Subcommands 1432 ├── filter [-ch] 1433 └── find [-h] 1434 1435DESCRIPTION 1436 Print the control API socket address of running unitd(8) 1437 instances. 1438 1439 Run '$0 sock SUBCOMMAND -h' for more information on a 1440 subcommand. 1441 1442SUBCOMMANDS 1443 filter Filter the output of the 'find' subcommand and transform it 1444 to something suitable for running other commands, such as 1445 curl(1) or ssh(1). 1446 1447 find Find and print the control API socket address of running 1448 unitd(8) instances. 1449 1450OPTIONS 1451 -h, --help 1452 Print this help. 1453 1454__EOF__ 1455} 1456 1457 1458unit_sock() 1459{ 1460 while test $# -ge 1; do 1461 case "$1" in 1462 -h | --help) 1463 help_unit_sock; 1464 exit 0; 1465 ;; 1466 -*) 1467 err "sock: $1: Unknown option."; 1468 ;; 1469 *) 1470 break; 1471 ;; 1472 esac; 1473 shift; 1474 done; 1475 1476 if ! test $# -ge 1; then 1477 err 'sock: Missing subcommand.'; 1478 fi; 1479 1480 case $1 in 1481 filter) 1482 shift; 1483 unit_sock_filter $@; 1484 ;; 1485 find) 1486 shift; 1487 unit_sock_find $@; 1488 ;; 1489 *) 1490 err "sock: $1: Unknown subcommand."; 1491 ;; 1492 esac; 1493} 1494 1495 1496help_unit_sock_filter() 1497{ 1498 cat <<__EOF__ ; 1499SYNOPSIS 1500 $0 sock filter [-chs] 1501 1502DESCRIPTION 1503 Filter the output of the 'sock find' command and transform it to 1504 something suitable for running other commands, such as 1505 curl(1) or ssh(1). 1506 1507OPTIONS 1508 -c, --curl 1509 Print an argument suitable for curl(1). 1510 1511 -h, --help 1512 Print this help. 1513 1514 -s, --ssh 1515 Print a socket address suitable for use in an ssh(1) tunnel. 1516 1517__EOF__ 1518} 1519 1520 1521unit_sock_filter() 1522{ 1523 while test $# -ge 1; do 1524 case "$1" in 1525 -c | --curl) 1526 if test -v ssh_flag; then 1527 err "sock: filter: $1: Missing argument."; 1528 fi; 1529 local curl_flag='yes'; 1530 ;; 1531 -h | --help) 1532 help_unit_sock_filter; 1533 exit 0; 1534 ;; 1535 -s | --ssh) 1536 if test -v curl_flag; then 1537 err "sock: filter: $1: Missing argument."; 1538 fi; 1539 local ssh_flag='yes'; 1540 ;; 1541 -*) 1542 err "sock: filter: $1: Unknown option."; 1543 ;; 1544 *) 1545 err "sock: filter: $1: Unknown argument."; 1546 ;; 1547 esac; 1548 shift; 1549 done; 1550 1551 while read -r control; do 1552 1553 if test -v curl_flag; then 1554 if echo "$control" | grep '^unix:' >/dev/null; then 1555 unix_socket="$(echo "$control" | sed 's/unix:/--unix-socket /')"; 1556 host='http://localhost'; 1557 else 1558 unix_socket=''; 1559 host="$control"; 1560 fi; 1561 1562 echo "$unix_socket $host"; 1563 1564 elif test -v ssh_flag; then 1565 echo "$control" \ 1566 | sed -E 's,^(unix:|http://|https://),,'; 1567 1568 else 1569 echo "$control"; 1570 fi; 1571 done; 1572} 1573 1574 1575help_unit_sock_find() 1576{ 1577 cat <<__EOF__ ; 1578SYNOPSIS 1579 $0 sock find [-h] 1580 1581DESCRIPTION 1582 Find and print the control API socket address of running 1583 unitd(8) instances. 1584 1585OPTIONS 1586 -h, --help 1587 Print this help. 1588 1589__EOF__ 1590} 1591 1592 1593unit_sock_find() 1594{ 1595 while test $# -ge 1; do 1596 case "$1" in 1597 -h | --help) 1598 help_unit_sock_find; 1599 exit 0; 1600 ;; 1601 -*) 1602 err "sock: find: $1: Unknown option."; 1603 ;; 1604 *) 1605 err "sock: find: $1: Unknown argument."; 1606 ;; 1607 esac; 1608 shift; 1609 done; 1610 1611 unit_cmd \ 1612 | while read -r cmd; do 1613 if echo "$cmd" | grep '\--control' >/dev/null; then 1614 echo "$cmd" \ 1615 | sed 's/ --/\n--/g' \ 1616 | grep '\--control' \ 1617 | cut -d' ' -f2; 1618 else 1619 if ! command -v $cmd >/dev/null; then 1620 local cmd='unitd'; 1621 fi; 1622 $cmd --help \ 1623 | sed -n '/\--control/,+1p' \ 1624 | grep 'default:' \ 1625 | sed 's/ *default: "\(.*\)"/\1/'; 1626 fi; 1627 done; 1628} 1629 1630 1631while test $# -ge 1; do 1632 case "$1" in 1633 -h | --help) 1634 help_unit; 1635 exit 0; 1636 ;; 1637 --help-more) 1638 help_more_unit; 1639 exit 0; 1640 ;; 1641 -*) 1642 err "$1: Unknown option."; 1643 ;; 1644 *) 1645 break; 1646 ;; 1647 esac; 1648 shift; 1649done; 1650 1651if ! test $# -ge 1; then 1652 err "Missing command."; 1653fi; 1654 1655case $1 in 1656cmd) 1657 shift; 1658 unit_cmd $@; 1659 ;; 1660ctl) 1661 shift; 1662 unit_ctl $@; 1663 ;; 1664freeport) 1665 shift; 1666 unit_freeport $@; 1667 ;; 1668json-ins) 1669 shift; 1670 unit_json_ins $@; 1671 ;; 1672os-probe) 1673 shift; 1674 unit_os_probe $@; 1675 ;; 1676ps) 1677 shift; 1678 unit_ps $@; 1679 ;; 1680repo-config) 1681 shift; 1682 unit_repo_config $@; 1683 ;; 1684restart) 1685 shift; 1686 unit_restart $@; 1687 ;; 1688sock) 1689 shift; 1690 unit_sock $@; 1691 ;; 1692welcome) 1693 shift; 1694 unit_ctl_welcome $@; 1695 ;; 1696*) 1697 err "$1: Unknown command."; 1698 ;; 1699esac; 1700