Browse Source

Merge pull request #4305 from oulfr/main

Limit CPU and RAM for Each User Using cgroup
Jaap Marcus 2 years ago
parent
commit
d0567b5117

+ 49 - 0
bin/v-add-sys-cgroups

@@ -0,0 +1,49 @@
+#!/bin/bash
+# info: Enable cgroup support for user
+# options: NONE
+#
+# example: v-add-sys-cgroup
+
+#----------------------------------------------------------#
+#                Variables & Functions                     #
+#----------------------------------------------------------#
+
+# Includes
+# shellcheck source=/etc/hestiacp/hestia.conf
+source /etc/hestiacp/hestia.conf
+# shellcheck source=/usr/local/hestia/func/main.sh
+source $HESTIA/func/main.sh
+# load config file
+source_conf "$HESTIA/conf/hestia.conf"
+
+#----------------------------------------------------------#
+#                    Verifications                         #
+#----------------------------------------------------------#
+
+# Perform verification if read-only mode is enabled
+check_hestia_demo_mode
+
+#----------------------------------------------------------#
+#                       Action                             #
+#----------------------------------------------------------#
+
+# Updating hestia.conf value
+$BIN/v-change-sys-config-value "RESOURCES_LIMIT" "yes"
+
+# enable cgroup for all users
+for user in $("$BIN/v-list-users" list); do
+	$BIN/v-update-user-cgroup "$user"
+done
+
+# Reload daemon
+systemctl daemon-reload
+
+#----------------------------------------------------------#
+#                       Hestia                             #
+#----------------------------------------------------------#
+
+# Logging
+$BIN/v-log-action "system" "Info" "Plugins" "System cgroup Enforcement Enabled."
+log_event "$OK" "$ARGUMENTS"
+
+exit

+ 5 - 0
bin/v-add-user

@@ -266,6 +266,11 @@ if [ "$DISK_QUOTA" = 'yes' ]; then
 	$BIN/v-update-user-quota "$user"
 fi
 
+# Update resource limitation (cgroup)
+if [ "$RESOURCES_LIMIT" = 'yes' ]; then
+	$BIN/v-update-user-cgroup "$user"
+fi
+
 # Updating admin counter
 if [ "$user" != "$ROOT_USER" ]; then
 	increase_user_value "$ROOT_USER" '$U_USERS'

+ 16 - 0
bin/v-add-user-package

