Просмотр исходного кода

Limit CPU and RAM for Each User Using cgroup

oulfr 2 лет назад
Родитель
Сommit
81146ecb60

+ 5 - 0
bin/v-add-user

@@ -266,6 +266,11 @@ if [ "$DISK_QUOTA" = 'yes' ]; then
 	$BIN/v-update-user-quota "$user"
 	$BIN/v-update-user-quota "$user"
 fi
 fi
 
 
+# Update cgroup
+if [ "$RESOURCES_LIMIT" = 'yes' ]; then
+	$BIN/v-update-user-cgroup "$user"
+fi
+
 # Updating admin counter
 # Updating admin counter
 if [ "$user" != "$ROOT_USER" ]; then
 if [ "$user" != "$ROOT_USER" ]; then
 	increase_user_value "$ROOT_USER" '$U_USERS'
 	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
 	if [ "$DISK_QUOTA" != 'unlimited' ]; then
 		is_int_format_valid "$DISK_QUOTA" 'DISK_QUOTA'
 		is_int_format_valid "$DISK_QUOTA" 'DISK_QUOTA'
 	fi
 	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
 	if [ "$BANDWIDTH" != 'unlimited' ]; then
 		is_int_format_valid "$BANDWIDTH" 'BANDWIDTH'
 		is_int_format_valid "$BANDWIDTH" 'BANDWIDTH'
 	fi
 	fi
@@ -130,6 +142,10 @@ RATE_LIMIT='$RATE_LIMIT'
 DATABASES='$DATABASES'
 DATABASES='$DATABASES'
 CRON_JOBS='$CRON_JOBS'
 CRON_JOBS='$CRON_JOBS'
 DISK_QUOTA='$DISK_QUOTA'
 DISK_QUOTA='$DISK_QUOTA'
+CPU_QUOTA='$CPU_QUOTA'
+CPU_QUOTA_PERIOD='$CPU_QUOTA_PERIOD'
+MEMORY_LIMIT='$MEMORY_LIMIT'
+SWAP_LIMIT='$SWAP_LIMIT'
 BANDWIDTH='$BANDWIDTH'
 BANDWIDTH='$BANDWIDTH'
 NS='$NS'
 NS='$NS'
 SHELL='$SHELL'
 SHELL='$SHELL'

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

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

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

@@ -38,6 +38,10 @@ json_list() {
         "DATABASES": "'$DATABASES'",
         "DATABASES": "'$DATABASES'",
         "CRON_JOBS": "'$CRON_JOBS'",
         "CRON_JOBS": "'$CRON_JOBS'",
         "DISK_QUOTA": "'$DISK_QUOTA'",
         "DISK_QUOTA": "'$DISK_QUOTA'",
+        "CPU_QUOTA":"'$CPU_QUOTA'",
+        "CPU_QUOTA_PERIOD":"'$CPU_QUOTA_PERIOD'",
+        "MEMORY_LIMIT":"'$MEMORY_LIMIT'",
+        "SWAP_LIMIT":"'$SWAP_LIMIT'",
         "BANDWIDTH": "'$BANDWIDTH'",
         "BANDWIDTH": "'$BANDWIDTH'",
         "NS": "'$NS'",
         "NS": "'$NS'",
         "SHELL": "'$SHELL'",
         "SHELL": "'$SHELL'",
@@ -66,6 +70,10 @@ shell_list() {
 	echo "DATABASES:        	$DATABASES"
 	echo "DATABASES:        	$DATABASES"
 	echo "CRON JOBS:        	$CRON_JOBS"
 	echo "CRON JOBS:        	$CRON_JOBS"
 	echo "DISK QUOTA:       	$DISK_QUOTA"
 	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 "BANDWIDTH:        	$BANDWIDTH"
 	echo "NS:               	$NS"
 	echo "NS:               	$NS"
 	echo "SHELL:            	$SHELL"
 	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 "$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 "$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 "$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"
 	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 "PACKAGE,WEB_TEMPLATE,BACKEND_TEMPLATE,PROXY_TEMPLATE,DNS_TEMPLATE,"
 	echo -n "WEB_DOMAINS,WEB_ALIASES,DNS_DOMAINS,DNS_RECORDS,"
 	echo -n "WEB_DOMAINS,WEB_ALIASES,DNS_DOMAINS,DNS_RECORDS,"
 	echo -n "MAIL_DOMAINS,MAIL_ACCOUNTS,RATE_LIMIT,DATABASES,CRON_JOBS,"
 	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 "$PACKAGE,$WEB_TEMPLATE,$BACKEND_TEMPLATE,$PROXY_TEMPLATE,$DNS_TEMPLATE,"
 	echo -n "$WEB_DOMAINS,$WEB_ALIASES,$DNS_DOMAINS,$DNS_RECORDS,"
 	echo -n "$WEB_DOMAINS,$WEB_ALIASES,$DNS_DOMAINS,$DNS_RECORDS,"
 	echo -n "$MAIL_DOMAINS,$MAIL_ACCOUNTS,$RATE_LIMIT,$DATABASES,$CRON_JOBS,"
 	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"
 }
 }
 
 
 #----------------------------------------------------------#
 #----------------------------------------------------------#

