362 lines
10 KiB
Bash
362 lines
10 KiB
Bash
# Remove a single trailing slash from a string.
|
|
# $1 (s): input string.
|
|
function strTrimSlash() {
|
|
local s="${1-}"
|
|
s="${s%/}"
|
|
printf '%s' "$s"
|
|
}
|
|
|
|
# Generates a random password with a specified length and character set.
|
|
#
|
|
# $1 (length): length of the password (defaults to 32 characters).
|
|
# $2 (charset): string of characters to use for generating the password (defaults to "A-Za-z0-9@#$%^*_+=[]{}:").
|
|
function strRandom() {
|
|
local length="${1:-32}"
|
|
local charset="${2:-"A-Za-z0-9_.-"}"
|
|
LC_ALL=C tr -dc "$charset" < /dev/urandom | head -c "$length"
|
|
}
|
|
|
|
# Format a number with thousands separators (space-separated groups).
|
|
# $1 (number): the number to format.
|
|
function numFormat() {
|
|
printf '%s\n' "${1-}" | sed ':a;s/\B[0-9]\{3\}\>/ &/;ta'
|
|
}
|
|
|
|
# Check if a value exists in an array passed as remaining arguments.
|
|
# $1 (needle): value to search for.
|
|
# $2 (items): array elements to search in (passed as individual arguments).
|
|
function arrayContains() {
|
|
local needle="${1-}"
|
|
shift || true
|
|
|
|
local item
|
|
for item in "$@"; do
|
|
[[ "$item" == "$needle" ]] && return 0
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
# Return success if $value exists as full line in multiline $list
|
|
function listContains() {
|
|
local value="${1-}"
|
|
local list="${2-}"
|
|
|
|
[[ -n "$value" ]] || return 1
|
|
|
|
grep -Fxq -- "$value" <<<"$list"
|
|
}
|
|
|
|
# Calculate the difference in seconds between two datetime strings.
|
|
# $1 (from): start datetime string (accepted by date -d).
|
|
# $2 (to): end datetime string (accepted by date -d).
|
|
function timeDiff() {
|
|
local from="${1-}"
|
|
local to="${2-}"
|
|
local t1 t2
|
|
|
|
[[ -n "$from" ]] || { appError "time_from not specified"; return 1; }
|
|
[[ -n "$to" ]] || { appError "time_to not specified"; return 1; }
|
|
|
|
t1="$(date -d "$from" +%s)" || { appError "invalid time_from: $from"; return 1; }
|
|
t2="$(date -d "$to" +%s)" || { appError "invalid time_to: $to"; return 1; }
|
|
|
|
printf '%s\n' "$(( t2 - t1 ))"
|
|
}
|
|
|
|
# Normalize a domain name: lowercase, strip whitespace and trailing dot, then IDN-encode.
|
|
# $1 (domain): raw domain string to normalize.
|
|
function domainPrepare() {
|
|
local domain="${1-}"
|
|
|
|
domain="${domain,,}"
|
|
domain="${domain//[[:space:]]/}"
|
|
domain="${domain%.}"
|
|
|
|
LC_ALL=C.UTF-8 idn2 <<<"$domain"
|
|
}
|
|
|
|
# Checks if the given string is a valid domain.
|
|
#
|
|
# $1 (domain): a string representing the domain.
|
|
function domainValidate() {
|
|
local domain="${1-}"
|
|
|
|
(( ${#domain} >= 4 )) || { appError "Domain name is too short: $domain"; return 1; }
|
|
(( ${#domain} <= 253 )) || { appError "Domain name is too long: $domain"; return 1; }
|
|
[[ "$domain" =~ ^([A-Za-z0-9]([-A-Za-z0-9]{0,61}[A-Za-z0-9])?\.)+([A-Za-z]{2,63}|xn--[A-Za-z0-9-]{2,59})$ ]] || { appError "Invalid domain name format: $domain"; return 1; }
|
|
}
|
|
|
|
# Validate a domain name and reject reserved names.
|
|
# $1 (domain): domain name to check.
|
|
function domainCheck() {
|
|
local domain="${1-}"
|
|
local reserved=("root" "user")
|
|
|
|
arrayContains "$domain" "${reserved[@]}" && { appError "Reserved domain name: $domain"; return 1; }
|
|
domainValidate "$domain"
|
|
}
|
|
|
|
# Generate username and groupname from domain
|
|
function domainToUser() {
|
|
local domain="${1-}"
|
|
local base hash
|
|
|
|
base="$(printf '%s' "$domain" \
|
|
| tr '[:upper:]' '[:lower:]' \
|
|
| sed -E 's/[^a-z0-9-]+/-/g; s/-{2,}/-/g; s/^-//; s/-$//')"
|
|
|
|
hash="$(printf '%s' "$domain$hostSalt" \
|
|
| sha256sum \
|
|
| awk '{print substr($1,1,8)}')"
|
|
|
|
printf '%s' "${base:0:21}-${hash}"
|
|
}
|
|
|
|
function domainToUid() {
|
|
local domain="${1-}"
|
|
local username uid
|
|
|
|
if [[ -z "$domain" ]]; then
|
|
appError "Domain not specified"
|
|
return 1
|
|
fi
|
|
|
|
username=$(domainToUser "$domain")
|
|
if [[ -z "$username" ]]; then
|
|
appError "Cannot compute username for: $domain"
|
|
return 1
|
|
fi
|
|
|
|
uid=$(id -u "$username" 2>/dev/null)
|
|
if [[ -z "$uid" ]]; then
|
|
appError "No such user: $username"
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$uid" == "0" ]]; then
|
|
appError "Refusing to return root UID for: $domain"
|
|
return 1
|
|
fi
|
|
|
|
printf '%s' "$uid"
|
|
}
|
|
|
|
function domainToGid() {
|
|
local domain="${1-}"
|
|
local username gid
|
|
|
|
if [[ -z "$domain" ]]; then
|
|
appError "Domain not specified"
|
|
return 1
|
|
fi
|
|
|
|
username=$(domainToUser "$domain")
|
|
if [[ -z "$username" ]]; then
|
|
appError "Cannot compute username for: $domain"
|
|
return 1
|
|
fi
|
|
|
|
gid=$(id -g "$username" 2>/dev/null)
|
|
if [[ -z "$gid" ]]; then
|
|
appError "No such user: $username"
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$gid" == "0" ]]; then
|
|
appError "Refusing to return root GID for: $domain"
|
|
return 1
|
|
fi
|
|
|
|
printf '%s' "$gid"
|
|
}
|
|
|
|
# Generate random string from domain
|
|
function domainToRandom() {
|
|
local domain="${1-}"
|
|
local maxLen="${2:-256}"
|
|
local tailLen="${3:-8}"
|
|
local base tail baseLen
|
|
|
|
base="$(printf '%s' "$domain" \
|
|
| tr '[:upper:]' '[:lower:]' \
|
|
| sed -E 's/[^a-z0-9]+/_/g; s/_{2,}/_/g; s/^_//; s/_$//')"
|
|
|
|
tail="$(strRandom "$tailLen" 'a-z0-9')" || return 1
|
|
|
|
baseLen=$(( maxLen - tailLen - 1 ))
|
|
(( baseLen < 1 )) && baseLen=1
|
|
|
|
printf '%s' "${base:0:baseLen}_${tail}"
|
|
}
|
|
|
|
# Run a command and capture its stdout and stderr into named variables.
|
|
# $1 (outVar): name of the variable to receive stdout.
|
|
# $2 (errVar): name of the variable to receive stderr.
|
|
# $3 (cmd): command and arguments to execute.
|
|
function run() {
|
|
local -n __runOut="$1"
|
|
local -n __runError="$2"
|
|
shift 2
|
|
|
|
local stdoutTmp stderrTmp status
|
|
stdoutTmp=$(mktemp) || return 1
|
|
stderrTmp=$(mktemp) || { rm -f "$stdoutTmp"; return 1; }
|
|
|
|
"$@" >"$stdoutTmp" 2>"$stderrTmp"
|
|
status=$?
|
|
|
|
__runOut=$(<"$stdoutTmp")
|
|
__runError=$(<"$stderrTmp")
|
|
|
|
rm -f "$stdoutTmp" "$stderrTmp"
|
|
return "$status"
|
|
}
|
|
|
|
# Run command, discard stdout
|
|
function runError() {
|
|
local -n __runErrorErr="$1"
|
|
shift || true
|
|
|
|
local __runErrorOutput __runErrorError status
|
|
run __runErrorOutput __runErrorError "$@"
|
|
status=$?
|
|
|
|
if [[ -n "$__runErrorError" ]]; then
|
|
__runErrorErr="$__runErrorError"
|
|
else
|
|
__runErrorErr="$__runErrorOutput"
|
|
fi
|
|
|
|
return "$status"
|
|
}
|
|
|
|
# Run a command, discarding output, and print an error message on failure.
|
|
# $1 (cmd): command and arguments to execute.
|
|
function runFail() {
|
|
local __runFailErr
|
|
runError __runFailErr "$@"
|
|
local status=$?
|
|
[[ $status -eq 0 ]] || appError "$__runFailErr"
|
|
return $status
|
|
}
|
|
|
|
# Run a command and discard all stdout and stderr output.
|
|
# $1 (cmd): command and arguments to execute.
|
|
function runSilent() {
|
|
local output error
|
|
run output error "$@"
|
|
return $?
|
|
}
|
|
|
|
# Run a shell command string (with operator support) and capture stdout and stderr into named variables.
|
|
# $1 (outVar): name of the variable to receive stdout.
|
|
# $2 (errVar): name of the variable to receive stderr.
|
|
# $3 (cmd): command and arguments, including shell operators (|, &&, ;, etc.).
|
|
function runShell() {
|
|
local -n __out="$1"
|
|
local -n __err="$2"
|
|
shift 2
|
|
|
|
local stdoutTmp stderrTmp status
|
|
stdoutTmp=$(mktemp) || return 1
|
|
stderrTmp=$(mktemp) || { rm -f "$stdoutTmp"; return 1; }
|
|
|
|
# Arguments: > >> < | || & && ; ( ) convert to operators.
|
|
local cmd="" arg
|
|
for arg in "$@"; do
|
|
if [[ "$arg" =~ ^([0-9]?>|[0-9]?>>|[0-9]?<|\||\|\||&&|;|\(|\))$ ]]; then
|
|
cmd+=" $arg"
|
|
else
|
|
cmd+=" $(printf '%q' "$arg")"
|
|
fi
|
|
done
|
|
|
|
(
|
|
set -o pipefail
|
|
eval -- "$cmd"
|
|
) >"$stdoutTmp" 2>"$stderrTmp"
|
|
status=$?
|
|
|
|
__out=$(<"$stdoutTmp")
|
|
__err=$(<"$stderrTmp")
|
|
|
|
rm -f "$stdoutTmp" "$stderrTmp"
|
|
return "$status"
|
|
}
|
|
|
|
# Converts notation (28Gi, 512Mi, 1Ki, 4Gb, ...) to bytes; returns -1 for empty input.
|
|
function sizeToBytes() {
|
|
local v="$1"
|
|
case "$v" in
|
|
*Ti) echo $(( ${v%Ti} * 1099511627776 )) ;;
|
|
*Gi) echo $(( ${v%Gi} * 1073741824 )) ;;
|
|
*Mi) echo $(( ${v%Mi} * 1048576 )) ;;
|
|
*Ki) echo $(( ${v%Ki} * 1024 )) ;;
|
|
*Tb) echo $(( ${v%Tb} * 1000000000000 )) ;;
|
|
*Gb) echo $(( ${v%Gb} * 1000000000 )) ;;
|
|
*Mb) echo $(( ${v%Mb} * 1000000 )) ;;
|
|
*Kb) echo $(( ${v%Kb} * 1000 )) ;;
|
|
"") echo -1 ;;
|
|
*) echo "$v" ;;
|
|
esac
|
|
}
|
|
|
|
#=========================================================================
|
|
|
|
# Comment out the first non-empty, non-commented line in the domains list file.
|
|
# Prints the domain name on success, returns 1 if no domain available.
|
|
function domainsListMark() {
|
|
local file="$1"
|
|
local line domain num=0 lineNum=0
|
|
|
|
[[ -f "$file" ]] || { appError "Domains list file not found: $file"; return 1; }
|
|
while IFS= read -r line; do
|
|
(( num++ ))
|
|
[[ -z "$line" || "$line" == \#* ]] && continue
|
|
domain="$line"
|
|
lineNum=$num
|
|
break
|
|
done < "$file"
|
|
|
|
[[ $lineNum -eq 0 ]] && return 1
|
|
|
|
sed -i "${lineNum}s/^/#/" "$file"
|
|
printf '%s\n' "$domain"
|
|
}
|
|
|
|
# Remove the '#' prefix from the specified domain line in the domains list file.
|
|
function domainsListUnmark() {
|
|
local file="$1"
|
|
local domain="$2"
|
|
local line num=0 lineNum=0
|
|
|
|
[[ -f "$file" ]] || { appError "Domains list file not found: $file"; return 1; }
|
|
[[ -n "$domain" ]] || { appError "Domain not specified"; return 1; }
|
|
while IFS= read -r line; do
|
|
(( num++ ))
|
|
[[ "$line" == "#${domain}" ]] && { lineNum=$num; break; }
|
|
done < "$file"
|
|
|
|
[[ $lineNum -eq 0 ]] && { appError "Domain not marked in list: $domain"; return 1; }
|
|
|
|
sed -i "${lineNum}s/^#//" "$file"
|
|
}
|
|
|
|
|
|
# Sends an email with the specified subject and body.
|
|
#
|
|
# $1 (mailSubject): string containing the subject of the email.
|
|
# $2 (mailBody): string containing the body of the email.
|
|
function emailSend() {
|
|
local mailSubject="$1"
|
|
local mailBody="$2"
|
|
|
|
[[ -n "$mailSubject" ]] || { appError "Subject not specified"; return 1; }
|
|
[[ -n "$mailBody" ]] || { appError "Body email not specified"; return 1; }
|
|
|
|
local result
|
|
result=$(printf 'Subject:%s\nFrom:%s\nTo:%s\n\n%s' "$mailSubject" "$mailFrom" "$mailTo" "$mailBody" | msmtp "$mailTo" 2>&1)
|
|
[[ $? -eq 0 ]] || { appError "$result"; return 1; }
|
|
}
|