@@ -55,6 +55,18 @@ is_package_consistent() {
 	if [ "$DISK_QUOTA" != 'unlimited' ]; then
 		is_int_format_valid "$DISK_QUOTA" 'DISK_QUOTA'
 	fi
+	if [ "$CPU_QUOTA" != 'unlimited' ]; then
+		is_valid_cpu_quota "$CPU_QUOTA" 'CPU_QUOTA'
+	fi
+	if [ "$CPU_QUOTA_PERIOD" != 'unlimited' ]; then
+		is_valid_cpu_quota_period "$CPU_QUOTA_PERIOD" 'CPU_QUOTA_PERIOD'
+	fi
+	if [ "$MEMORY_LIMIT" != 'unlimited' ]; then
+		is_valid_memory_size "$MEMORY_LIMIT" 'MEMORY_LIMIT'
+	fi
+	if [ "$SWAP_LIMIT" != 'unlimited' ]; then
+		is_valid_swap_size "$SWAP_LIMIT" 'SWAP_LIMIT'
+	fi
 	if [ "$BANDWIDTH" != 'unlimited' ]; then
 		is_int_format_valid "$BANDWIDTH" 'BANDWIDTH'
 	fi
@@ -130,6 +142,10 @@ RATE_LIMIT='$RATE_LIMIT'
 DATABASES='$DATABASES'
 CRON_JOBS='$CRON_JOBS'
 DISK_QUOTA='$DISK_QUOTA'
+CPU_QUOTA='$CPU_QUOTA'
+CPU_QUOTA_PERIOD='$CPU_QUOTA_PERIOD'
+MEMORY_LIMIT='$MEMORY_LIMIT'
+SWAP_LIMIT='$SWAP_LIMIT'
 BANDWIDTH='$BANDWIDTH'
 NS='$NS'
 SHELL='$SHELL'

+ 8 - 0
bin/v-change-user-package

@@ -96,6 +96,10 @@ RATE_LIMIT='$RATE_LIMIT'
 DATABASES='$DATABASES'
 CRON_JOBS='$CRON_JOBS'
 DISK_QUOTA='$DISK_QUOTA'
+CPU_QUOTA='$CPU_QUOTA'
+CPU_QUOTA_PERIOD='$CPU_QUOTA_PERIOD'
+MEMORY_LIMIT='$MEMORY_LIMIT'
+SWAP_LIMIT='$SWAP_LIMIT'
 BANDWIDTH='$BANDWIDTH'
 NS='$NS'
 SHELL='$SHELL'
@@ -185,6 +189,10 @@ if [ "$DISK_QUOTA" = 'yes' ]; then
 	$BIN/v-update-user-quota "$user"
 fi
 
+# Update cgroup
+if [ "$RESOURCES_LIMIT" = 'yes' ]; then
+	$BIN/v-update-user-cgroup "$user"
+fi
 #----------------------------------------------------------#
 #                       Hestia                             #
 #----------------------------------------------------------#

+ 53 - 0
bin/v-delete-sys-cgroup

@@ -0,0 +1,53 @@
+#!/bin/bash
+# info: delete all cgroup
+# options: NONE
+#
+# example: v-delete-sys-cgroup
+#
+# This function disables cgroup
+
+#----------------------------------------------------------#
+#                Variables & Functions                     #
+#----------------------------------------------------------#
+
+# Includes
+# shellcheck source=/etc/hestiacp/hestia.conf
+source /etc/hestiacp/hestia.conf
+# shellcheck source=/usr/local/hestia/func/main.sh
+source $HESTIA/func/main.sh
+# load config file
+source_conf "$HESTIA/conf/hestia.conf"
+
+#----------------------------------------------------------#
+#                    Verifications                         #
+#----------------------------------------------------------#
+
+# Perform verification if read-only mode is enabled
+check_hestia_demo_mode
+
+#----------------------------------------------------------#
+#                       Action                             #
+#----------------------------------------------------------#
+
+# Revert cgroup for all users
+for user in $("$BIN/v-list-users" list); do
+	user_id=$(id -u "$user")
+	user_slice="user-${user_id}.slice"
+	systemctl revert "$user_slice"
+done
+
+# Reload daemon
+systemctl daemon-reload
+
+# Updating hestia.conf value
+$BIN/v-change-sys-config-value "RESOURCES_LIMIT" "no"
+
+#----------------------------------------------------------#
+#                       Hestia                             #
+#----------------------------------------------------------#
+
+# Logging
+$BIN/v-log-action "system" "Info" "Plugins" "System cgroup Enforcement disabled."
+log_event "$OK" "$ARGUMENTS"
+
+exit

+ 8 - 4
bin/v-list-sys-config

@@ -46,6 +46,7 @@ json_list() {
 			"DEMO_MODE": "'$DEMO_MODE'",
 			"DISABLE_IP_CHECK": "'$DISABLE_IP_CHECK'",
 			"DISK_QUOTA": "'$DISK_QUOTA'",
+			"RESOURCES_LIMIT": "'$RESOURCES_LIMIT'",
 			"DNS_CLUSTER": "'$DNS_CLUSTER'",
 			"DNS_CLUSTER_SYSTEM": "'$DNS_CLUSTER_SYSTEM'",
 			"DNS_SYSTEM": "'$DNS_SYSTEM'",
@@ -86,7 +87,7 @@ json_list() {
 			"PROXY_SYSTEM": "'$PROXY_SYSTEM'",
 			"RELEASE_BRANCH": "'$RELEASE_BRANCH'",
 			"REPOSITORY": "'$REPOSITORY'",
-      "ROOT_USER": "'$ROOT_USER'",
+      		"ROOT_USER": "'$ROOT_USER'",
 			"SERVER_SMTP_ADDR": "'$SERVER_SMTP_ADDR'",
 			"SERVER_SMTP_HOST": "'$SERVER_SMTP_HOST'",
 			"SERVER_SMTP_PASSWD": "'$SERVER_SMTP_PASSWD'",
@@ -189,6 +190,9 @@ shell_list() {
 	if [ -n "$DISK_QUOTA" ]; then
 		echo "Disk Quota enabled:               $DISK_QUOTA"
 	fi
+	if [ -n "$RESOURCES_LIMIT" ]; then
+		echo "Resource limit with cgroup enabled:               $RESOURCES_LIMIT"
+	fi
 	if [ -n "$LANGUAGE" ] && [ "$LANGUAGE" != 'en' ]; then
 		echo "System Language:                  $LANGUAGE"
 	fi
@@ -239,7 +243,7 @@ plain_list() {
 	echo -ne "$PROXY_SSL_PORT\t$FTP_SYSTEM\t$MAIL_SYSTEM\t$IMAP_SYSTEM\t"
 	echo -ne "$ANTIVIRUS_SYSTEM\t$ANTISPAM_SYSTEM\t$DB_SYSTEM\t"
 	echo -ne "$DNS_SYSTEM\t$DNS_CLUSTER\t$STATS_SYSTEM\t$BACKUP_SYSTEM\t"
-	echo -ne "$CRON_SYSTEM\t$DISK_QUOTA\t$FIREWALL_SYSTEM\t$FIREWALL_EXTENSION\t"
+	echo -ne "$CRON_SYSTEM\t$DISK_QUOTA\t$RESOURCES_LIMIT\t$FIREWALL_SYSTEM\t$FIREWALL_EXTENSION\t"
 	echo -ne "$FILE_MANAGER\t$REPOSITORY\t$VERSION\t$DEMO_MODE\t$RELEASE_BRANCH\t"
 	echo -ne "$SMTP_RELAY_HOST\t$SMTP_RELAY_PORT\t$SMTP_RELAY_USER\t"
 	echo -ne "$UPGRADE_SEND_EMAIL\t$UPGRADE_SEND_EMAIL_LOG\t$THEME\t$LANGUAGE\t$BACKUP_GZIP\t"
@@ -254,7 +258,7 @@ csv_list() {
 	echo -n "'PROXY_SSL_PORT','FTP_SYSTEM','MAIL_SYSTEM','IMAP_SYSTEM',"
 	echo -n "'ANTIVIRUS_SYSTEM','ANTISPAM_SYSTEM','DB_SYSTEM',"
 	echo -n "'DNS_SYSTEM','DNS_CLUSTER','STATS_SYSTEM','BACKUP_SYSTEM',"
-	echo -n "'CRON_SYSTEM','DISK_QUOTA','FIREWALL_SYSTEM',"
+	echo -n "'CRON_SYSTEM','DISK_QUOTA','RESOURCES_LIMIT','FIREWALL_SYSTEM',"
 	echo -n "'FIREWALL_EXTENSION','FILE_MANAGER','REPOSITORY',"
 	echo -n "'VERSION','DEMO_MODE','RELEASE_BRANCH',"
 	echo -n "'SMTP_RELAY','SMTP_RELAY_HOST','SMTP_RELAY_PORT','SMTP_RELAY_USER',"
@@ -268,7 +272,7 @@ csv_list() {
 	echo -n "'$PROXY_SSL_PORT','$FTP_SYSTEM','$MAIL_SYSTEM','$IMAP_SYSTEM',"
 	echo -n "'$ANTIVIRUS_SYSTEM','$ANTISPAM_SYSTEM','$DB_SYSTEM','$DNS_SYSTEM',"
 	echo -n "'$DNS_CLUSTER','$STATS_SYSTEM','$BACKUP_SYSTEM','$CRON_SYSTEM',"
-	echo -n "'$DISK_QUOTA','$FIREWALL_SYSTEM','$FIREWALL_EXTENSION','$FILE_MANAGER',"
+	echo -n "'$DISK_QUOTA','$RESOURCES_LIMIT','$FIREWALL_SYSTEM','$FIREWALL_EXTENSION','$FILE_MANAGER',"
 	echo -n "'$REPOSITORY', '$VERSION','$DEMO_MODE','$RELEASE_BRANCH',"
 	echo -n "'$SMTP_RELAY','$SMTP_RELAY_HOST','$SMTP_RELAY_PORT','$SMTP_RELAY_USER',"
 	echo -n "'$UPGRADE_SEND_EMAIL','$UPGRADE_SEND_EMAIL_LOG','$THEME','$LANGUAGE',"

+ 11 - 2
bin/v-list-user-package

@@ -38,6 +38,10 @@ json_list() {
         "DATABASES": "'$DATABASES'",
         "CRON_JOBS": "'$CRON_JOBS'",
         "DISK_QUOTA": "'$DISK_QUOTA'",
+        "CPU_QUOTA":"'$CPU_QUOTA'",
+        "CPU_QUOTA_PERIOD":"'$CPU_QUOTA_PERIOD'",
+        "MEMORY_LIMIT":"'$MEMORY_LIMIT'",
+        "SWAP_LIMIT":"'$SWAP_LIMIT'",
         "BANDWIDTH": "'$BANDWIDTH'",
         "NS": "'$NS'",
         "SHELL": "'$SHELL'",
@@ -66,6 +70,10 @@ shell_list() {
 	echo "DATABASES:        	$DATABASES"
 	echo "CRON JOBS:        	$CRON_JOBS"
 	echo "DISK QUOTA:       	$DISK_QUOTA"
+	echo "CPU_QUOTA: 					$CPU_QUOTA"
+	echo "CPU_QUOTA_PERIOD:		$CPU_QUOTA_PERIOD"
+	echo "MEMORY_LIMIT:				$MEMORY_LIMIT"
+	echo "SWAP_LIMIT: 				$SWAP_LIMIT"
 	echo "BANDWIDTH:        	$BANDWIDTH"
 	echo "NS:               	$NS"
 	echo "SHELL:            	$SHELL"
@@ -80,6 +88,7 @@ plain_list() {
 	echo -ne "$PACKAGE\t$WEB_TEMPLATE\t$BACKEND_TEMPLATE\t$PROXY_TEMPLATE\t$DNS_TEMPLATE\t"
 	echo -ne "$WEB_DOMAINS\t$WEB_ALIASES\t$DNS_DOMAINS\t$DNS_RECORDS\t"
 	echo -ne "$MAIL_DOMAINS\t$MAIL_ACCOUNTS\t$RATE_LIMIT\t$DATABASES\t$CRON_JOBS\t"
+	echo -ne "$CPU_QUOTA\t$CPU_QUOTA_PERIOD\t$MEMORY_LIMIT\t$SWAP_LIMIT\t"
 	echo -e "$DISK_QUOTA\t$BANDWIDTH\t$NS\t$SHELL\t$SHELL_JAIL_ENABLED\t$BACKUPS\t$TIME\t$DATE"
 }
 
@@ -88,11 +97,11 @@ csv_list() {
 	echo -n "PACKAGE,WEB_TEMPLATE,BACKEND_TEMPLATE,PROXY_TEMPLATE,DNS_TEMPLATE,"
 	echo -n "WEB_DOMAINS,WEB_ALIASES,DNS_DOMAINS,DNS_RECORDS,"
 	echo -n "MAIL_DOMAINS,MAIL_ACCOUNTS,RATE_LIMIT,DATABASES,CRON_JOBS,"
-	echo "DISK_QUOTA,BANDWIDTH,NS,SHELL,SHELL_JAIL_ENABLED,BACKUPS,TIME,DATE"
+	echo "DISK_QUOTA,CPU_QUOTA,CPU_QUOTA_PERIOD,MEMORY_LIMIT,SWAP_LIMIT,BANDWIDTH,NS,SHELL,SHELL_JAIL_ENABLED,BACKUPS,TIME,DATE"
 	echo -n "$PACKAGE,$WEB_TEMPLATE,$BACKEND_TEMPLATE,$PROXY_TEMPLATE,$DNS_TEMPLATE,"
 	echo -n "$WEB_DOMAINS,$WEB_ALIASES,$DNS_DOMAINS,$DNS_RECORDS,"
 	echo -n "$MAIL_DOMAINS,$MAIL_ACCOUNTS,$RATE_LIMIT,$DATABASES,$CRON_JOBS,"
-	echo "$DISK_QUOTA,$BANDWIDTH,\"$NS\",$SHELL,$BACKUPS,$TIME,$DATE"
+	echo "$DISK_QUOTA,$CPU_QUOTA,$CPU_QUOTA_PERIOD,$MEMORY_LIMIT,$SWAP_LIMIT,$BANDWIDTH,\"$NS\",$SHELL,$BACKUPS,$TIME,$DATE"
 }
 
 #----------------------------------------------------------#

+ 10 - 3
bin/v-list-users

@@ -49,6 +49,10 @@ json_list() {
         "DATABASES": "'$DATABASES'",
         "CRON_JOBS": "'$CRON_JOBS'",
         "DISK_QUOTA": "'$DISK_QUOTA'",
+        "CPU_QUOTA": "'$CPU_QUOTA'",
+        "CPU_QUOTA_PERIOD": "'$CPU_QUOTA_PERIOD'",
+        "MEMORY_LIMIT": "'$MEMORY_LIMIT'",
+        "SWAP_LIMIT": "'$SWAP_LIMIT'",
         "BANDWIDTH": "'$BANDWIDTH'",
         "NS": "'$NS'",
         "SHELL": "'$SHELL'",
@@ -118,7 +122,8 @@ plain_list() {
 		echo -ne "$BACKEND_TEMPLATE\t$PROXY_TEMPLATE\t$DNS_TEMPLATE\t"
 		echo -ne "$WEB_DOMAINS\t$WEB_ALIASES\t$DNS_DOMAINS\t$DNS_RECORDS\t"
 		echo -ne "$MAIL_DOMAINS\t$MAIL_ACCOUNTS\t$DATABASES\t$CRON_JOBS\t"
-		echo -ne "$DISK_QUOTA\t$BANDWIDTH\t$NS\t$SHELL\t$BACKUPS\t"
+		echo -ne "$DISK_QUOTA\t$CPU_QUOTA\t$CPU_QUOTA_PERIOD\t$MEMORY_LIMIT\t"
+		echo -ne "$SWAP_LIMIT\t$BANDWIDTH\t$NS\t$SHELL\t$BACKUPS\t"
 		echo -ne "$CONTACT\t$CRON_REPORTS\t$RKEY\t$ROLE\t$SUSPENDED\t"
 		echo -ne "$SUSPENDED_USERS\t$SUSPENDED_WEB\t$SUSPENDED_DNS\t"
 		echo -ne "$SUSPENDED_MAIL\t$SUSPENDED_DB\t$SUSPENDED_CRON\t"
@@ -136,7 +141,8 @@ csv_list() {
 	echo -n "USER,NAME,PACKAGE,WEB_TEMPLATE,BACKEND_TEMPLATE,"
 	echo -n "PROXY_TEMPLATE,DNS_TEMPLATE,WEB_DOMAINS,WEB_ALIASES,"
 	echo -n "DNS_DOMAINS,DNS_RECORDS,MAIL_DOMAINS,MAIL_ACCOUNTS,"
-	echo -n "DATABASES,CRON_JOBS,DISK_QUOTA,BANDWIDTH,NS,HOME,SHELL,"
+	echo -n "DATABASES,CRON_JOBS,DISK_QUOTA,CPU_QUOTA,CPU_QUOTA_PERIOD,"
+	echo -n "MEMORY_LIMIT,SWAP_LIMIT,BANDWIDTH,NS,HOME,SHELL,"
 	echo -n "BACKUPS,CONTACT,CRON_REPORTS,RKEY,ROLE,SUSPENDED,SUSPENDED_USERS,"
 	echo -n "SUSPENDED_WEB,SUSPENDED_DNS,SUSPENDED_MAIL,SUSPENDED_DB,"
 	echo -n "SUSPENDED_CRON,IP_AVAIL,IP_OWNED,U_USERS,U_DISK,U_DISK_DIRS,"
@@ -153,7 +159,8 @@ csv_list() {
 		echo -n "$BACKEND_TEMPLATE,$PROXY_TEMPLATE,$DNS_TEMPLATE,"
 		echo -n "$WEB_DOMAINS,$WEB_ALIASES,$DNS_DOMAINS,$DNS_RECORDS,"
 		echo -n "$MAIL_DOMAINS,$MAIL_ACCOUNTS,$DATABASES,$CRON_JOBS,"
-		echo -n "$DISK_QUOTA,$BANDWIDTH,\"$NS\",$HOME,$SHELL,$BACKUPS,"
+		echo -n "$DISK_QUOTA,$CPU_QUOTA,$CPU_QUOTA_PERIOD,$MEMORY_LIMIT,"
+		echo -n "$SWAP_LIMIT,$BANDWIDTH,\"$NS\",$HOME,$SHELL,$BACKUPS,"
 		echo -n "$CONTACT,$CRON_REPORTS,\"$RKEY\",$ROLE,$SUSPENDED,"
 		echo -n "$SUSPENDED_USERS,$SUSPENDED_WEB,$SUSPENDED_DNS,"
 		echo -n "$SUSPENDED_MAIL,$SUSPENDED_DB,$SUSPENDED_CRON,$IP_AVAIL,"

+ 5 - 0
bin/v-rebuild-user

@@ -54,6 +54,11 @@ if [ "$DISK_QUOTA" = 'yes' ]; then
 	$BIN/v-update-user-quota "$user"
 fi
 
+# Update cgroup
+if [ "$RESOURCES_LIMIT" = 'yes' ]; then
+	$BIN/v-update-user-cgroup "$user"
+fi
+
 # Rebuild user
 rebuild_user_conf
 

+ 84 - 0
bin/v-update-user-cgroup

@@ -0,0 +1,84 @@
+#!/bin/bash
+# info: update user disk quota
+# options: USER
+#
+# example: v-update-user-cgroup admin
+#
+# The functions upates cgroup, cpu, ram ,... for specific user
+
+#----------------------------------------------------------#
+#                Variables & Functions                     #
+#----------------------------------------------------------#
+
+# Argument definition
+user=$1
+
+# Includes
+# shellcheck source=/etc/hestiacp/hestia.conf
+source /etc/hestiacp/hestia.conf
+# shellcheck source=/usr/local/hestia/func/main.sh
+source $HESTIA/func/main.sh
+# load config file
+source_conf "$HESTIA/conf/hestia.conf"
+
+#----------------------------------------------------------#
+#                    Verifications                         #
+#----------------------------------------------------------#
+
+check_args '0' "$#" 'USER'
+is_format_valid 'user'
+is_object_valid 'user' 'USER' "$user"
+
+# Perform verification if read-only mode is enabled
+check_hestia_demo_mode
+
+#----------------------------------------------------------#
+#                       Action                             #
+#----------------------------------------------------------#
+user_id=$(id -u "$user")
+user_slice="user-${user_id}.slice"
+# get user resources values
+cpu_quota=$(get_user_value '$CPU_QUOTA')
+cpu_quota_period=$(get_user_value '$CPU_QUOTA_PERIOD')
+memory_limit=$(get_user_value '$MEMORY_LIMIT')
+swap_limit=$(get_user_value '$SWAP_LIMIT')
+
+# Set CPU quota for CFS:
+if [ "$cpu_quota" != 'unlimited' ]; then
+	systemctl set-property "$user_slice" CPUQuota="$cpu_quota"
+else
+	systemctl set-property "$user_slice" CPUQuota=
+fi
+
+# Set CPU period for CFS:
+if [ "$cpu_quota_period" != 'unlimited' ]; then
+	systemctl set-property "$user_slice" CPUQuotaPeriodSec="$cpu_quota_period"
+else
+	systemctl set-property "$user_slice" CPUQuotaPeriodSec=
+fi
+
+# Set memory limits:
+if [ "$memory_limit" != 'unlimited' ]; then
+	systemctl set-property "$user_slice" MemoryHigh="$memory_limit"
+else
+	systemctl set-property "$user_slice" MemoryHigh=
+fi
+
+# Set memory swap limits:
+if [ "$swap_limit" != 'unlimited' ]; then
+	systemctl set-property "$user_slice" MemorySwapMax="$swap_limit"
+else
+	systemctl set-property "$user_slice" MemorySwapMax=
+fi
+
+# Apply change immediately: not needed for now
+#systemctl restart "$user_slice"
+
+#----------------------------------------------------------#
+#                       Hestia                             #
+#----------------------------------------------------------#
+
+# Logging
+log_event "$OK" "$ARGUMENTS"
+
+exit

+ 3 - 2
bin/v-update-user-package

@@ -32,9 +32,10 @@ is_package_valid
 #----------------------------------------------------------#
 #                       Action                             #
 #----------------------------------------------------------#
-
 for user in $("$BIN/v-list-users" list); do
-	check_package=$(grep "PACKAGE='$package'" $USER_DATA/$user/user.conf)
+	# Fix Bug User Data contain path to the root user
+	USER_DATA="$HESTIA/data/users/$user"
+	check_package=$(grep "PACKAGE='$package'" $USER_DATA/user.conf)
 	if [ -n "$check_package" ]; then
 		"$BIN/v-change-user-package" "$user" "$package" 'yes'
 	fi

+ 28 - 0
func/main.sh

@@ -1150,6 +1150,34 @@ is_cron_format_valid() {
 	fi
 }
 
+# Validate CPU Quota:
+is_valid_cpu_quota() {
+	if [[ ! "$1" =~ ^[0-9]+%$ ]]; then
+		check_result "$E_INVALID" "Invalid CPU Quota format :: $1"
+	fi
+}
+
+# Validate CPU Quota Period:
+is_valid_cpu_quota_period() {
+	if [[ ! "$1" =~ ^[0-9]+(ms|s)$ ]]; then
+		check_result "$E_INVALID" "Invalid CPU Quota Period format :: $1"
+	fi
+}
+
+# Validate Memory Size:
+is_valid_memory_size() {
+	if [[ ! "$1" =~ ^[0-9]+[KMGTK]?$ ]]; then
+		check_result "$E_INVALID" "Invalid Memory Size format :: $1"
+	fi
+}
+
+# Validate Swap Size:
+is_valid_swap_size() {
+	if [[ ! "$1" =~ ^[0-9]+[KMGTK]?$ ]]; then
+		check_result "$E_INVALID" "Invalid Swap Size format :: $1"
+	fi
+}
+
 is_object_name_format_valid() {
 	if ! [[ "$1" =~ ^[-|\ |\.|_[:alnum:]]{0,50}$ ]]; then
 		check_result "$E_INVALID" "invalid $2 format :: $1"

+ 4 - 0
install/common/packages/default.pkg

@@ -12,6 +12,10 @@ RATE_LIMIT='200'
 DATABASES='unlimited'
 CRON_JOBS='unlimited'
 DISK_QUOTA='unlimited'
+CPU_QUOTA='unlimited'
+CPU_QUOTA_PERIOD='unlimited'
+MEMORY_LIMIT='unlimited'
+SWAP_LIMIT='unlimited'
 BANDWIDTH='unlimited'
 NS='ns1.domain.tld,ns2.domain.tld'
 SHELL='nologin'

+ 4 - 0
install/common/packages/system.pkg

@@ -12,6 +12,10 @@ RATE_LIMIT='200'
 DATABASES='0'
 CRON_JOBS='unlimited'
 DISK_QUOTA='unlimited'
+CPU_QUOTA='unlimited'
+CPU_QUOTA_PERIOD='unlimited'
+MEMORY_LIMIT='unlimited'
+SWAP_LIMIT='unlimited'
 BANDWIDTH='unlimited'
 NS='ns1.domain.tld,ns2.domain.tld'
 SHELL='nologin'

+ 42 - 31
install/hst-install-debian.sh

@@ -74,6 +74,7 @@ help() {
   -i, --iptables          Install iptables      [yes|no]  default: yes
   -b, --fail2ban          Install Fail2Ban      [yes|no]  default: yes
   -q, --quota             Filesystem Quota      [yes|no]  default: no
+  -L, --resourcelimit     Resource Limitation   [yes|no]  default: no
   -W, --webterminal       Web Terminal          [yes|no]  default: no
   -d, --api               Activate API          [yes|no]  default: yes
   -r, --port              Change Backend Port             default: 8083
@@ -258,6 +259,7 @@ for arg; do
 		--fail2ban) args="${args}-b " ;;
 		--multiphp) args="${args}-o " ;;
 		--quota) args="${args}-q " ;;
+		--resourcelimit) args="${args}-L " ;;
 		--webterminal) args="${args}-W " ;;
 		--port) args="${args}-r " ;;
 		--lang) args="${args}-l " ;;
@@ -279,38 +281,39 @@ done
 eval set -- "$args"
 
 # Parsing arguments
-while getopts "a:w:v:j:k:m:M:g:d:x:z:Z:c:t:i:b:r:o:q:l:y:s:u:e:p:W:D:fh" Option; do
+while getopts "a:w:v:j:k:m:M:g:d:x:z:Z:c:t:i:b:r:o:q:L:l:y:s:u:e:p:W:D:fh" Option; do
 	case $Option in
-		a) apache=$OPTARG ;;      # Apache
-		w) phpfpm=$OPTARG ;;      # PHP-FPM
-		o) multiphp=$OPTARG ;;    # Multi-PHP
-		v) vsftpd=$OPTARG ;;      # Vsftpd
-		j) proftpd=$OPTARG ;;     # Proftpd
-		k) named=$OPTARG ;;       # Named
-		m) mysql=$OPTARG ;;       # MariaDB
-		M) mysql8=$OPTARG ;;      # MySQL
-		g) postgresql=$OPTARG ;;  # PostgreSQL
-		x) exim=$OPTARG ;;        # Exim
-		z) dovecot=$OPTARG ;;     # Dovecot
-		Z) sieve=$OPTARG ;;       # Sieve
-		c) clamd=$OPTARG ;;       # ClamAV
-		t) spamd=$OPTARG ;;       # SpamAssassin
-		i) iptables=$OPTARG ;;    # Iptables
-		b) fail2ban=$OPTARG ;;    # Fail2ban
-		q) quota=$OPTARG ;;       # FS Quota
-		W) webterminal=$OPTARG ;; # Web Terminal
-		r) port=$OPTARG ;;        # Backend Port
-		l) lang=$OPTARG ;;        # Language
-		d) api=$OPTARG ;;         # Activate API
-		y) interactive=$OPTARG ;; # Interactive install
-		s) servername=$OPTARG ;;  # Hostname
-		e) email=$OPTARG ;;       # Admin email
-		u) username=$OPTARG ;;    # Admin username
-		p) vpass=$OPTARG ;;       # Admin password
-		D) withdebs=$OPTARG ;;    # Hestia debs path
-		f) force='yes' ;;         # Force install
-		h) help ;;                # Help
-		*) help ;;                # Print help (default)
+		a) apache=$OPTARG ;;        # Apache
+		w) phpfpm=$OPTARG ;;        # PHP-FPM
+		o) multiphp=$OPTARG ;;      # Multi-PHP
+		v) vsftpd=$OPTARG ;;        # Vsftpd
+		j) proftpd=$OPTARG ;;       # Proftpd
+		k) named=$OPTARG ;;         # Named
+		m) mysql=$OPTARG ;;         # MariaDB
+		M) mysql8=$OPTARG ;;        # MySQL
+		g) postgresql=$OPTARG ;;    # PostgreSQL
+		x) exim=$OPTARG ;;          # Exim
+		z) dovecot=$OPTARG ;;       # Dovecot
+		Z) sieve=$OPTARG ;;         # Sieve
+		c) clamd=$OPTARG ;;         # ClamAV
+		t) spamd=$OPTARG ;;         # SpamAssassin
+		i) iptables=$OPTARG ;;      # Iptables
+		b) fail2ban=$OPTARG ;;      # Fail2ban
+		q) quota=$OPTARG ;;         # FS Quota
+		L) resourcelimit=$OPTARG ;; # Resource Limitaiton
+		W) webterminal=$OPTARG ;;   # Web Terminal
+		r) port=$OPTARG ;;          # Backend Port
+		l) lang=$OPTARG ;;          # Language
+		d) api=$OPTARG ;;           # Activate API
+		y) interactive=$OPTARG ;;   # Interactive install
+		s) servername=$OPTARG ;;    # Hostname
+		e) email=$OPTARG ;;         # Admin email
+		u) username=$OPTARG ;;      # Admin username
+		p) vpass=$OPTARG ;;         # Admin password
+		D) withdebs=$OPTARG ;;      # Hestia debs path
+		f) force='yes' ;;           # Force install
+		h) help ;;                  # Help
+		*) help ;;                  # Print help (default)
 	esac
 done
 
