394 lines
11 KiB
Bash
Executable File
394 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
### Copyright 1999-2025. WebPros International GmbH. All rights reserved.
|
|
|
|
# --- utility functions ---
|
|
|
|
p_echo()
|
|
{
|
|
echo "$@" >&2
|
|
}
|
|
|
|
write() {
|
|
|
|
local filename="$1"
|
|
|
|
local directory=`dirname "$filename"`
|
|
mkdirectory "$directory"
|
|
if [ "x$?" != "x0" ] ; then
|
|
ERROR=$?
|
|
p_echo "Can not create directory $directory"
|
|
return $ERROR
|
|
fi
|
|
|
|
# Check target file can be replaced
|
|
if [ -f "$filename" ] ; then
|
|
if [ ! -w "$filename" -o ! -r "$filename" ] ; then
|
|
p_echo "Can not read/write to $filename"
|
|
return 100
|
|
fi
|
|
fi
|
|
|
|
# Put new content into temporary file
|
|
local tmpfile
|
|
tmpfile=`mktemp "$filename".XXXXXX`
|
|
if [ "x$?" != "x0" -o ! -w "$tmpfile" ] ; then
|
|
p_echo "Can not create temporary file $tmpfile"
|
|
return 101
|
|
fi
|
|
|
|
# Correct owner & permissions
|
|
chown "$FILE_OWNERGROUP" "$tmpfile" && chmod "$FILE_PERMS" "$tmpfile"
|
|
|
|
cat > "$tmpfile"
|
|
if [ "x$?" != "x0" ] ; then
|
|
p_echo "Error writing to $tmpfile"
|
|
rm -f "$tmpfile"
|
|
return 101
|
|
fi
|
|
|
|
# Check that new file is not empty
|
|
if [ ! -s "$tmpfile" ] ; then
|
|
p_echo "No or empty input supplied on utility's standard input stream!"
|
|
rm -f "$tmpfile"
|
|
return 101
|
|
fi
|
|
|
|
# Commit changes to target file (disable interactivity in mv)
|
|
mv -f "$tmpfile" "$filename"
|
|
if [ "x$?" != "x0" ] ; then
|
|
ERROR=$?
|
|
rm -f "$tmpfile"
|
|
return $ERROR
|
|
fi
|
|
|
|
return $?
|
|
}
|
|
|
|
mklink() {
|
|
if ! echo "$1" | grep -q ':' 2>/dev/null; then
|
|
p_echo "Invalid format for mklink: wait source:destinstation, got $1"
|
|
return 102
|
|
fi
|
|
|
|
local filename="${1%%:*}"
|
|
local destination="${1#*:}"
|
|
local directory=`dirname "$filename"`
|
|
local destination_directory=`dirname "$destination"`
|
|
|
|
if ! [ -d "$directory" -a -f "$filename" ]; then
|
|
p_echo "Path $filename doesn't exist"
|
|
return 100
|
|
fi
|
|
|
|
if [ ! -d "$destination_directory" ]; then
|
|
p_echo "Destination directory '$destination_directory' not exist"
|
|
return 100
|
|
fi
|
|
|
|
if [ -f "$destination" -a ! -L "$destination" ]; then
|
|
p_echo "Refusing to create symlink '$destination': file with the same name already exists"
|
|
return 101
|
|
fi
|
|
|
|
local fname=`basename "$filename"`
|
|
local lnkname="last_${fname#*_}"
|
|
# Create convenience symlink to last written config
|
|
pushd "$directory"
|
|
if [ ! -f "$lnkname" -o -L "$lnkname" ] ; then
|
|
ln -snfT "$fname" "$lnkname"
|
|
else
|
|
p_echo "Refusing to create symlink '$directory/$lnkname': file with the same name already exists"
|
|
ERROR=101
|
|
fi
|
|
popd
|
|
# Create symlink for argument
|
|
ln -snfT "$filename" "$destination"
|
|
local tmp_err="$?"
|
|
[ "$tmp_err" = "0" ] || ERROR="$tmp_err"
|
|
|
|
return $ERROR
|
|
}
|
|
|
|
mkdirectory() {
|
|
|
|
local path="$1"
|
|
|
|
if [ -d "$path" ] ; then
|
|
return 0
|
|
fi
|
|
|
|
if [ -e "$path" ] ; then
|
|
p_echo "File $path already exists"
|
|
return 100
|
|
fi
|
|
|
|
mkdir "$path"
|
|
if [ "x$?" != "x0" ] ; then
|
|
ERROR=$?
|
|
return $ERROR
|
|
fi
|
|
|
|
chmod "$DIR_PERMS" "$path"
|
|
if [ "x$?" != "x0" ] ; then
|
|
ERROR=$?
|
|
return $ERROR
|
|
fi
|
|
chown "$DIR_OWNERGROUP" "$path"
|
|
if [ "x$?" != "x0" ] ; then
|
|
ERROR=$?
|
|
return $ERROR
|
|
fi
|
|
|
|
return $?
|
|
}
|
|
|
|
check() {
|
|
local id file
|
|
while read data; do
|
|
if [ "x$data" != "x" ]; then
|
|
id=${data%%:*};
|
|
file=${data#*:};
|
|
if [ ! -f $file ]; then
|
|
echo "$id:File '$file' not found";
|
|
fi
|
|
fi
|
|
done
|
|
return 0
|
|
}
|
|
|
|
backup_file() {
|
|
|
|
local filename="$1"
|
|
|
|
[ ! -f "$filename" ] && return 0
|
|
|
|
cp -f "$filename" "$filename.bak"
|
|
chown "$FILE_OWNERGROUP" "$filename.bak" && chmod "$FILE_PERMS" "$filename.bak"
|
|
|
|
return $?
|
|
}
|
|
|
|
restore_file() {
|
|
|
|
local filename="$1"
|
|
|
|
[ ! -f "$filename.bak" ] && return 0
|
|
|
|
cp -f "$filename.bak" "$filename"
|
|
chown "$FILE_OWNERGROUP" "$filename" && chmod "$FILE_PERMS" "$filename"
|
|
|
|
return $?
|
|
}
|
|
#!/usr/bin/env bash
|
|
### Copyright 1999-2025. WebPros International GmbH. All rights reserved.
|
|
|
|
usage() {
|
|
cat << EOH
|
|
|
|
Usage: $0 [options]
|
|
|
|
Helper utility to manage NGINX configuration files
|
|
|
|
OPTIONS:
|
|
-t - Test and fix NGINX configuration if possible.
|
|
-T - Just test NGINX configuration.
|
|
-d dir - Create directory.
|
|
-w file - Overwrite or create specified file with content from stdin.
|
|
-b file - Create backup copy of specified file.
|
|
-r file - Restore backup copy of specified file if present.
|
|
-l file:destination - Switch or create symlink to the specified file
|
|
and switch or create 'last_*' symlink
|
|
-c - Read configuration files list from stdin and check their
|
|
their presence. Each line should be like '<id>:<filepath>'.
|
|
|
|
EOH
|
|
}
|
|
|
|
# --- nginx-specific ---
|
|
|
|
set_params()
|
|
{
|
|
DIR_OWNERGROUP="nginx":"psacln"
|
|
DIR_PERMS=770
|
|
FILE_OWNERGROUP="root":"nginx"
|
|
FILE_PERMS=600
|
|
NGINX_BIN="/usr/sbin/nginx"
|
|
NGINX_INCLUDE_D="/etc/nginx/conf.d"
|
|
NGINX_RC_CONFIG="/etc/default/nginx"
|
|
PRODUCT_ROOT_D="/opt/psa"
|
|
}
|
|
|
|
get_cur_value()
|
|
{
|
|
local msg="$1"
|
|
local param="$2"
|
|
echo "$msg" | sed -ne 's/^.*'$param':[[:space:]]*\([[:digit:]]\+\).*$/\1/p' | tail -n 1
|
|
}
|
|
|
|
update_conf_value()
|
|
{
|
|
local param="$1"
|
|
local value="$2"
|
|
local config="$NGINX_INCLUDE_D/aa500_psa_tweaks.conf"
|
|
|
|
echo "Updating config value: $param = $value"
|
|
if grep -q "^\s*$param" "$config" >/dev/null 2>&1 ; then
|
|
sed -e 's/^\(\s*'$param'\s*\)[^;#]*\(;\s*\(#.*\)\?\)$/\1'$value'\2/g' "$config" > "$config.tmp" &&
|
|
mv -f "$config.tmp" "$config" ||
|
|
rm -f "$config.tmp"
|
|
else
|
|
echo "$param $value;" >> "$config"
|
|
fi
|
|
chown "$FILE_OWNERGROUP" "$config" && chmod "$FILE_PERMS" "$config"
|
|
}
|
|
|
|
get_approx_server_names_num()
|
|
{
|
|
local bootstrap="/etc/nginx/conf.d/zz010_psa_nginx.conf"
|
|
local num add
|
|
|
|
num=0
|
|
for config in `sed -ne 's/^\s*include\s*\([^;]*\);/\1/p' "$bootstrap" 2>/dev/null`; do
|
|
add=`awk '/^[[:space:]]*server_name[[:space:]]*/ { ++count } END { print count }' "$config" 2>/dev/null`
|
|
[ -z "$add" ] || num=$(( num + add ))
|
|
done
|
|
|
|
echo "$num"
|
|
}
|
|
|
|
check_conf()
|
|
{
|
|
local NGINX_ULIMIT=
|
|
|
|
# NGINX also tries to open all log files on configuration test. Set ulimits, if any.
|
|
if [ -f "/lib/systemd/system/nginx.service.d/limit_nofile.conf" ]; then
|
|
NGINX_ULIMIT=`sed -n 's|^[[:space:]]*LimitNOFILE[[:space:]]*=[[:space:]]*\([[:digit:]]\+\).*$|\1|p' "/lib/systemd/system/nginx.service.d/limit_nofile.conf" 2>/dev/null | tail -n1`
|
|
[ -z "${NGINX_ULIMIT}" ] || NGINX_ULIMIT="-n ${NGINX_ULIMIT}"
|
|
elif [ -f "$NGINX_RC_CONFIG" ]; then
|
|
. "$NGINX_RC_CONFIG"
|
|
fi
|
|
|
|
[ -z "$NGINX_ULIMIT" ] || ulimit $NGINX_ULIMIT
|
|
$NGINX_BIN -qt
|
|
}
|
|
|
|
check_and_fix_conf()
|
|
{
|
|
local msg
|
|
local server_names_hash_max_size server_names_hash_bucket_size
|
|
local server_names_num max_server_names_hash_max_size
|
|
# This value is actually variable (most common values being 32 and 64),
|
|
# but it doesn't really matter much for following calculations.
|
|
# Just assume 64 as the most common biggest cache line size.
|
|
local min_bucket_size=64
|
|
|
|
msg=`check_conf 2>&1`; ERROR=$?
|
|
while [ "$ERROR" -ne 0 ] || \
|
|
echo "$msg" | grep -q 'could not build the server_names_hash' 2>/dev/null || \
|
|
echo "$msg" | grep -q 'could not build optimal server_names_hash' 2>/dev/null
|
|
do
|
|
# Tweaking NGINX hash parameters (*_hash_max_size and *_hash_bucket_size) is quite tricky.
|
|
# Internally NGINX attempts to build a minimal hashing such that each bucket size is *_hash_bucket_size
|
|
# (which should fit into as few cache lines as possible, ideally 1), and total number of buckets in hash
|
|
# table is the minimal possible value that does not exceed *_hash_max_size. Therefore when we are asked
|
|
# to increase either of parameters we should first try to increase *_hash_max_size. Increasing it way
|
|
# past amount of items in hashing (e.g. server names) makes hash table too sparse which increases memory
|
|
# consumption. If the error persists this means that there are simply too many collisions - therefore we
|
|
# should then increase *_hash_bucket_size value.
|
|
# If NGINX configuration test takes too long this might also mean that we need to increase
|
|
# *_hash_bucket_size instead of *_hash_max_size. The maximum time should grow as O(<hashing items> ^ 2)
|
|
# assuming that *_hash_max_size is the same order of magnitude as number of items in a hashing. This
|
|
# code doesn't implement such logic as it is too error prone and hard to test. Therefore occasionally
|
|
# user might still need to tweak these parameters manually.
|
|
# Note that there are at least 3 sets of hashes employed by NGINX: variables, types and server_names.
|
|
# As nothing but the latter one is used by Plesk, we handle only server_names parameters here.
|
|
server_names_hash_max_size=` get_cur_value "$msg" server_names_hash_max_size`
|
|
server_names_hash_bucket_size=`get_cur_value "$msg" server_names_hash_bucket_size`
|
|
if [ -z "$server_names_hash_max_size" -a -z "$server_names_hash_bucket_size" ]; then
|
|
# some error not related to server_names_hash_max_size or server_names_hash_bucket_size occurs
|
|
# e.g. syntax error in configuration
|
|
break
|
|
fi
|
|
if [ -n "$server_names_hash_max_size" -a -z "$server_names_num" ]; then
|
|
server_names_num=`get_approx_server_names_num`
|
|
max_server_names_hash_max_size=$(( server_names_num * 6 ))
|
|
fi
|
|
# The order of checks is important!
|
|
if [ -n "$server_names_hash_max_size" ] && [ "$server_names_hash_max_size" -lt "$max_server_names_hash_max_size" ]; then
|
|
# At most 4 (= 6/2 + 1) attempts to increase server_names_hash_max_size will be made
|
|
if [ "$server_names_hash_max_size" -lt "$server_names_num" ]; then
|
|
server_names_hash_max_size=$server_names_num
|
|
else
|
|
server_names_hash_max_size=$(( server_names_hash_max_size + server_names_num * 2 ))
|
|
[ "$server_names_hash_max_size" -gt "$max_server_names_hash_max_size" ] &&
|
|
server_names_hash_max_size="$max_server_names_hash_max_size"
|
|
fi
|
|
update_conf_value "server_names_hash_max_size" "$server_names_hash_max_size"
|
|
elif [ -n "$server_names_hash_bucket_size" ]; then
|
|
# $(( server_names_hash_bucket_size + 1 )) would produce same results due to internal NGINX handling,
|
|
# but let's put a clear and accurate value into config.
|
|
server_names_hash_bucket_size=$(( (server_names_hash_bucket_size + 1 + (min_bucket_size - 1)) & ~(min_bucket_size - 1) ))
|
|
update_conf_value "server_names_hash_bucket_size" "$server_names_hash_bucket_size"
|
|
fi
|
|
|
|
msg=`check_conf 2>&1`; ERROR=$?
|
|
done
|
|
|
|
[ -n "$msg" ] && p_echo "$msg"
|
|
return $ERROR
|
|
}
|
|
|
|
# --- script ---
|
|
|
|
if [ $# -eq 0 ] ; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
|
|
getopts "Ttd:w:l:cb:r:" OPTION
|
|
|
|
set_params
|
|
|
|
ERROR=0
|
|
case $OPTION in
|
|
T)
|
|
check_conf
|
|
ERROR=$?
|
|
;;
|
|
t)
|
|
check_and_fix_conf
|
|
ERROR=$?
|
|
;;
|
|
d)
|
|
mkdirectory "$OPTARG"
|
|
ERROR=$?
|
|
;;
|
|
w)
|
|
write "$OPTARG"
|
|
ERROR=$?
|
|
;;
|
|
l)
|
|
mklink "$OPTARG"
|
|
ERROR=$?
|
|
;;
|
|
c)
|
|
check
|
|
ERROR=$?
|
|
;;
|
|
b)
|
|
backup_file "$OPTARG"
|
|
ERROR=$?
|
|
;;
|
|
r)
|
|
restore_file "$OPTARG"
|
|
ERROR=$?
|
|
;;
|
|
*)
|
|
usage
|
|
ERROR=1
|
|
;;
|
|
esac
|
|
|
|
exit $ERROR
|
|
|