Files
configure-server/.includes/nginx.sh

567 lines
15 KiB
Bash

#!/usr/bin/env bash
nginxConfigFile="/etc/nginx/nginx.conf"
nginxBackupFile="/etc/nginx/nginx.conf.configure-server.bak"
nginxSitesAvailable="/etc/nginx/sites-available"
nginxSitesEnabled="/etc/nginx/sites-enabled"
nginxDefaultSite="${nginxSitesAvailable}/default.conf"
nginxDefaultSiteLink="${nginxSitesEnabled}/default.conf"
clacksInfoUrl="https://xclacksoverhead.org/home/about"
nginxManagedInclude="include /etc/nginx/sites-enabled/*.conf;"
nginx_installed() {
pacman -Q nginx &> /dev/null
}
ufw_installed() {
pacman -Q ufw &> /dev/null
}
ensure_ufw() {
if ufw_installed; then
return 0
fi
if [[ "$(yesno "ufw is not installed. Install it now and continue?")" != "Yes" ]]; then
msgbox "Firewall action cancelled."
return 1
fi
if ! install_package ufw; then
msgbox "Failed to install ufw."
return 1
fi
msgbox "ufw installed."
return 0
}
enable_nginx_service() {
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" systemctl enable --now nginx; then
msgbox "nginx was configured, but the service failed to enable or start."
return 1
fi
return 0
}
web_ports_open() {
local statusLine=""
local hasHttp="No"
local hasHttps="No"
if ! ufw_installed; then
return 1
fi
while IFS= read -r statusLine; do
if [[ "$statusLine" =~ ^[[:space:]]*80/tcp([[:space:]]+\(v6\))?[[:space:]]+ALLOW([[:space:]]+IN)?[[:space:]]+Anywhere([[:space:]]+\(v6\))?[[:space:]]*$ ]]; then
hasHttp="Yes"
elif [[ "$statusLine" =~ ^[[:space:]]*443/tcp([[:space:]]+\(v6\))?[[:space:]]+ALLOW([[:space:]]+IN)?[[:space:]]+Anywhere([[:space:]]+\(v6\))?[[:space:]]*$ ]]; then
hasHttps="Yes"
fi
done < <(
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
sudo "${sudoFlags[@]}" ufw status 2>&1
)
[[ "$hasHttp" == "Yes" && "$hasHttps" == "Yes" ]]
}
offer_open_web_ports() {
if ! ufw_installed; then
return 0
fi
if web_ports_open; then
return 0
fi
if [[ "$(yesno "ufw is installed. Open 80/tcp and 443/tcp for nginx now?")" != "Yes" ]]; then
return 0
fi
open_web_ports
}
ensure_nginx() {
local clacksHeader=""
if nginx_installed; then
return 0
fi
if [[ "$(yesno "nginx is not installed. Install it now and continue?")" != "Yes" ]]; then
msgbox "nginx action cancelled."
return 1
fi
if ! install_package nginx; then
msgbox "Failed to install nginx."
return 1
fi
clacksHeader="$(prompt_clacks_header || true)"
setup_nginx_layout "$clacksHeader" || return 1
if ! test_nginx_config; then
return 1
fi
if ! enable_nginx_service; then
return 1
fi
offer_open_web_ports || return 1
msgbox "nginx is installed and running."
return 0
}
prompt_clacks_header() {
local rawNames=""
local formattedNames=()
local nameEntry=""
local clacksHeader=""
msgbox "X-Clacks-Overhead is an optional tribute header inspired by Terry Pratchett's Clacks. Websites use it to remember names with values such as GNU Terry Pratchett. For more information, please see: ${clacksInfoUrl}"
rawNames="$(inputbox "Enter a comma-separated list of names for the X-Clacks-Overhead header, or leave blank to skip.")" || return 1
if [[ -z "${rawNames//[[:space:]]/}" ]]; then
return 1
fi
while IFS= read -r nameEntry; do
nameEntry="${nameEntry#"${nameEntry%%[![:space:]]*}"}"
nameEntry="${nameEntry%"${nameEntry##*[![:space:]]}"}"
if [[ -n "$nameEntry" ]]; then
formattedNames+=("GNU ${nameEntry}")
fi
done < <(printf '%s\n' "$rawNames" | tr ',' '\n')
if [[ "${#formattedNames[@]}" -eq 0 ]]; then
return 1
fi
clacksHeader="$(printf '%s, ' "${formattedNames[@]}")"
clacksHeader="${clacksHeader%, }"
printf '%s\n' "$clacksHeader"
return 0
}
setup_nginx_layout() {
local clacksHeader="$1"
local generatedConfig=""
local tempConfig=""
if ! sudo "${sudoFlags[@]}" test -r "$nginxConfigFile"; then
msgbox "Unable to read ${nginxConfigFile}. Install nginx first."
return 1
fi
generatedConfig="$(generate_nginx_config "$clacksHeader")" || {
msgbox "Failed to generate nginx.conf."
return 1
}
tempConfig="$(mktemp)"
printf '%s\n' "$generatedConfig" > "$tempConfig"
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" install -d -m 755 "$nginxSitesAvailable" "$nginxSitesEnabled"; then
rm -f "$tempConfig"
msgbox "Failed to create the nginx site directories."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" test -e "$nginxBackupFile"; then
# shellcheck disable=SC2154
sudo "${sudoFlags[@]}" cp "$nginxConfigFile" "$nginxBackupFile" 2> /dev/null || true
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" install -m 644 "$tempConfig" "$nginxConfigFile"; then
rm -f "$tempConfig"
msgbox "Failed to write ${nginxConfigFile}."
return 1
fi
rm -f "$tempConfig"
ensure_default_site
}
generate_nginx_config() {
local clacksHeader="$1"
sudo "${sudoFlags[@]}" awk -v clacksHeader="$clacksHeader" -v managedInclude="$nginxManagedInclude" '
BEGIN {
skippingServer = 0
braceDepth = 0
insertedSitesInclude = 0
skipManagedComment = 0
}
{
if ($0 == "# Managed by configure-server") {
skipManagedComment = 1
next
}
if ($0 ~ /^[[:space:]]*add_header X-Clacks-Overhead /) {
next
}
if ($0 ~ /^[[:space:]]*include \/etc\/nginx\/sites-enabled\/\*\.conf;$/) {
next
}
if (skippingServer) {
opens = gsub(/\{/, "{")
closes = gsub(/\}/, "}")
braceDepth += opens - closes
if (braceDepth <= 0) {
skippingServer = 0
}
next
}
if ($0 ~ /^ server \{$/) {
skippingServer = 1
braceDepth = 1
next
}
if (skipManagedComment) {
skipManagedComment = 0
}
print
if (!insertedSitesInclude && $0 ~ /^[[:space:]]*#gzip[[:space:]]+on;/) {
if (clacksHeader != "") {
print ""
print " add_header X-Clacks-Overhead \"" clacksHeader "\" always;"
}
print ""
print " " managedInclude
insertedSitesInclude = 1
}
}
' "$nginxConfigFile"
}
ensure_default_site() {
local tempSiteFile=""
if [[ -e "$nginxDefaultSite" ]]; then
if [[ ! -L "$nginxDefaultSiteLink" ]]; then
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
sudo "${sudoFlags[@]}" ln -sf "$nginxDefaultSite" "$nginxDefaultSiteLink"
fi
return 0
fi
tempSiteFile="$(mktemp)"
cat > "$tempSiteFile" <<'EOF'
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
EOF
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" install -m 644 "$tempSiteFile" "$nginxDefaultSite"; then
rm -f "$tempSiteFile"
msgbox "Failed to create the default nginx site."
return 1
fi
rm -f "$tempSiteFile"
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ln -sf "$nginxDefaultSite" "$nginxDefaultSiteLink"; then
msgbox "Failed to enable the default nginx site."
return 1
fi
return 0
}
create_site() {
local siteName=""
local serverNames=""
local siteRoot=""
local tempSiteFile=""
local siteConfigFile=""
local defaultIndexFile=""
ensure_nginx || return 1
siteName="$(inputbox "Enter a short site name for the config file, for example example.com.")" || return 1
if [[ -z "$siteName" || ! "$siteName" =~ ^[A-Za-z0-9._-]+$ ]]; then
msgbox "Enter a valid site name using letters, numbers, dots, dashes, or underscores."
return 1
fi
serverNames="$(inputbox "Enter the server_name value, using spaces between names." "$siteName")" || return 1
siteRoot="$(inputbox "Enter the site document root." "/srv/http/${siteName}")" || return 1
if [[ -z "$siteRoot" ]]; then
msgbox "A document root is required."
return 1
fi
siteConfigFile="${nginxSitesAvailable}/${siteName}.conf"
tempSiteFile="$(mktemp)"
cat > "$tempSiteFile" <<EOF
server {
listen 80;
server_name ${serverNames};
root ${siteRoot};
index index.html index.htm;
location / {
try_files \$uri \$uri/ =404;
}
}
EOF
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" install -d -m 755 "$siteRoot"; then
rm -f "$tempSiteFile"
msgbox "Failed to create ${siteRoot}."
return 1
fi
defaultIndexFile="$(mktemp)"
cat > "$defaultIndexFile" <<EOF
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>${siteName}</title>
</head>
<body>
<h1>${siteName}</h1>
<p>nginx is serving ${siteName} from ${siteRoot}.</p>
</body>
</html>
EOF
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" install -m 644 "$tempSiteFile" "$siteConfigFile"; then
rm -f "$tempSiteFile" "$defaultIndexFile"
msgbox "Failed to write ${siteConfigFile}."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" test -e "${siteRoot}/index.html"; then
# shellcheck disable=SC2154
sudo "${sudoFlags[@]}" install -m 644 "$defaultIndexFile" "${siteRoot}/index.html" || true
fi
rm -f "$tempSiteFile" "$defaultIndexFile"
msgbox "Site ${siteName} was created in ${nginxSitesAvailable}. Enable it from the nginx menu when you are ready."
return 0
}
select_site_file() {
local searchPath="$1"
local filePattern="$2"
local selectedSite=""
local siteEntries=()
local sitePath=""
while IFS= read -r sitePath; do
[[ -n "$sitePath" ]] && siteEntries+=("$(basename "$sitePath")")
done < <(find "$searchPath" -maxdepth 1 -type "$filePattern" -name '*.conf' | sort)
if [[ "${#siteEntries[@]}" -eq 0 ]]; then
return 1
fi
selectedSite="$(menulist "${siteEntries[@]}")" || return 1
printf '%s\n' "$selectedSite"
}
enable_site() {
local siteName=""
ensure_nginx || return 1
siteName="$(select_site_file "$nginxSitesAvailable" f)" || {
msgbox "No available site configs were found."
return 1
}
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ln -sf "${nginxSitesAvailable}/${siteName}" "${nginxSitesEnabled}/${siteName}"; then
msgbox "Failed to enable ${siteName}."
return 1
fi
msgbox "${siteName} is enabled. Test and reload nginx when you are ready."
return 0
}
disable_site() {
local siteName=""
ensure_nginx || return 1
siteName="$(select_site_file "$nginxSitesEnabled" l)" || {
msgbox "No enabled sites were found."
return 1
}
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" rm -f "${nginxSitesEnabled}/${siteName}"; then
msgbox "Failed to disable ${siteName}."
return 1
fi
msgbox "${siteName} is disabled. Test and reload nginx when you are ready."
return 0
}
test_nginx_config() {
local tempFile=""
local status=0
local statusText=""
ensure_nginx || return 1
tempFile="$(mktemp)"
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
statusText="$(sudo "${sudoFlags[@]}" nginx -t 2>&1)"
status=$?
printf '%s\n' "$statusText" > "$tempFile"
textbox "$tempFile"
rm -f "$tempFile"
return "$status"
}
reload_nginx() {
ensure_nginx || return 1
if ! test_nginx_config; then
msgbox "nginx was not reloaded because the config test failed."
return 1
fi
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" systemctl reload nginx; then
msgbox "Failed to reload nginx."
return 1
fi
msgbox "nginx reloaded successfully."
return 0
}
open_web_ports() {
ensure_ufw || return 1
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ufw allow 80/tcp; then
msgbox "Failed to open 80/tcp."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ufw allow 443/tcp; then
msgbox "Failed to open 443/tcp."
return 1
fi
msgbox "Web ports 80/tcp and 443/tcp are allowed."
return 0
}
close_web_ports() {
ensure_ufw || return 1
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ufw delete allow 80/tcp; then
msgbox "Failed to close 80/tcp."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ufw delete allow 443/tcp; then
msgbox "Failed to close 443/tcp."
return 1
fi
msgbox "Web ports 80/tcp and 443/tcp were removed."
return 0
}
web_ports_menu_label() {
if web_ports_open; then
printf '%s\n' "Close web ports"
else
printf '%s\n' "Open web ports"
fi
}
while true; do
webPortsChoice="$(web_ports_menu_label)"
nginxChoice="$(menulist \
"Create site" \
"Enable site" \
"Disable site" \
"Test config" \
"Reload nginx" \
"$webPortsChoice" \
"Back")" || break
case "$nginxChoice" in
"Create site")
create_site
;;
"Enable site")
enable_site
;;
"Disable site")
disable_site
;;
"Test config")
test_nginx_config
;;
"Reload nginx")
reload_nginx
;;
"Open web ports")
open_web_ports
;;
"Close web ports")
close_web_ports
;;
"Back")
break
;;
esac
done