@@ -378,6 +381,7 @@ fi
 set_default_value 'iptables' 'yes'
 set_default_value 'fail2ban' 'yes'
 set_default_value 'quota' 'no'
+set_default_value 'resourcelimit' 'no'
 set_default_value 'webterminal' 'no'
 set_default_value 'interactive' 'yes'
 set_default_value 'api' 'yes'
@@ -1366,6 +1370,13 @@ else
 	write_config_value "DISK_QUOTA" "no"
 fi
 
+# Resource limitation
+if [ "$resourcelimit" = 'yes' ]; then
+	write_config_value "RESOURCES_LIMIT" "yes"
+else
+	write_config_value "RESOURCES_LIMIT" "no"
+fi
+
 write_config_value "WEB_TERMINAL_PORT" "8085"
 
 # Backups

+ 42 - 31
install/hst-install-ubuntu.sh

@@ -75,6 +75,7 @@ help() {
   -i, --iptables          Install iptables      [yes|no]  default: yes
   -b, --fail2ban          Install Fail2Ban      [yes|no]  default: yes
   -q, --quota             Filesystem Quota      [yes|no]  default: no
+  -L, --resourcelimit     Resource Limitation   [yes|no]  default: no
   -W, --webterminal       Web Terminal          [yes|no]  default: no
   -d, --api               Activate API          [yes|no]  default: yes
   -r, --port              Change Backend Port             default: 8083
@@ -259,6 +260,7 @@ for arg; do
 		--fail2ban) args="${args}-b " ;;
 		--multiphp) args="${args}-o " ;;
 		--quota) args="${args}-q " ;;