+ 5 - 0
bin/v-rebuild-user

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

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

@@ -0,0 +1,83 @@
+#!/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" MemoryMax="$memory_limit"
+else
+	systemctl set-property "$user_slice" MemoryMax=
+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                             #
 #                       Action                             #
 #----------------------------------------------------------#
 #----------------------------------------------------------#
-
 for user in $("$BIN/v-list-users" list); do
 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
 	if [ -n "$check_package" ]; then
 		"$BIN/v-change-user-package" "$user" "$package" 'yes'
 		"$BIN/v-change-user-package" "$user" "$package" 'yes'
 	fi
 	fi

+ 28 - 0
func/main.sh

@@ -1150,6 +1150,34 @@ is_cron_format_valid() {
 	fi
 	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() {
 is_object_name_format_valid() {
 	if ! [[ "$1" =~ ^[-|\ |\.|_[:alnum:]]{0,50}$ ]]; then
 	if ! [[ "$1" =~ ^[-|\ |\.|_[:alnum:]]{0,50}$ ]]; then
 		check_result "$E_INVALID" "invalid $2 format :: $1"
 		check_result "$E_INVALID" "invalid $2 format :: $1"

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

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

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

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

+ 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
 	if [ -z "$(grep -e 'SHELL_JAIL_ENABLED' $HESTIA/data/packages/$package)" ]; then
 		echo "SHELL_JAIL_ENABLED='no'" >> $HESTIA/data/packages/$package
 		echo "SHELL_JAIL_ENABLED='no'" >> $HESTIA/data/packages/$package
 	fi
 	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
 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!'
 $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!'

+ 2 - 1
package.json

@@ -15,7 +15,8 @@
 		"lint-staged": "lint-staged",
 		"lint-staged": "lint-staged",
 		"format": "prettier --cache --write .",
 		"format": "prettier --cache --write .",
 		"preinstall": "npx only-allow npm",
 		"preinstall": "npx only-allow npm",
-		"prepare": "husky"
+		"prepare": "husky",
+		"hestia": "cd src && ./hst_autocompile.sh --hestia --install '~localsrc'"
 	},
 	},
 	"dependencies": {
 	"dependencies": {
 		"@fortawesome/fontawesome-free": "^6.5.1",
 		"@fortawesome/fontawesome-free": "^6.5.1",

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

@@ -83,6 +83,20 @@ if (!empty($_POST["ok"])) {
 	if (!isset($_POST["v_ratelimit"])) {
 	if (!isset($_POST["v_ratelimit"])) {
 		$errors[] = _("Rate Limit");
 		$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
 	// Check if name server entries are blank if DNS server is installed
 	if (isset($_SESSION["DNS_SYSTEM"]) && !empty($_SESSION["DNS_SYSTEM"])) {
 	if (isset($_SESSION["DNS_SYSTEM"]) && !empty($_SESSION["DNS_SYSTEM"])) {
 		if (empty($_POST["v_ns1"])) {
 		if (empty($_POST["v_ns1"])) {
@@ -132,6 +146,10 @@ if (!empty($_POST["ok"])) {
 		$v_disk_quota = quoteshellarg($_POST["v_disk_quota"]);
 		$v_disk_quota = quoteshellarg($_POST["v_disk_quota"]);
 		$v_bandwidth = quoteshellarg($_POST["v_bandwidth"]);
 		$v_bandwidth = quoteshellarg($_POST["v_bandwidth"]);
 		$v_ratelimit = quoteshellarg($_POST["v_ratelimit"]);
 		$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_ns1 = !empty($_POST["v_ns1"]) ? trim($_POST["v_ns1"], ".") : "";
 		$v_ns2 = !empty($_POST["v_ns2"]) ? trim($_POST["v_ns2"], ".") : "";
 		$v_ns2 = !empty($_POST["v_ns2"]) ? trim($_POST["v_ns2"], ".") : "";
 		$v_ns3 = !empty($_POST["v_ns3"]) ? trim($_POST["v_ns3"], ".") : "";
 		$v_ns3 = !empty($_POST["v_ns3"]) ? trim($_POST["v_ns3"], ".") : "";
@@ -183,6 +201,10 @@ if (!empty($_POST["ok"])) {
 			$pkg .= "DATABASES=" . $v_databases . "\n";
 			$pkg .= "DATABASES=" . $v_databases . "\n";
 			$pkg .= "CRON_JOBS=" . $v_cron_jobs . "\n";
 			$pkg .= "CRON_JOBS=" . $v_cron_jobs . "\n";
 			$pkg .= "DISK_QUOTA=" . $v_disk_quota . "\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 .= "BANDWIDTH=" . $v_bandwidth . "\n";
 			$pkg .= "RATE_LIMIT=" . $v_ratelimit . "\n";
 			$pkg .= "RATE_LIMIT=" . $v_ratelimit . "\n";
 			$pkg .= "NS=" . $v_ns . "\n";
 			$pkg .= "NS=" . $v_ns . "\n";
@@ -309,6 +331,20 @@ if (empty($v_bandwidth)) {
 if (empty($v_ratelimit)) {
 if (empty($v_ratelimit)) {
 	$v_ratelimit = "'200'";
 	$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)) {
 if (empty($v_ns1)) {
 	$v_ns1 = "ns1.example.tld";
 	$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_bandwidth = $data[$v_package]["BANDWIDTH"];
 $v_shell = $data[$v_package]["SHELL"];
 $v_shell = $data[$v_package]["SHELL"];
 $v_shell_jail_enabled = $data[$v_package]["SHELL_JAIL_ENABLED"];
 $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"];
 $v_ns = $data[$v_package]["NS"];
 $nameservers = explode(",", $v_ns);
 $nameservers = explode(",", $v_ns);
 if (empty($nameservers[0])) {
 if (empty($nameservers[0])) {
@@ -193,6 +197,19 @@ if (!empty($_POST["save"])) {
 		$errors[] = _("Bandwidth");
 		$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
 	// Check if name server entries are blank if DNS server is installed
 	if (isset($_SESSION["DNS_SYSTEM"]) && !empty($_SESSION["DNS_SYSTEM"])) {
 	if (isset($_SESSION["DNS_SYSTEM"]) && !empty($_SESSION["DNS_SYSTEM"])) {
 		if (empty($_POST["v_ns1"])) {
 		if (empty($_POST["v_ns1"])) {
@@ -256,6 +273,10 @@ if (!empty($_POST["save"])) {
 	$v_backups = quoteshellarg($_POST["v_backups"]);
 	$v_backups = quoteshellarg($_POST["v_backups"]);
 	$v_disk_quota = quoteshellarg($_POST["v_disk_quota"]);
 	$v_disk_quota = quoteshellarg($_POST["v_disk_quota"]);
 	$v_bandwidth = quoteshellarg($_POST["v_bandwidth"]);
 	$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_ns1 = !empty($_POST["v_ns1"]) ? trim($_POST["v_ns1"], ".") : "";
 	$v_ns2 = !empty($_POST["v_ns2"]) ? trim($_POST["v_ns2"], ".") : "";
 	$v_ns2 = !empty($_POST["v_ns2"]) ? trim($_POST["v_ns2"], ".") : "";
 	$v_ns3 = !empty($_POST["v_ns3"]) ? trim($_POST["v_ns3"], ".") : "";
 	$v_ns3 = !empty($_POST["v_ns3"]) ? trim($_POST["v_ns3"], ".") : "";
@@ -302,6 +323,10 @@ if (!empty($_POST["save"])) {
 	$pkg .= "DATABASES=" . $v_databases . "\n";
 	$pkg .= "DATABASES=" . $v_databases . "\n";
 	$pkg .= "CRON_JOBS=" . $v_cron_jobs . "\n";
 	$pkg .= "CRON_JOBS=" . $v_cron_jobs . "\n";
 	$pkg .= "DISK_QUOTA=" . $v_disk_quota . "\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 .= "BANDWIDTH=" . $v_bandwidth . "\n";
 	$pkg .= "NS=" . $v_ns . "\n";
 	$pkg .= "NS=" . $v_ns . "\n";
 	$pkg .= "SHELL=" . $v_shell . "\n";
 	$pkg .= "SHELL=" . $v_shell . "\n";

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

@@ -278,6 +278,64 @@
 					</div>
 					</div>
 				</div>
 				</div>
 			</details>
 			</details>
+			<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>
 		</div>
 		</div>
 
 
 	</form>
 	</form>

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

@@ -91,52 +91,56 @@
 						</label>
 						</label>
 						<select class="form-select" name="v_web_template" id="v_web_template">
 						<select class="form-select" name="v_web_template" id="v_web_template">
 							<?php
 							<?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>
 						</select>
 					</div>
 					</div>
-					<?php if (!empty($_SESSION['WEB_BACKEND'])) { echo ""; ?>
+					<?php if (!empty($_SESSION['WEB_BACKEND'])) {
+						echo ""; ?>
 						<div class="u-mb10">
 						<div class="u-mb10">
 							<label for="v_backend_template" class="form-label">
 							<label for="v_backend_template" class="form-label">
 								<?= _("Backend Template") . "<span class='optional'>" . strtoupper($_SESSION["WEB_BACKEND"]) . "</span>" ?>
 								<?= _("Backend Template") . "<span class='optional'>" . strtoupper($_SESSION["WEB_BACKEND"]) . "</span>" ?>
 							</label>
 							</label>
 							<select class="form-select" name="v_backend_template" id="v_backend_template">
 							<select class="form-select" name="v_backend_template" id="v_backend_template">
 								<?php
 								<?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>
 							</select>
 						</div>
 						</div>
-					<?=""; }?>
-					<?php if (!empty($_SESSION['PROXY_SYSTEM'])) { echo ""; ?>
+						<?= "";
+					} ?>
+					<?php if (!empty($_SESSION['PROXY_SYSTEM'])) {
+						echo ""; ?>
 						<div class="u-mb10">
 						<div class="u-mb10">
 							<label for="v_proxy_template" class="form-label">
 							<label for="v_proxy_template" class="form-label">
 								<?= _("Proxy Template") . "<span class='optional'>" . strtoupper($_SESSION["PROXY_SYSTEM"]) . "</span>" ?>
 								<?= _("Proxy Template") . "<span class='optional'>" . strtoupper($_SESSION["PROXY_SYSTEM"]) . "</span>" ?>
 							</label>
 							</label>
 							<select class="form-select" name="v_proxy_template" id="v_proxy_template">
 							<select class="form-select" name="v_proxy_template" id="v_proxy_template">
 								<?php
 								<?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>
 							</select>
 						</div>
 						</div>
-					<?=""; }?>
+						<?= "";
+					} ?>
 				</div>
 				</div>
 			</details>
 			</details>
 			<details class="collapse" id="dns-options">
 			<details class="collapse" id="dns-options">
@@ -150,16 +154,16 @@
 						</label>
 						</label>
 						<select class="form-select" name="v_dns_template" id="v_dns_template">
 						<select class="form-select" name="v_dns_template" id="v_dns_template">
 							<?php
 							<?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>
 						</select>
 					</div>
 					</div>
@@ -276,13 +280,73 @@
 						</select>
 						</select>
 					</div>
 					</div>
 					<div class="form-check u-mb10">
 					<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">
 						<label for="v_shell_jail_enabled">
 							<?= _("Jail User Shell") ?>
 							<?= _("Jail User Shell") ?>
 						</label>
 						</label>
 					</div>
 					</div>
 				</div>
 				</div>
 			</details>
 			</details>
+
+			<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>
 		</div>
 		</div>
 
 
 	</form>
 	</form>