Files
SODEW/sodew-bash-main/libs/modules/openlitespeed.sh
T
2026-08-12 10:59:38 +02:00

527 lines
22 KiB
Bash

# [ Config editor ] ===========================================================
# Normalizes an OpenLiteSpeed configuration file.
# Collapses three or more consecutive blank lines into two.
# [$1] (file): config file path (defaults to $openlitespeedConfigFile).
function openlitespeedConfigNormalize() {
local file="${1:-$openlitespeedConfigFile}"
[[ -n "$file" ]] || { appError "Config file not specified"; return 1; }
perl -0pi -e 's/\n{3,}/\n\n/g' "$file" || {
appError "Failed normalize config file: $file"
return 1
}
}
# Edits an OpenLiteSpeed configuration file via the OLS config editor script.
# $1 (file): config file path.
# $2 (mode): edit mode passed to ols-editor.pl.
# $3+ (...): additional editor arguments.
function openlitespeedConfigEditFile() {
local file="$1"
local mode="$2"
shift 2 || return 1
[[ -n "$file" ]] || { appError "Config file not specified"; return 1; }
[[ -n "$mode" ]] || { appError "Edit mode not specified"; return 1; }
perl "$appPath/libs/modules/assets/ols-editor.pl" "$file" "$mode" "$@" || {
appError "Failed edit config file: $file | mode=$mode"
return 1
}
}
# Edits the main OLS config file.
# $@ (...): edit mode and arguments.
function openlitespeedConfigEdit() {
openlitespeedConfigEditFile "$openlitespeedConfigFile" "$@"
}
# Edits the OLS WebAdmin config file.
# $@ (...): edit mode and arguments.
function openlitespeedAdminConfigEdit() {
openlitespeedConfigEditFile "$openlitespeedAdminPath/admin_config.conf" "$@"
}
# Deletes a value from the main OLS config.
function openlitespeedConfigEditDel() {
openlitespeedConfigEdit del "$@"
}
# Deletes masked values from the main OLS config.
function openlitespeedConfigEditDelMasked() {
openlitespeedConfigEdit del_masked "$@"
}
# Adds a value to the main OLS config.
function openlitespeedConfigEditAdd() {
openlitespeedConfigEdit add "$@"
}
# Sets a value in the main OLS config.
function openlitespeedConfigEditSet() {
openlitespeedConfigEdit set "$@"
}
# Sets a masked value in the main OLS config.
function openlitespeedConfigEditSetMasked() {
openlitespeedConfigEdit set_masked "$@"
}
# Adds a virtual host entry to the main OLS config.
# $1 (domain): virtual host name.
function openlitespeedConfigVHostAdd() {
local domain="$1"
[[ -n "$domain" ]] || { appError "Domain not specified"; return 1; }
openlitespeedConfigEdit vhost_add "$domain"
}
# Deletes a virtual host entry from the main OLS config.
# $1 (domain): virtual host name.
function openlitespeedConfigVHostDel() {
local domain="$1"
[[ -n "$domain" ]] || { appError "Domain not specified"; return 1; }
openlitespeedConfigEdit vhost_del "$domain"
}
# Sets a value in the OLS WebAdmin config.
function openlitespeedAdminConfigEditSet() {
openlitespeedAdminConfigEdit set "$@"
}
# Lists OLS virtual hosts filtered by state.
# [$1] (type): filter type: all, up, or down (defaults to all).
function openlitespeedVhostList() {
local type="${1:-all}"
arrayContains "$type" "all" "up" "down" || { appError "Unknown type: $type"; return 1; }
openlitespeedConfigEdit vhost_list "$type"
}
# Lists OLS virtual hosts with their map entries, filtered by state.
# [$1] (type): filter type: all, up, or down (defaults to all).
function openlitespeedVhostListMap() {
local type="${1:-all}"
arrayContains "$type" "all" "up" "down" || { appError "Unknown type: $type"; return 1; }
openlitespeedConfigEdit vhost_list_map "$type"
}
# Lists configured OLS aliases.
function openlitespeedAliasList() {
openlitespeedConfigEdit alias_list
}
# Lists aliases assigned to a virtual host.
# $1 (domain): site domain name.
function openlitespeedVhostAlias() {
local domain="$1"
[[ -n "$domain" ]] || { appError "Domain not specified"; return 1; }
openlitespeedConfigEdit vhost_alias "$domain"
}
# Executes a command inside the OLS deployment container.
# $@ (...): command and arguments to execute.
function openlitespeedExec() {
k3sRun exec deploy/"$openlitespeedKube" -c "$openlitespeedKube" -- "$@"
}
# Executes a command inside a specific OLS pod.
# $1 (pod): pod name.
# $2+ (...): command and arguments to execute.
function openlitespeedPodExec() {
local pod="$1"
[[ -n "$pod" ]] || { appError "Pod not specified"; return 1; }
shift
k3sRun exec pod/"$pod" -c "$openlitespeedKube" -- "$@"
}
# Rebuilds filesystem layout, ownership, and permissions for a virtual host.
# $1 (vhostPath): virtual host chroot path.
# $2 (vhostDataPath): virtual host data path.
# $3 (ug): system user/group name for the site.
function openlitespeedVhostRebuildPath() {
local vhostPath="$1"
local vhostDataPath="$2"
local ug="$3"
[[ -n "$vhostPath" ]] || { appError "vhostPath not specified"; return 1; }
[[ -n "$vhostDataPath" ]] || { appError "vhostDataPath not specified"; return 1; }
[[ -n "$ug" ]] || { appError "ug not specified"; return 1; }
# Create site directories
mkdir -p -- "$vhostPath"/{www,tmp,session} || { appError "Create vhost directories failed: $vhostPath"; return 1; }
# Chroot directory: owned by root (required for OpenSSH)
chown root:root -- "$vhostPath" || { appError "Change owner of chroot directory failed: $vhostPath"; return 1; }
chmod 755 -- "$vhostPath" || { appError "Change permission of chroot directory failed: $vhostPath"; return 1; }
# Site directories: owned by site user
chown -R -- "$ug:$ug" "$vhostPath/www" "$vhostPath/tmp" "$vhostPath/session" || { appError "Change owner of site directories failed: $vhostPath"; return 1; }
# Permissions: www
find "$vhostPath/www" -type d -exec chmod 2750 -- {} + || { appError "Change permissions of www directories failed"; return 1; }
find "$vhostPath/www" -type f -exec chmod 640 -- {} + || { appError "Change permissions of www files failed"; return 1; }
# Permissions: tmp
find "$vhostPath/tmp" -type d -exec chmod 700 -- {} + || { appError "Change permissions of tmp directories failed"; return 1; }
find "$vhostPath/tmp" -type f -exec chmod 600 -- {} + || { appError "Change permissions of tmp files failed"; return 1; }
# Permissions: session
find "$vhostPath/session" -type d -exec chmod 700 -- {} + || { appError "Change permissions of session directories failed"; return 1; }
find "$vhostPath/session" -type f -exec chmod 600 -- {} + || { appError "Change permissions of session files failed"; return 1; }
# Vhost data directory and bootstrap.php
mkdir -p -- "$vhostDataPath" || { appError "Create vhost data directory failed: $vhostDataPath"; return 1; }
touch -- "$vhostDataPath/bootstrap.php" || { appError "Create bootstrap.php failed: $vhostDataPath/bootstrap.php"; return 1; }
chown -R -- "$ug:$ug" "$vhostDataPath" || { appError "Change owner of vhost data directory failed: $vhostDataPath"; return 1; }
find "$vhostDataPath" -type d -exec chmod 700 -- {} + || { appError "Change permissions of vhost data directories failed"; return 1; }
find "$vhostDataPath" -type f -exec chmod 600 -- {} + || { appError "Change permissions of vhost data files failed"; return 1; }
}
# Rebuilds ACL rules for a virtual host.
# Resets existing ACLs, then grants OLS nobody user read access to www/data and rw to tmp/session.
# $1 (vhostPath): virtual host chroot path.
# $2 (vhostDataPath): virtual host data path.
function openlitespeedVhostRebuildACL() {
local vhostPath="$1"
local vhostDataPath="$2"
[[ -n "$vhostPath" ]] || { appError "vhostPath not specified"; return 1; }
[[ -n "$vhostDataPath" ]] || { appError "vhostDataPath not specified"; return 1; }
# ACL reset: vhostPath
setfacl -R -b -- "$vhostPath" || { appError "Clean ACL rules for vhost path failed: $vhostPath"; return 1; }
find "$vhostPath" -type d -exec setfacl -k -- {} + || { appError "Clean default ACL rules for vhost directories failed: $vhostPath"; return 1; }
# ACL for OLS user nobody: www
# Directories: read/traverse + inheritance | Files: read
find "$vhostPath/www" -type d -exec setfacl -m u:nobody:rx,m:rx,d:u:nobody:rx,d:m:rx -- {} + || { appError "Set ACL for nobody on www directories failed"; return 1; }
find "$vhostPath/www" -type f -exec setfacl -m u:nobody:r,m:r -- {} + || { appError "Set ACL for nobody on www files failed"; return 1; }
# ACL for OLS user nobody: tmp/session
# Directories: rwx + inheritance | Files: rw
find "$vhostPath/tmp" "$vhostPath/session" -type d -exec setfacl -m u:nobody:rwx,m:rwx,d:u:nobody:rwx,d:m:rwx -- {} + || { appError "Set ACL for nobody on tmp/session directories failed"; return 1; }
find "$vhostPath/tmp" "$vhostPath/session" -type f -exec setfacl -m u:nobody:rw,m:rw -- {} + || { appError "Set ACL for nobody on tmp/session files failed"; return 1; }
# ACL reset: vhostDataPath
setfacl -R -b -- "$vhostDataPath" || { appError "Clean ACL rules for vhost data path failed: $vhostDataPath"; return 1; }
find "$vhostDataPath" -type d -exec setfacl -k -- {} + || { appError "Clean default ACL rules for vhost data directories failed: $vhostDataPath"; return 1; }
# ACL for OLS user nobody: vhostDataPath
find "$vhostDataPath" -type d -exec setfacl -m u:nobody:rx,m:rx,d:u:nobody:rx,d:m:rx -- {} + || { appError "Set ACL for nobody on vhost data directories failed"; return 1; }
find "$vhostDataPath" -type f -exec setfacl -m u:nobody:r,m:r -- {} + || { appError "Set ACL for nobody on vhost data files failed"; return 1; }
}
# Sets user disk and inode quota for a virtual host.
# Requires user quota to be already enabled and active on the target filesystem.
# $1 (domain): site domain name.
function openlitespeedVhostRebuildQuota() {
local domain
domain=$(domainPrepare "$1")
domainCheck "$domain" || return 1
local ug
ug=$(domainToUser "$domain")
local quotaMount
quotaMount=$(findmnt -no TARGET --target "$vhostsPath")
[[ -n "$quotaMount" ]] || { appError "Quota mount not found: $vhostsPath"; return 1; }
local quotaBlockLimit quotaInodeLimit
quotaBlockLimit=$(siteConfigGetOrSet "$domain" "quotaBlockLimit" "$openlitespeedQuotaBlockLimit")
quotaInodeLimit=$(siteConfigGetOrSet "$domain" "quotaInodeLimit" "$openlitespeedQuotaInodeLimit")
setquota -u "$ug" "$quotaBlockLimit" "$quotaBlockLimit" "$quotaInodeLimit" "$quotaInodeLimit" "$quotaMount" || {
appError "Set quota failed: ug=$ug mount=$quotaMount"
return 1
}
}
# Checks whether user quota support is ready on the virtual host filesystem.
function openlitespeedQuotaCheck() {
local quotaMount
quotaMount=$(findmnt -no TARGET --target "$vhostsPath")
[[ -n "$quotaMount" ]] || { appError "Quota mount not found: $vhostsPath"; return 1; }
local quotaOptions
quotaOptions=$(findmnt -no OPTIONS --target "$quotaMount")
[[ "$quotaOptions" == *usrquota* || "$quotaOptions" == *uquota* ]] || {
appError "User quota is not enabled: mount=$quotaMount options=$quotaOptions"
return 1
}
quotaon -p "$quotaMount" 2>/dev/null | grep -qi "user quota on" || {
appError "User quota is not active: mount=$quotaMount"
return 1
}
command -v setquota >/dev/null 2>&1 || { appError "setquota command not found"; return 1; }
}
# Prints disk and inode quota hard limits for a user on the virtual host filesystem.
# Output format: <blockLimit> <inodeLimit>
# $1 (user): username or numeric UID.
function openlitespeedVhostQuota() {
local user="$1"
[[ -n "$user" ]] || { appError "User not specified"; return 1; }
local quotaMount
quotaMount=$(findmnt -no TARGET --target "$vhostsPath")
[[ -n "$quotaMount" ]] || { appError "Quota mount not found: $vhostsPath"; return 1; }
local quotaLimits error
run quotaLimits error quota -u "$user" --filesystem "$quotaMount" || { appError "$error"; return 1; }
quotaLimits=$(awk 'NR>2 && $1 != "" { print $4, $7; exit }' <<< "$quotaLimits")
[[ -n "$quotaLimits" ]] || { appError "Parse quota limits failed: user=$user mount=$quotaMount"; return 1; }
printf '%s\n' "$quotaLimits"
}
# Configures XFS project quota for a virtual host.
# $1 (vhostPath): virtual host path to assign to an XFS project.
# $2 (projectId): numeric XFS project ID.
# $3 (projectName): XFS project name.
function openlitespeedVhostRebuildXFS() {
local vhostPath="$1"
local projectId="$2"
local projectName="$3"
[[ -n "$vhostPath" ]] || { appError "vhostPath not specified"; return 1; }
[[ -n "$projectId" ]] || { appError "projectId not specified"; return 1; }
[[ -n "$projectName" ]] || { appError "projectName not specified"; return 1; }
local quotaFs
quotaFs=$(findmnt -no FSTYPE --target "$vhostPath")
[[ "$quotaFs" == "xfs" ]] || {
appError "XFS quota filesystem mismatch: vhostPath=$vhostPath fs=$quotaFs expected=xfs"
return 1
}
local quotaMount
quotaMount=$(findmnt -no TARGET --target "$vhostPath")
[[ -n "$quotaMount" ]] || { appError "Detect XFS quota mount failed: $vhostPath"; return 1; }
[[ "$quotaMount" == '/' || "$vhostPath" == "$quotaMount"/* ]] || {
appError "XFS quota mount mismatch: vhostPath=$vhostPath quotaMount=$quotaMount expected=$vhostsPath"
return 1
}
local quotaOptions
quotaOptions=$(findmnt -no OPTIONS --target "$vhostPath")
[[ "$quotaOptions" != *noquota* && ( "$quotaOptions" == *prjquota* || "$quotaOptions" == *pquota* ) ]] || {
appError "XFS project quota is not enabled: vhostPath=$vhostPath mount=$quotaMount options=$quotaOptions"
return 1
}
touch /etc/projects /etc/projid || { appError "Create XFS quota registry files failed"; return 1; }
# /etc/projects format: projectId:path
sed -i "\#:$vhostPath\$#d" /etc/projects
sed -i "\#^$projectId:#d" /etc/projects
printf '%s:%s\n' "$projectId" "$vhostPath" >> /etc/projects
# /etc/projid format: projectName:projectId
sed -i "\#^$projectName:#d" /etc/projid
sed -i "\#:$projectId\$#d" /etc/projid
printf '%s:%s\n' "$projectName" "$projectId" >> /etc/projid
xfs_quota -x -c "project -s $projectName" "$quotaMount" || {
appError "Set XFS project quota project failed: $projectName $vhostPath"
return 1
}
xfs_quota -x -c "limit -p bhard=$openlitespeedVhostBlockHard ihard=$openlitespeedVhostInodeHard $projectName" "$quotaMount" || {
appError "Set XFS project quota limits failed: $projectName"
return 1
}
}
# Rebuilds a virtual host: user/group, filesystem layout, permissions, and ACLs.
# $1 (domain): site domain name.
function openlitespeedVhostRebuild() {
local domain
domain=$(domainPrepare "$1")
domainCheck "$domain" || return 1
local ug vhostPath vhostDataPath
ug=$(domainToUser "$domain")
vhostPath="$vhostsPath/$domain"
vhostDataPath="$openlitespeedVhostDataPath/$domain"
# User and group
if ! getent group "$ug" >/dev/null 2>&1; then
groupadd -- "$ug" || { appError "Create group failed: $ug"; return 1; }
fi
if ! id "$ug" >/dev/null 2>&1; then
useradd -M -g "$ug" -d "$vhostPath" -s /usr/sbin/nologin -- "$ug" >/dev/null 2>&1 || { appError "Create user failed: $ug"; return 1; }
else
usermod -g "$ug" -d "$vhostPath" -s /usr/sbin/nologin -- "$ug" >/dev/null 2>&1 || { appError "Update user failed: $ug"; return 1; }
fi
openlitespeedVhostRebuildPath "$vhostPath" "$vhostDataPath" "$ug" || return 1
openlitespeedVhostRebuildACL "$vhostPath" "$vhostDataPath" || return 1
# Quota
# openlitespeedVhostRebuildQuota "$domain" || return 1
# XFS [ NO USE ! | Only for XFS + prjquota ]
# local uId
# uId=$(id -u "$ug" 2>/dev/null)
# [[ -n "$uId" ]] || { appError "Get user id failed: $ug"; return 1; }
# openlitespeedVhostRebuildXFS "$vhostPath" "$uId" "$domain" || return 1
}
# Creates or deletes OLS config entries for a virtual host.
# On create, generates the vhost config file from template if it does not exist.
# $1 (domain): site domain name.
# [$2] (option): action: create or delete (defaults to create).
function openlitespeedConfigRebuild() {
local domain
domain=$(domainPrepare "$1")
domainCheck "$domain" || return 1
local option="${2:-create}"
case "$option" in
create|delete) ;;
*)
appError "Unknown option: $option"
return 1
;;
esac
fileBackup "$openlitespeedConfigFile" || return 1
openlitespeedConfigVHostDel "$domain" || return 1
local vHostFile="$openlitespeedVhostsPath/$domain.conf"
if [[ "$option" == "create" ]]; then
openlitespeedConfigEditAdd 'listener\h+HTTP' map "$domain $domain" || return 1
openlitespeedConfigVHostAdd "$domain" || return 1
if [[ ! -f "$vHostFile" ]]; then
local profileFile memory_limit max_execution_time post_max_size upload_max_filesize
profileFile="$appAssetsPath/openlitespeed/profiles/memory-$kubeMemoryProfile.config"
[[ -f "$profileFile" ]] || { appError "Profile not found: $profileFile"; return 1; }
memory_limit=$(configGet "$profileFile" "memory_limit")
max_execution_time=$(configGet "$profileFile" "max_execution_time")
post_max_size=$(configGet "$profileFile" "post_max_size")
upload_max_filesize=$(configGet "$profileFile" "upload_max_filesize")
cp -f -- "$appAssetsPath/openlitespeed/vhost.conf" "$vHostFile" || { appError "Copy vhost template failed"; return 1; }
sed -i \
-e "s|{{domain}}|$domain|g" \
-e "s|{{memory_limit}}|$memory_limit|g" \
-e "s|{{max_execution_time}}|$max_execution_time|g" \
-e "s|{{post_max_size}}|$post_max_size|g" \
-e "s|{{upload_max_filesize}}|$upload_max_filesize|g" \
-- "$vHostFile" || { appError "Template substitution failed: $vHostFile"; return 1; }
fi
else
openlitespeedConfigEditDelMasked 'listener\h+HTTP' map "$domain *" || return 1
rm -f -- "$vHostFile"
rm -f -- "$openlitespeedVhostsPath/$domain.conf0"
rm -f -- "$openlitespeedVhostsPath/$domain.txt"
openlitespeedConfigEdit vhost_up "$domain"
fi
openlitespeedConfigNormalize "$openlitespeedConfigFile" || return 1
}
# Sets the domain aliases for a virtual host, updating both site config and OLS listener map.
# $1 (domain): primary site domain name.
# $@ (...): alias domain names to assign.
function openlitespeedAliasSet() {
local domain
domain=$(domainPrepare "$1")
domainCheck "$domain" || return 1
shift
local -a aliasesRaw=("$@")
local -a aliases=()
run vhostList error openlitespeedVhostList || { appError "Failed get list of virtual hosts: $error"; return 1; }
listContains "$domain" "$vhostList" || { appError "Domain is not exists in OpenLiteSpeed config"; return 1; }
for aliasRaw in "${aliasesRaw[@]}"; do
alias="$(domainPrepare "$aliasRaw")"
[[ -n "$alias" ]] || continue
[[ "$alias" == "$domain" ]] && continue
arrayContains "$alias" "${aliases[@]}" || aliases+=("$alias")
done
for alias in "${aliases[@]}"; do
runError error domainCheck "$alias" || { appError "$alias: $error"; return 1; }
listContains "$alias" "$vhostList" && { appError "$alias: Is already exists as virtual host"; return 1; }
done
local aliasValue=''
[[ ${#aliases[@]} -gt 0 ]] && aliasValue="$(IFS=','; printf '%s\n' "${aliases[*]}")"
local domainConfigFile="$appDataPath/config/$domain.config"
configSet "$domainConfigFile" alias "$aliasValue" || { appError "Failed set alias in $domainConfigFile"; return 1; }
fileBackup "$openlitespeedConfigFile" || return 1
openlitespeedConfigEditDelMasked 'listener\h+HTTP' map "$domain *" || return 1
openlitespeedConfigEditAdd 'listener\h+HTTP' map "$domain $domain" || return 1
for alias in "${aliases[@]}"; do
openlitespeedConfigEditAdd 'listener\h+HTTP' map "$domain $alias" || return 1
done
printf '%s' "$aliasValue"
return 0
}
# Marks a virtual host as suspended.
# $1 (domain): site domain name.
function openlitespeedVHostDown() {
local domain
domain=$(domainPrepare "$1")
domainCheck "$domain" || return 1
local vhostList error
run vhostList error openlitespeedVhostList || return 1
runSilent fileCheckLineLength "$openlitespeedConfigFile" 8000 || { appError "fileCheckLineLength 8000"; return 1; }
if listContains "$domain" "$vhostList"; then
openlitespeedConfigEdit vhost_down "$domain" || return 1
fi
}
# Marks a virtual host as active.
# $1 (domain): site domain name.
function openlitespeedVHostUp() {
local domain
domain=$(domainPrepare "$1")
domainCheck "$domain" || return 1
local vhostList error
run vhostList error openlitespeedVhostList || return 1
if listContains "$domain" "$vhostList"; then
openlitespeedConfigEdit vhost_up "$domain" || return 1
fi
}
# Updates the Traefik middleware IP whitelist for the OLS admin panel from $openlitespeedAdminWhiteList.
function openlitespeedAdminWhiteList() {
local ipList=() whiteList error
for i in "${!openlitespeedAdminWhiteList[@]}"; do
ipList[$i]="\"${openlitespeedAdminWhiteList[$i]}\""
done
whiteList=$(IFS=','; printf '%s' "${ipList[*]}")
runError error k3sRun patch middleware "$openlitespeedKube-admin-allowlist" --type=merge -p "{\"spec\":{\"ipWhiteList\":{\"sourceRange\":[${whiteList}]}}}" \
|| { appError "$error"; return 1; }
printf '%s' "$whiteList"
}
# Restricts OLS admin access to Traefik pod IPs only by updating the admin_config.conf ACL.
function openlitespeedAdminAllowList() {
local podList
podList=$("$k3sCmd" kubectl -n kube-system get pod -l app.kubernetes.io/name=traefik -o jsonpath='{range .items[*]}{.status.podIP}{"\n"}{end}' | awk 'NF')
local -a ipList
mapfile -t ipList < <(printf '%s\n' "$podList")
[[ "${#ipList[@]}" -gt 0 ]] || { appError "Traefik pod IPs not found"; return 1; }
local allowList
allowList=$(IFS=','; printf '%s' "${ipList[*]}")
fileBackup "$openlitespeedAdminPath/admin_config.conf" || return 1
openlitespeedAdminConfigEditSet 'accessControl' deny 'ALL' || return 1
openlitespeedAdminConfigEditSet 'accessControl' allow "$allowList" || return 1
printf '%s' "$allowList"
}