+		--resourcelimit) args="${args}-L " ;;
 		--webterminal) args="${args}-W " ;;
 		--port) args="${args}-r " ;;
 		--lang) args="${args}-l " ;;
@@ -280,38 +282,39 @@ done
 eval set -- "$args"
 
 # Parsing arguments
-while getopts "a:w:v:j:k:m:M:g:d:x:z:Z:c:t:i:b:r:o:q:l:y:s:u:e:p:W:D:fh" Option; do
+while getopts "a:w:v:j:k:m:M:g:d:x:z:Z:c:t:i:b:r:o:q:L:l:y:s:u:e:p:W:D:fh" Option; do
 	case $Option in
-		a) apache=$OPTARG ;;      # Apache
-		w) phpfpm=$OPTARG ;;      # PHP-FPM
-		o) multiphp=$OPTARG ;;    # Multi-PHP
-		v) vsftpd=$OPTARG ;;      # Vsftpd
-		j) proftpd=$OPTARG ;;     # Proftpd
-		k) named=$OPTARG ;;       # Named
-		m) mysql=$OPTARG ;;       # MariaDB
-		M) mysql8=$OPTARG ;;      # MySQL
-		g) postgresql=$OPTARG ;;  # PostgreSQL
-		x) exim=$OPTARG ;;        # Exim
-		z) dovecot=$OPTARG ;;     # Dovecot
-		Z) sieve=$OPTARG ;;       # Sieve
-		c) clamd=$OPTARG ;;       # ClamAV
-		t) spamd=$OPTARG ;;       # SpamAssassin
-		i) iptables=$OPTARG ;;    # Iptables
-		b) fail2ban=$OPTARG ;;    # Fail2ban
-		q) quota=$OPTARG ;;       # FS Quota
-		W) webterminal=$OPTARG ;; # Web Terminal
-		r) port=$OPTARG ;;        # Backend Port
-		l) lang=$OPTARG ;;        # Language
-		d) api=$OPTARG ;;         # Activate API
-		y) interactive=$OPTARG ;; # Interactive install
-		s) servername=$OPTARG ;;  # Hostname
-		e) email=$OPTARG ;;       # Admin email
-		u) username=$OPTARG ;;    # Admin username
-		p) vpass=$OPTARG ;;       # Admin password
-		D) withdebs=$OPTARG ;;    # Hestia debs path
-		f) force='yes' ;;         # Force install
-		h) help ;;                # Help
-		*) help ;;                # Print help (default)
+		a) apache=$OPTARG ;;        # Apache
+		w) phpfpm=$OPTARG ;;        # PHP-FPM
+		o) multiphp=$OPTARG ;;      # Multi-PHP
+		v) vsftpd=$OPTARG ;;        # Vsftpd
+		j) proftpd=$OPTARG ;;       # Proftpd
+		k) named=$OPTARG ;;         # Named
+		m) mysql=$OPTARG ;;         # MariaDB
+		M) mysql8=$OPTARG ;;        # MySQL
+		g) postgresql=$OPTARG ;;    # PostgreSQL
+		x) exim=$OPTARG ;;          # Exim
+		z) dovecot=$OPTARG ;;       # Dovecot
+		Z) sieve=$OPTARG ;;         # Sieve
+		c) clamd=$OPTARG ;;         # ClamAV
+		t) spamd=$OPTARG ;;         # SpamAssassin
+		i) iptables=$OPTARG ;;      # Iptables
+		b) fail2ban=$OPTARG ;;      # Fail2ban
+		q) quota=$OPTARG ;;         # FS Quota
+		L) resourcelimit=$OPTARG ;; # Resource Limitation
+		W) webterminal=$OPTARG ;;   # Web Terminal
+		r) port=$OPTARG ;;          # Backend Port
+		l) lang=$OPTARG ;;          # Language
+		d) api=$OPTARG ;;           # Activate API
+		y) interactive=$OPTARG ;;   # Interactive install
+		s) servername=$OPTARG ;;    # Hostname
+		e) email=$OPTARG ;;         # Admin email
+		u) username=$OPTARG ;;      # Admin username
+		p) vpass=$OPTARG ;;         # Admin password
+		D) withdebs=$OPTARG ;;      # Hestia debs path
+		f) force='yes' ;;           # Force install
+		h) help ;;                  # Help
+		*) help ;;                  # Print help (default)
 	esac
 done
 
@@ -379,6 +382,7 @@ fi
 set_default_value 'iptables' 'yes'
 set_default_value 'fail2ban' 'yes'
 set_default_value 'quota' 'no'
+set_default_value 'resourcelimit' 'no'
 set_default_value 'webterminal' 'no'
 set_default_value 'interactive' 'yes'
 set_default_value 'api' 'yes'
@@ -1380,6 +1384,13 @@ else
 	write_config_value "DISK_QUOTA" "no"
 fi
 
+# Resource limitation
+if [ "$resourcelimit" = 'yes' ]; then
+	write_config_value "RESOURCES_LIMIT" "yes"
+else
+	write_config_value "RESOURCES_LIMIT" "no"
+fi
+
 write_config_value "WEB_TERMINAL_PORT" "8085"
 
 # Backups

+ 6 - 0
install/upgrade/versions/1.9.0.sh

@@ -78,6 +78,12 @@ for package in $packages; do
 	if [ -z "$(grep -e 'SHELL_JAIL_ENABLED' $HESTIA/data/packages/$package)" ]; then
 		echo "SHELL_JAIL_ENABLED='no'" >> $HESTIA/data/packages/$package
 	fi
+	# Add additional key-value pairs if they don't exist
+	for key in DISK_QUOTA CPU_QUOTA CPU_QUOTA_PERIOD MEMORY_LIMIT SWAP_LIMIT; do
+		if [ -z "$(grep -e "$key" $HESTIA/data/packages/$package)" ]; then
+			echo "$key='unlimited'" >> $HESTIA/data/packages/$package
+		fi
+	done
 done
 
 $BIN/v-add-user-notification 'admin' 'Hestia securirty has been upgraded' 'Here should come a nice message about the upgrade and how to change the user name of the admin user!'

+ 36 - 0
web/add/package/index.php

@@ -83,6 +83,20 @@ if (!empty($_POST["ok"])) {
 	if (!isset($_POST["v_ratelimit"])) {
 		$errors[] = _("Rate Limit");
 	}
+
+	if (!isset($_POST["v_cpu_quota"])) {
+		$errors[] = _("CPU quota");
+	}
+	if (!isset($_POST["v_cpu_quota_period"])) {
+		$errors[] = _("CPU quota period");
+	}
+	if (!isset($_POST["v_memory_limit"])) {
+		$errors[] = _("Memory Limit");
+	}
+	if (!isset($_POST["v_swap_limit"])) {
+		$errors[] = _("Swap Limit");
+	}
+
 	// Check if name server entries are blank if DNS server is installed
 	if (isset($_SESSION["DNS_SYSTEM"]) && !empty($_SESSION["DNS_SYSTEM"])) {
 		if (empty($_POST["v_ns1"])) {
@@ -132,6 +146,10 @@ if (!empty($_POST["ok"])) {
 		$v_disk_quota = quoteshellarg($_POST["v_disk_quota"]);
 		$v_bandwidth = quoteshellarg($_POST["v_bandwidth"]);
 		$v_ratelimit = quoteshellarg($_POST["v_ratelimit"]);
+		$v_cpu_quota = quoteshellarg($_POST["v_cpu_quota"]);
+		$v_cpu_quota_period = quoteshellarg($_POST["v_cpu_quota_period"]);
+		$v_memory_limit = quoteshellarg($_POST["v_memory_limit"]);
+		$v_swap_limit = quoteshellarg($_POST["v_swap_limit"]);
 		$v_ns1 = !empty($_POST["v_ns1"]) ? trim($_POST["v_ns1"], ".") : "";
 		$v_ns2 = !empty($_POST["v_ns2"]) ? trim($_POST["v_ns2"], ".") : "";
 		$v_ns3 = !empty($_POST["v_ns3"]) ? trim($_POST["v_ns3"], ".") : "";
@@ -183,6 +201,10 @@ if (!empty($_POST["ok"])) {
 			$pkg .= "DATABASES=" . $v_databases . "\n";
 			$pkg .= "CRON_JOBS=" . $v_cron_jobs . "\n";
 			$pkg .= "DISK_QUOTA=" . $v_disk_quota . "\n";
+			$pkg .= "CPU_QUOTA=" . $v_cpu_quota . "\n";
+			$pkg .= "CPU_QUOTA_PERIOD=" . $v_cpu_quota_period . "\n";
+			$pkg .= "MEMORY_LIMIT=" . $v_memory_limit . "\n";
+			$pkg .= "SWAP_LIMIT=" . $v_swap_limit . "\n";
 			$pkg .= "BANDWIDTH=" . $v_bandwidth . "\n";
 			$pkg .= "RATE_LIMIT=" . $v_ratelimit . "\n";
 			$pkg .= "NS=" . $v_ns . "\n";
@@ -309,6 +331,20 @@ if (empty($v_bandwidth)) {
 if (empty($v_ratelimit)) {
 	$v_ratelimit = "'200'";
 }
+
+if (empty($v_cpu_quota)) {
+	$v_cpu_quota = "'unlimited'";
+}
+if (empty($v_cpu_quota_period)) {
+	$v_cpu_quota_period = "'unlimited'";
+}
+if (empty($v_memory_limit)) {
+	$v_memory_limit = "'unlimited'";
+}
+if (empty($v_swap_limit)) {
+	$v_swap_limit = "'unlimited'";
+}
+
 if (empty($v_ns1)) {
 	$v_ns1 = "ns1.example.tld";
 }

+ 25 - 0
web/edit/package/index.php

@@ -52,6 +52,10 @@ $v_disk_quota = $data[$v_package]["DISK_QUOTA"];
 $v_bandwidth = $data[$v_package]["BANDWIDTH"];
 $v_shell = $data[$v_package]["SHELL"];
 $v_shell_jail_enabled = $data[$v_package]["SHELL_JAIL_ENABLED"];
+$v_cpu_quota = $data[$v_package]["CPU_QUOTA"];
+$v_cpu_quota_period = $data[$v_package]["CPU_QUOTA_PERIOD"];
+$v_memory_limit = $data[$v_package]["MEMORY_LIMIT"];
+$v_swap_limit = $data[$v_package]["SWAP_LIMIT"];
 $v_ns = $data[$v_package]["NS"];
 $nameservers = explode(",", $v_ns);
 if (empty($nameservers[0])) {
@@ -193,6 +197,19 @@ if (!empty($_POST["save"])) {
 		$errors[] = _("Bandwidth");
 	}
 
+	if (!isset($_POST["v_cpu_quota"])) {
+		$errors[] = _("CPU quota");
+	}
+	if (!isset($_POST["v_cpu_quota_period"])) {
+		$errors[] = _("CPU quota period");
+	}
+	if (!isset($_POST["v_memory_limit"])) {
+		$errors[] = _("Memory Limit");
+	}
+	if (!isset($_POST["v_swap_limit"])) {
+		$errors[] = _("Swap Limit");
+	}
+
 	// Check if name server entries are blank if DNS server is installed
 	if (isset($_SESSION["DNS_SYSTEM"]) && !empty($_SESSION["DNS_SYSTEM"])) {
 		if (empty($_POST["v_ns1"])) {
@@ -256,6 +273,10 @@ if (!empty($_POST["save"])) {
 	$v_backups = quoteshellarg($_POST["v_backups"]);
 	$v_disk_quota = quoteshellarg($_POST["v_disk_quota"]);
 	$v_bandwidth = quoteshellarg($_POST["v_bandwidth"]);
+	$v_cpu_quota = quoteshellarg($_POST["v_cpu_quota"]);
+	$v_cpu_quota_period = quoteshellarg($_POST["v_cpu_quota_period"]);
+	$v_memory_limit = quoteshellarg($_POST["v_memory_limit"]);
+	$v_swap_limit = quoteshellarg($_POST["v_swap_limit"]);
 	$v_ns1 = !empty($_POST["v_ns1"]) ? trim($_POST["v_ns1"], ".") : "";
 	$v_ns2 = !empty($_POST["v_ns2"]) ? trim($_POST["v_ns2"], ".") : "";
 	$v_ns3 = !empty($_POST["v_ns3"]) ? trim($_POST["v_ns3"], ".") : "";
@@ -302,6 +323,10 @@ if (!empty($_POST["save"])) {
 	$pkg .= "DATABASES=" . $v_databases . "\n";
 	$pkg .= "CRON_JOBS=" . $v_cron_jobs . "\n";
 	$pkg .= "DISK_QUOTA=" . $v_disk_quota . "\n";
+	$pkg .= "CPU_QUOTA=" . $v_cpu_quota . "\n";
+	$pkg .= "CPU_QUOTA_PERIOD=" . $v_cpu_quota_period . "\n";
+	$pkg .= "MEMORY_LIMIT=" . $v_memory_limit . "\n";
+	$pkg .= "SWAP_LIMIT=" . $v_swap_limit . "\n";
 	$pkg .= "BANDWIDTH=" . $v_bandwidth . "\n";
 	$pkg .= "NS=" . $v_ns . "\n";
 	$pkg .= "SHELL=" . $v_shell . "\n";

+ 24 - 0
web/edit/server/index.php

@@ -582,6 +582,30 @@ if (!empty($_POST["save"])) {
 		}
 	}
 
+	// Set systen resources limit support
+	if (empty($_SESSION["error_msg"])) {
+		if (
+			!empty($_POST["v_resources_limit"]) &&
+			$_SESSION["RESOURCES_LIMIT"] != $_POST["v_resources_limit"]
+		) {
+			if ($_POST["v_resources_limit"] == "yes") {
+				exec(HESTIA_CMD . "v-add-sys-cgroups", $output, $return_var);
+				check_return_code($return_var, $output);
+				unset($output);
+				if (empty($_SESSION["error_msg"])) {
+					$_SESSION["RESOURCES_LIMIT"] = "yes";
+				}
+			} else {
+				exec(HESTIA_CMD . "v-delete-sys-cgroups", $output, $return_var);
+				check_return_code($return_var, $output);
+				unset($output);
+				if (empty($_SESSION["error_msg"])) {
+					$_SESSION["RESOURCES_LIMIT"] = "no";
+				}
+			}
+		}
+	}
+
 	// Set firewall support
 	if (empty($_SESSION["error_msg"])) {
 		if ($_SESSION["FIREWALL_SYSTEM"] == "iptables") {

+ 61 - 0
web/templates/pages/add_package.php

@@ -278,6 +278,67 @@
 					</div>
 				</div>
 			</details>
+
+			<?php if ($_SESSION['RESOURCES_LIMIT'] == 'yes') { ?>
+				<details class="collapse" id="system-resources-options">
+				<summary class="collapse-header">
+					<?= _("System Resources") ?>
+				</summary>
+				<div class="collapse-content">
+					<div class="u-mb10">
+						<label for="cfs_quota" class="form-label">
+							<?= _("CPU Quota (in %)") ?>
+						</label>
+						<div class="u-pos-relative">
+							<input type="text" class="form-control" name="v_cpu_quota" id="v_cpu_quota" value="<?= htmlentities(trim($v_cpu_quota, "'")) ?>">
+							<button type="button" class="unlimited-toggle js-unlimited-toggle" title="<?= _("Unlimited") ?>">
+								<i class="fas fa-infinity"></i>
+							</button>
+						</div>
+						<small class="form-text text-muted"><?= _("CPUQuota=20% ensures that the executed processes will never get more than 20% CPU time on one CPU.") ?></small>
+					</div>
+
+					<div class="u-mb10">
+						<label for="cfs_period" class="form-label">
+							<?= _("CPU Quota Period (in ms for milliseconds or s for seconds.)") ?>
+						</label>
+						<div class="u-pos-relative">
+							<input type="text" class="form-control" name="v_cpu_quota_period" id="v_cpu_quota_period" value="<?= htmlentities(trim($v_cpu_quota_period, "'")) ?>">
+							<button type="button" class="unlimited-toggle js-unlimited-toggle" title="<?= _("Unlimited") ?>">
+								<i class="fas fa-infinity"></i>
+							</button>
+						</div>
+						<small class="form-text text-muted"><?= _("CPUQuotaPeriodSec=10ms to request that the CPU quota is measured in periods of 10ms.") ?></small>
+					</div>
+
+					<div class="u-mb10">
+						<label for="memory_limit" class="form-label">
+							<?= _("Memory Limit (in bytes or with units like '2G')") ?>
+						</label>
+						<div class="u-pos-relative">
+							<input type="text" class="form-control" name="v_memory_limit" id="v_memory_limit" value="<?= htmlentities(trim($v_memory_limit, "'")) ?>">
+							<button type="button" class="unlimited-toggle js-unlimited-toggle" title="<?= _("Unlimited") ?>">
+								<i class="fas fa-infinity"></i>
+							</button>
+						</div>
+						<small class="form-text text-muted"><?= _("Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively") ?></small>
+					</div>
+
+					<div class="u-mb10">
+						<label for="swap_limit" class="form-label">
+							<?= _("Swap Limit (in bytes or with units like '2G')") ?>
+						</label>
+						<div class="u-pos-relative">
+							<input type="text" class="form-control" name="v_swap_limit" id="v_swap_limit" value="<?= htmlentities(trim($v_swap_limit, "'")) ?>">
+							<button type="button" class="unlimited-toggle js-unlimited-toggle" title="<?= _("Unlimited") ?>">
+								<i class="fas fa-infinity"></i>
+							</button>
+						</div>
+						<small class="form-text text-muted"><?= _("Takes a swap size in bytes. If the value is suffixed with K, M, G or T, the specified swap size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively") ?></small>
+					</div>
+				</div>
+			</details>
+			<?php } ?>
 		</div>
 
 	</form>

+ 99 - 32
web/templates/pages/edit_package.php

@@ -91,52 +91,56 @@
 						</label>
 						<select class="form-select" name="v_web_template" id="v_web_template">
 							<?php
-								foreach ($web_templates as $key => $value) {
-									echo "\t\t\t\t<option value=\"".htmlentities($value)."\"";
-									if ((!empty($v_web_template)) && ( $value == trim($v_web_template, "'"))){
-										echo ' selected' ;
-									}
-									echo ">".htmlentities($value)."</option>\n";
+							foreach ($web_templates as $key => $value) {
+								echo "\t\t\t\t<option value=\"" . htmlentities($value) . "\"";
+								if ((!empty($v_web_template)) && ($value == trim($v_web_template, "'"))) {
+									echo ' selected';
 								}
+								echo ">" . htmlentities($value) . "</option>\n";
+							}
 							?>
 						</select>
 					</div>
-					<?php if (!empty($_SESSION['WEB_BACKEND'])) { echo ""; ?>
+					<?php if (!empty($_SESSION['WEB_BACKEND'])) {
+						echo ""; ?>
 						<div class="u-mb10">
 							<label for="v_backend_template" class="form-label">
 								<?= _("Backend Template") . "<span class='optional'>" . strtoupper($_SESSION["WEB_BACKEND"]) . "</span>" ?>
 							</label>
 							<select class="form-select" name="v_backend_template" id="v_backend_template">
 								<?php
-									foreach ($backend_templates as $key => $value) {
-										echo "\t\t\t\t<option value=\"".$value."\"";
-										if ((!empty($v_backend_template)) && ( $value == trim($v_backend_template, "'"))){
-											echo ' selected' ;
-										}
-										echo ">".htmlentities($value)."</option>\n";
+								foreach ($backend_templates as $key => $value) {
+									echo "\t\t\t\t<option value=\"" . $value . "\"";
+									if ((!empty($v_backend_template)) && ($value == trim($v_backend_template, "'"))) {
+										echo ' selected';
 									}
+									echo ">" . htmlentities($value) . "</option>\n";
+								}
 								?>
 							</select>
 						</div>
-					<?=""; }?>
-					<?php if (!empty($_SESSION['PROXY_SYSTEM'])) { echo ""; ?>
+						<?= "";
+					} ?>
+					<?php if (!empty($_SESSION['PROXY_SYSTEM'])) {
+						echo ""; ?>
 						<div class="u-mb10">
 							<label for="v_proxy_template" class="form-label">
 								<?= _("Proxy Template") . "<span class='optional'>" . strtoupper($_SESSION["PROXY_SYSTEM"]) . "</span>" ?>
 							</label>
 							<select class="form-select" name="v_proxy_template" id="v_proxy_template">
 								<?php
-									foreach ($proxy_templates as $key => $value) {
-										echo "\t\t\t\t<option value=\"".htmlentities($value)."\"";
-										if ((!empty($v_proxy_template)) && ( $value == trim($v_proxy_template, "'"))){
-											echo ' selected' ;
-										}
-										echo ">".htmlentities($value)."</option>\n";
+								foreach ($proxy_templates as $key => $value) {
+									echo "\t\t\t\t<option value=\"" . htmlentities($value) . "\"";
+									if ((!empty($v_proxy_template)) && ($value == trim($v_proxy_template, "'"))) {
+										echo ' selected';
 									}
+									echo ">" . htmlentities($value) . "</option>\n";
+								}
 								?>
 							</select>
 						</div>
-					<?=""; }?>
+						<?= "";
+					} ?>
 				</div>
 			</details>
 			<details class="collapse" id="dns-options">
@@ -150,16 +154,16 @@
 						</label>
 						<select class="form-select" name="v_dns_template" id="v_dns_template">
 							<?php
-								foreach ($dns_templates as $key => $value) {
-									echo "\t\t\t\t<option value=\"".htmlentities($value)."\"";
-									if ((!empty($v_dns_template)) && ( $value == $v_dns_template)){
-										echo ' selected' ;
-									}
-									if ((!empty($v_dns_template)) && ( $value == trim($v_dns_template, "'"))){
-										echo ' selected' ;
-									}
-									echo ">".htmlentities($value)."</option>\n";
+							foreach ($dns_templates as $key => $value) {
+								echo "\t\t\t\t<option value=\"" . htmlentities($value) . "\"";
+								if ((!empty($v_dns_template)) && ($value == $v_dns_template)) {
+									echo ' selected';
+								}
+								if ((!empty($v_dns_template)) && ($value == trim($v_dns_template, "'"))) {
+									echo ' selected';
 								}
+								echo ">" . htmlentities($value) . "</option>\n";
+							}
 							?>
 						</select>
 					</div>
@@ -276,13 +280,76 @@
 						</select>
 					</div>
 					<div class="form-check u-mb10">
-						<input class="form-check-input" type="checkbox" name="v_shell_jail_enabled" id="v_shell_jail_enabled" value="yes" <?php if (htmlentities(trim($v_shell_jail_enabled, "'")) == "yes") echo 'checked' ?>>
+						<input class="form-check-input" type="checkbox" name="v_shell_jail_enabled" id="v_shell_jail_enabled"
+							   value="yes" <?php if (htmlentities(trim($v_shell_jail_enabled, "'")) == "yes") echo 'checked' ?>>
 						<label for="v_shell_jail_enabled">
 							<?= _("Jail User Shell") ?>
 						</label>
 					</div>
 				</div>
 			</details>
+
+			<?php if ($_SESSION['RESOURCES_LIMIT'] == 'yes') { ?>
+				<details class="collapse" id="system-resources-options">
+					<summary class="collapse-header">
+						<?= _("System Resources") ?>
+					</summary>
+					<div class="collapse-content">
+						<div class="u-mb10">
+							<label for="cfs_quota" class="form-label">
+								<?= _("CPU Quota (in %)") ?>
+							</label>
+							<div class="u-pos-relative">
+								<input type="text" class="form-control" name="v_cpu_quota" id="v_cpu_quota" value="<?= htmlentities(trim($v_cpu_quota, "'")) ?>">
+								<button type="button" class="unlimited-toggle js-unlimited-toggle" title="<?= _("Unlimited") ?>">
+									<i class="fas fa-infinity"></i>
+								</button>
+							</div>
+							<small class="form-text text-muted"><?= _("CPUQuota=20% ensures that the executed processes will never get more than 20% CPU time on one CPU.") ?></small>
+						</div>
+
+						<div class="u-mb10">
+							<label for="cfs_period" class="form-label">
+								<?= _("CPU Quota Period (in ms for milliseconds or s for seconds.)") ?>
+							</label>
+							<div class="u-pos-relative">
+								<input type="text" class="form-control" name="v_cpu_quota_period" id="v_cpu_quota_period" value="<?= htmlentities(trim($v_cpu_quota_period, "'")) ?>">
+								<button type="button" class="unlimited-toggle js-unlimited-toggle" title="<?= _("Unlimited") ?>">
+									<i class="fas fa-infinity"></i>
+								</button>
+							</div>
+							<small class="form-text text-muted"><?= _("CPUQuotaPeriodSec=10ms to request that the CPU quota is measured in periods of 10ms.") ?></small>
+						</div>
+
+						<div class="u-mb10">
+							<label for="memory_limit" class="form-label">
+								<?= _("Memory Limit (in bytes or with units like '2G')") ?>
+							</label>
+							<div class="u-pos-relative">
+								<input type="text" class="form-control" name="v_memory_limit" id="v_memory_limit" value="<?= htmlentities(trim($v_memory_limit, "'")) ?>">
+								<button type="button" class="unlimited-toggle js-unlimited-toggle" title="<?= _("Unlimited") ?>">
+									<i class="fas fa-infinity"></i>
+								</button>
+							</div>
+							<small class="form-text text-muted"><?= _("Takes a memory size in bytes. If the value is suffixed with K, M, G or T, the specified memory size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively") ?></small>
+						</div>
+
+						<div class="u-mb10">
+							<label for="swap_limit" class="form-label">
+								<?= _("Swap Limit (in bytes or with units like '2G')") ?>
+							</label>
+							<div class="u-pos-relative">
+								<input type="text" class="form-control" name="v_swap_limit" id="v_swap_limit" value="<?= htmlentities(trim($v_swap_limit, "'")) ?>">
+								<button type="button" class="unlimited-toggle js-unlimited-toggle" title="<?= _("Unlimited") ?>">
+									<i class="fas fa-infinity"></i>
+								</button>
+							</div>
+							<small class="form-text text-muted"><?= _("Takes a swap size in bytes. If the value is suffixed with K, M, G or T, the specified swap size is parsed as Kilobytes, Megabytes, Gigabytes, or Terabytes (with the base 1024), respectively") ?></small>
+						</div>
+					</div>
+				</details>
+			<?php } ?>
+
 		</div>
 
 	</form>

+ 13 - 0
web/templates/pages/edit_server.php

@@ -1401,6 +1401,19 @@
 							</option>
 						</select>
 					</div>
+					<div class="u-mb10">
+						<label for="v_resources_limit" class="form-label">
+							<?= _("Limit System Resources") ?>
+						</label>
+						<select class="form-select" name="v_resources_limit" id="v_resources_limit">
+							<option value="no">
+								<?= _("No") ?>
+							</option>
+							<option value="yes" <?= $_SESSION["RESOURCES_LIMIT"] == "yes" ? "selected" : "" ?>>
+								<?= _("Yes") ?>
+							</option>
+						</select>
+					</div>
 					<div class="u-mb10">
 						<label for="v_quota" class="form-label">
 							<?= _("File System Disk Quota") ?>