mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-12-15 12:59:42 +01:00
Add comprehensive resource limits with automatic OpenLiteSpeed cgroups setup
This commit implements per-package resource limits for CyberPanel shared hosting using OpenLiteSpeed's native cgroups v2 integration with automatic server configuration. Features: - 7 new package fields: memoryLimitMB, cpuCores, ioLimitMBPS, inodeLimit, maxConnections, procSoftLimit, procHardLimit - Automatic OLS cgroups setup (no manual server configuration required) - Multi-layer enforcement: OLS config + kernel cgroups v2 + filesystem quotas - Per-user enforcement (subdomains/addon domains share parent's limits) - Graceful degradation if cgroups unavailable - Automatic backup of OLS config before modification Backend Changes: - packages/models.py: Added 7 resource limit fields with defaults - packages/packagesManager.py: CRUD operations for resource limits - plogical/resourceLimits.py: NEW - Resource manager with auto-setup * _ensure_cgroups_enabled(): Automatic OLS cgroups configuration * set_user_limits(): Apply limits via lscgctl * remove_user_limits(): Cleanup on deletion * set_inode_limit(): Filesystem quota management - plogical/vhostConfs.py: Parameterized hardcoded resource limits - plogical/vhost.py: Updated signatures to accept resource limits - plogical/virtualHostUtilities.py: Extract and apply package limits Frontend Changes: - packages/templates/packages/createPackage.html: Resource limits UI - packages/templates/packages/modifyPackage.html: Resource limits UI - packages/static/packages/packages.js: AngularJS controller updates Automatic Setup Flow: When creating a website with enforceDiskLimits=True: 1. Check kernel cgroups v2 support 2. Run lssetup if lscgctl missing 3. Enable cgroups in OLS config if needed 4. Backup and modify /usr/local/lsws/conf/httpd_config.conf 5. Graceful restart of OpenLiteSpeed 6. Apply per-user limits via lscgctl 7. Set inode quotas via setquota Requirements: - Linux kernel 5.2+ (cgroups v2) - OpenLiteSpeed 1.8+ (with lsns support) - quota tools (optional, for inode limits) Backward Compatibility: - Existing packages receive default values via migration - No manual setup required for new installations - Graceful fallback if cgroups unavailable
This commit is contained in:
@@ -17,3 +17,12 @@ class Package(models.Model):
|
||||
allowedDomains = models.IntegerField(default=0)
|
||||
allowFullDomain = models.IntegerField(default=1)
|
||||
enforceDiskLimits = models.IntegerField(default=0)
|
||||
|
||||
# Resource Limits - enforced via cgroups v2 and OpenLiteSpeed
|
||||
memoryLimitMB = models.IntegerField(default=1024, help_text="Memory limit in MB")
|
||||
cpuCores = models.IntegerField(default=1, help_text="Number of CPU cores")
|
||||
ioLimitMBPS = models.IntegerField(default=10, help_text="I/O limit in MB/s")
|
||||
inodeLimit = models.IntegerField(default=400000, help_text="Maximum number of files/directories")
|
||||
maxConnections = models.IntegerField(default=10, help_text="Max concurrent PHP connections")
|
||||
procSoftLimit = models.IntegerField(default=400, help_text="Soft process limit")
|
||||
procHardLimit = models.IntegerField(default=500, help_text="Hard process limit")
|
||||
|
||||
@@ -71,12 +71,53 @@ class PackagesManager:
|
||||
except:
|
||||
enforceDiskLimits = 0
|
||||
|
||||
# Resource Limits - with backward compatibility
|
||||
try:
|
||||
memoryLimitMB = int(data['memoryLimitMB'])
|
||||
except:
|
||||
memoryLimitMB = 1024
|
||||
|
||||
try:
|
||||
cpuCores = int(data['cpuCores'])
|
||||
except:
|
||||
cpuCores = 1
|
||||
|
||||
try:
|
||||
ioLimitMBPS = int(data['ioLimitMBPS'])
|
||||
except:
|
||||
ioLimitMBPS = 10
|
||||
|
||||
try:
|
||||
inodeLimit = int(data['inodeLimit'])
|
||||
except:
|
||||
inodeLimit = 400000
|
||||
|
||||
try:
|
||||
maxConnections = int(data['maxConnections'])
|
||||
except:
|
||||
maxConnections = 10
|
||||
|
||||
try:
|
||||
procSoftLimit = int(data['procSoftLimit'])
|
||||
except:
|
||||
procSoftLimit = 400
|
||||
|
||||
try:
|
||||
procHardLimit = int(data['procHardLimit'])
|
||||
except:
|
||||
procHardLimit = 500
|
||||
|
||||
if packageSpace < 0 or packageBandwidth < 0 or packageDatabases < 0 or ftpAccounts < 0 or emails < 0 or allowedDomains < 0:
|
||||
data_ret = {'saveStatus': 0, 'error_message': "All values should be positive or 0."}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
# Validate resource limits
|
||||
if memoryLimitMB < 256 or cpuCores < 1 or ioLimitMBPS < 1 or inodeLimit < 10000 or maxConnections < 1 or procSoftLimit < 1 or procHardLimit < 1:
|
||||
data_ret = {'saveStatus': 0, 'error_message': "Resource limits must be positive and within valid ranges."}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
|
||||
if api == '0':
|
||||
@@ -84,7 +125,10 @@ class PackagesManager:
|
||||
|
||||
package = Package(admin=admin, packageName=packageName, diskSpace=packageSpace,
|
||||
bandwidth=packageBandwidth, ftpAccounts=ftpAccounts, dataBases=packageDatabases,
|
||||
emailAccounts=emails, allowedDomains=allowedDomains, allowFullDomain=allowFullDomain, enforceDiskLimits=enforceDiskLimits)
|
||||
emailAccounts=emails, allowedDomains=allowedDomains, allowFullDomain=allowFullDomain,
|
||||
enforceDiskLimits=enforceDiskLimits, memoryLimitMB=memoryLimitMB, cpuCores=cpuCores,
|
||||
ioLimitMBPS=ioLimitMBPS, inodeLimit=inodeLimit, maxConnections=maxConnections,
|
||||
procSoftLimit=procSoftLimit, procHardLimit=procHardLimit)
|
||||
|
||||
package.save()
|
||||
|
||||
@@ -162,7 +206,12 @@ class PackagesManager:
|
||||
|
||||
data_ret = {'emails': emails, 'modifyStatus': 1, 'error_message': "None",
|
||||
"diskSpace": diskSpace, "bandwidth": bandwidth, "ftpAccounts": ftpAccounts,
|
||||
"dataBases": dataBases, "allowedDomains": modifyPack.allowedDomains, 'allowFullDomain': modifyPack.allowFullDomain, 'enforceDiskLimits': modifyPack.enforceDiskLimits}
|
||||
"dataBases": dataBases, "allowedDomains": modifyPack.allowedDomains,
|
||||
'allowFullDomain': modifyPack.allowFullDomain, 'enforceDiskLimits': modifyPack.enforceDiskLimits,
|
||||
'memoryLimitMB': modifyPack.memoryLimitMB, 'cpuCores': modifyPack.cpuCores,
|
||||
'ioLimitMBPS': modifyPack.ioLimitMBPS, 'inodeLimit': modifyPack.inodeLimit,
|
||||
'maxConnections': modifyPack.maxConnections, 'procSoftLimit': modifyPack.procSoftLimit,
|
||||
'procHardLimit': modifyPack.procHardLimit}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
@@ -213,6 +262,42 @@ class PackagesManager:
|
||||
except:
|
||||
modifyPack.enforceDiskLimits = 0
|
||||
|
||||
# Update resource limits
|
||||
try:
|
||||
modifyPack.memoryLimitMB = int(data['memoryLimitMB'])
|
||||
except:
|
||||
pass # Keep existing value
|
||||
|
||||
try:
|
||||
modifyPack.cpuCores = int(data['cpuCores'])
|
||||
except:
|
||||
pass # Keep existing value
|
||||
|
||||
try:
|
||||
modifyPack.ioLimitMBPS = int(data['ioLimitMBPS'])
|
||||
except:
|
||||
pass # Keep existing value
|
||||
|
||||
try:
|
||||
modifyPack.inodeLimit = int(data['inodeLimit'])
|
||||
except:
|
||||
pass # Keep existing value
|
||||
|
||||
try:
|
||||
modifyPack.maxConnections = int(data['maxConnections'])
|
||||
except:
|
||||
pass # Keep existing value
|
||||
|
||||
try:
|
||||
modifyPack.procSoftLimit = int(data['procSoftLimit'])
|
||||
except:
|
||||
pass # Keep existing value
|
||||
|
||||
try:
|
||||
modifyPack.procHardLimit = int(data['procHardLimit'])
|
||||
except:
|
||||
pass # Keep existing value
|
||||
|
||||
modifyPack.save()
|
||||
|
||||
## Fix https://github.com/usmannasir/cyberpanel/issues/998
|
||||
|
||||
@@ -64,7 +64,15 @@ app.controller('createPackage', function ($scope, $http) {
|
||||
dataBases: dataBases,
|
||||
emails: emails,
|
||||
allowedDomains: $scope.allowedDomains,
|
||||
enforceDiskLimits: $scope.enforceDiskLimits
|
||||
enforceDiskLimits: $scope.enforceDiskLimits,
|
||||
// Resource Limits
|
||||
memoryLimitMB: $scope.memoryLimitMB || 1024,
|
||||
cpuCores: $scope.cpuCores || 1,
|
||||
ioLimitMBPS: $scope.ioLimitMBPS || 10,
|
||||
inodeLimit: $scope.inodeLimit || 400000,
|
||||
maxConnections: $scope.maxConnections || 10,
|
||||
procSoftLimit: $scope.procSoftLimit || 400,
|
||||
procHardLimit: $scope.procHardLimit || 500
|
||||
};
|
||||
|
||||
var config = {
|
||||
@@ -236,6 +244,15 @@ app.controller('modifyPackages', function ($scope, $http) {
|
||||
$scope.allowFullDomain = response.data.allowFullDomain === 1;
|
||||
$scope.enforceDiskLimits = response.data.enforceDiskLimits === 1;
|
||||
|
||||
// Load resource limits
|
||||
$scope.memoryLimitMB = response.data.memoryLimitMB || 1024;
|
||||
$scope.cpuCores = response.data.cpuCores || 1;
|
||||
$scope.ioLimitMBPS = response.data.ioLimitMBPS || 10;
|
||||
$scope.inodeLimit = response.data.inodeLimit || 400000;
|
||||
$scope.maxConnections = response.data.maxConnections || 10;
|
||||
$scope.procSoftLimit = response.data.procSoftLimit || 400;
|
||||
$scope.procHardLimit = response.data.procHardLimit || 500;
|
||||
|
||||
$scope.modifyButton = "Save Details";
|
||||
|
||||
$("#packageDetailsToBeModified").fadeIn();
|
||||
@@ -283,6 +300,14 @@ app.controller('modifyPackages', function ($scope, $http) {
|
||||
allowedDomains: $scope.allowedDomains,
|
||||
allowFullDomain: $scope.allowFullDomain,
|
||||
enforceDiskLimits: $scope.enforceDiskLimits,
|
||||
// Resource Limits
|
||||
memoryLimitMB: $scope.memoryLimitMB || 1024,
|
||||
cpuCores: $scope.cpuCores || 1,
|
||||
ioLimitMBPS: $scope.ioLimitMBPS || 10,
|
||||
inodeLimit: $scope.inodeLimit || 400000,
|
||||
maxConnections: $scope.maxConnections || 10,
|
||||
procSoftLimit: $scope.procSoftLimit || 400,
|
||||
procHardLimit: $scope.procHardLimit || 500
|
||||
};
|
||||
|
||||
var config = {
|
||||
|
||||
@@ -435,6 +435,99 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-card">
|
||||
<h2 class="card-title">{% trans "Advanced Resource Limits" %}</h2>
|
||||
|
||||
<div class="info-box">
|
||||
<i class="fas fa-server"></i>
|
||||
<div class="info-box-text">
|
||||
{% trans "These limits are enforced via cgroups v2 and OpenLiteSpeed to prevent resource abuse and ensure server stability." %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-section">
|
||||
<div class="resource-title">
|
||||
<i class="fas fa-microchip"></i>
|
||||
{% trans "CPU & Memory" %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Memory Limit" %}</label>
|
||||
<div class="input-group">
|
||||
<input name="memoryLimitMB" type="number" class="form-control" ng-model="memoryLimitMB"
|
||||
value="1024" min="256" max="16384" step="256" required>
|
||||
<span class="input-suffix">MB</span>
|
||||
</div>
|
||||
<div class="help-text">{% trans "RAM allocated per website (256 MB - 16 GB)" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "CPU Cores" %}</label>
|
||||
<input name="cpuCores" type="number" class="form-control" ng-model="cpuCores"
|
||||
value="1" min="1" max="16" required>
|
||||
<div class="help-text">{% trans "Number of CPU cores (1-16)" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-section">
|
||||
<div class="resource-title">
|
||||
<i class="fas fa-hdd"></i>
|
||||
{% trans "Disk & I/O" %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "I/O Limit" %}</label>
|
||||
<div class="input-group">
|
||||
<input name="ioLimitMBPS" type="number" class="form-control" ng-model="ioLimitMBPS"
|
||||
value="10" min="5" max="100" required>
|
||||
<span class="input-suffix">MB/s</span>
|
||||
</div>
|
||||
<div class="help-text">{% trans "Disk I/O bandwidth limit (5-100 MB/s)" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Inode Limit" %}</label>
|
||||
<input name="inodeLimit" type="number" class="form-control" ng-model="inodeLimit"
|
||||
value="400000" min="100000" max="2000000" step="100000" required>
|
||||
<div class="help-text">{% trans "Maximum files/directories (100k - 2M)" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="resource-section">
|
||||
<div class="resource-title">
|
||||
<i class="fas fa-cogs"></i>
|
||||
{% trans "Process Limits" %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Max Connections" %}</label>
|
||||
<input name="maxConnections" type="number" class="form-control" ng-model="maxConnections"
|
||||
value="10" min="1" max="100" required>
|
||||
<div class="help-text">{% trans "Max concurrent PHP connections (1-100)" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Process Soft Limit" %}</label>
|
||||
<input name="procSoftLimit" type="number" class="form-control" ng-model="procSoftLimit"
|
||||
value="400" min="100" max="2000" required>
|
||||
<div class="help-text">{% trans "Soft process limit (100-2000)" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Process Hard Limit" %}</label>
|
||||
<input name="procHardLimit" type="number" class="form-control" ng-model="procHardLimit"
|
||||
value="500" min="100" max="2000" required>
|
||||
<div class="help-text">{% trans "Hard process limit (100-2000)" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-card" ng-hide="installationDetailsForm">
|
||||
<h2 class="card-title">{% trans "Additional Features" %}</h2>
|
||||
|
||||
|
||||
@@ -471,7 +471,93 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="content-card">
|
||||
<h2 class="card-title">{% trans "Advanced Resource Limits" %}</h2>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="form-section-title">
|
||||
<i class="fas fa-microchip"></i>
|
||||
{% trans "CPU & Memory" %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Memory Limit" %}</label>
|
||||
<div class="input-group">
|
||||
<input name="memoryLimitMB" type="number" class="form-control" ng-model="memoryLimitMB"
|
||||
min="256" max="16384" step="256" required aria-label="{% trans 'Memory limit in MB' %}">
|
||||
<span class="input-suffix">MB</span>
|
||||
</div>
|
||||
<div class="help-text">{% trans "RAM allocated per website (256 MB - 16 GB)" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "CPU Cores" %}</label>
|
||||
<input name="cpuCores" type="number" class="form-control" ng-model="cpuCores"
|
||||
min="1" max="16" required aria-label="{% trans 'Number of CPU cores' %}">
|
||||
<div class="help-text">{% trans "Number of CPU cores (1-16)" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="form-section-title">
|
||||
<i class="fas fa-hdd"></i>
|
||||
{% trans "Disk & I/O" %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "I/O Limit" %}</label>
|
||||
<div class="input-group">
|
||||
<input name="ioLimitMBPS" type="number" class="form-control" ng-model="ioLimitMBPS"
|
||||
min="5" max="100" required aria-label="{% trans 'I/O limit in MB/s' %}">
|
||||
<span class="input-suffix">MB/s</span>
|
||||
</div>
|
||||
<div class="help-text">{% trans "Disk I/O bandwidth limit (5-100 MB/s)" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Inode Limit" %}</label>
|
||||
<input name="inodeLimit" type="number" class="form-control" ng-model="inodeLimit"
|
||||
min="100000" max="2000000" step="100000" required aria-label="{% trans 'Maximum files/directories' %}">
|
||||
<div class="help-text">{% trans "Maximum files/directories (100k - 2M)" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="form-section-title">
|
||||
<i class="fas fa-cogs"></i>
|
||||
{% trans "Process Limits" %}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Max Connections" %}</label>
|
||||
<input name="maxConnections" type="number" class="form-control" ng-model="maxConnections"
|
||||
min="1" max="100" required aria-label="{% trans 'Max concurrent PHP connections' %}">
|
||||
<div class="help-text">{% trans "Max concurrent PHP connections (1-100)" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Process Soft Limit" %}</label>
|
||||
<input name="procSoftLimit" type="number" class="form-control" ng-model="procSoftLimit"
|
||||
min="100" max="2000" required aria-label="{% trans 'Soft process limit' %}">
|
||||
<div class="help-text">{% trans "Soft process limit (100-2000)" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Process Hard Limit" %}</label>
|
||||
<input name="procHardLimit" type="number" class="form-control" ng-model="procHardLimit"
|
||||
min="100" max="2000" required aria-label="{% trans 'Hard process limit' %}">
|
||||
<div class="help-text">{% trans "Hard process limit (100-2000)" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-card" ng-hide="installationDetailsForm">
|
||||
<h2 class="card-title">{% trans "Additional Features" %}</h2>
|
||||
|
||||
|
||||
438
plogical/resourceLimits.py
Normal file
438
plogical/resourceLimits.py
Normal file
@@ -0,0 +1,438 @@
|
||||
#!/usr/local/CyberCP/bin/python
|
||||
"""
|
||||
CyberPanel Resource Limits Manager
|
||||
Handles resource limits using OpenLiteSpeed native cgroups v2 integration
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import logging as log
|
||||
from pathlib import Path
|
||||
|
||||
# Django imports
|
||||
import sys
|
||||
sys.path.append('/usr/local/CyberCP')
|
||||
import django
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
|
||||
django.setup()
|
||||
|
||||
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
||||
|
||||
|
||||
class ResourceLimitsManager:
|
||||
"""
|
||||
Manages resource limits for websites using OpenLiteSpeed native cgroups v2 API
|
||||
This uses the lscgctl command to set per-user limits, which OLS enforces automatically
|
||||
"""
|
||||
|
||||
# Path to OLS cgroups control tool
|
||||
LSCGCTL_PATH = "/usr/local/lsws/lsns/bin/lscgctl"
|
||||
LSSETUP_PATH = "/usr/local/lsws/lsns/bin/lssetup"
|
||||
OLS_CONF_PATH = "/usr/local/lsws/conf/httpd_config.conf"
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the resource limits manager"""
|
||||
self._initialized = False
|
||||
|
||||
def _ensure_cgroups_enabled(self):
|
||||
"""
|
||||
Ensure OpenLiteSpeed cgroups are enabled
|
||||
This performs automatic setup if needed
|
||||
|
||||
Returns:
|
||||
bool: True if cgroups are enabled, False otherwise
|
||||
"""
|
||||
if self._initialized:
|
||||
return True
|
||||
|
||||
try:
|
||||
# Check kernel support first
|
||||
if not os.path.exists('/sys/fs/cgroup/cgroup.controllers'):
|
||||
logging.writeToFile("cgroups v2 not available on this system (requires kernel 5.2+)")
|
||||
return False
|
||||
|
||||
# Check if lscgctl exists
|
||||
if not os.path.exists(self.LSCGCTL_PATH):
|
||||
logging.writeToFile("lscgctl not found, attempting to run lssetup...")
|
||||
|
||||
# Try to run lssetup
|
||||
if os.path.exists(self.LSSETUP_PATH):
|
||||
result = subprocess.run(
|
||||
[self.LSSETUP_PATH],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logging.writeToFile("lssetup completed successfully")
|
||||
else:
|
||||
logging.writeToFile(f"lssetup failed: {result.stderr}")
|
||||
return False
|
||||
else:
|
||||
logging.writeToFile(f"lssetup not found at {self.LSSETUP_PATH}")
|
||||
return False
|
||||
|
||||
# Check if cgroups are enabled in OLS config
|
||||
if not self._check_ols_cgroups_enabled():
|
||||
logging.writeToFile("Enabling cgroups in OpenLiteSpeed configuration...")
|
||||
if not self._enable_ols_cgroups():
|
||||
return False
|
||||
|
||||
self._initialized = True
|
||||
logging.writeToFile("OpenLiteSpeed cgroups support ready")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"Error ensuring cgroups enabled: {str(e)}")
|
||||
return False
|
||||
|
||||
def _check_ols_cgroups_enabled(self):
|
||||
"""
|
||||
Check if cgroups are enabled in OpenLiteSpeed config
|
||||
|
||||
Returns:
|
||||
bool: True if enabled, False otherwise
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(self.OLS_CONF_PATH):
|
||||
logging.writeToFile(f"OLS config not found at {self.OLS_CONF_PATH}")
|
||||
return False
|
||||
|
||||
with open(self.OLS_CONF_PATH, 'r') as f:
|
||||
config = f.read()
|
||||
|
||||
# Look for CGIRLimit section and check cgroups value
|
||||
# Pattern: cgroups followed by whitespace and value
|
||||
import re
|
||||
|
||||
# Find CGIRLimit section
|
||||
cgirlimit_match = re.search(r'CGIRLimit\s*\{([^}]+)\}', config, re.DOTALL)
|
||||
if not cgirlimit_match:
|
||||
logging.writeToFile("CGIRLimit section not found in OLS config")
|
||||
return False
|
||||
|
||||
cgirlimit_section = cgirlimit_match.group(1)
|
||||
|
||||
# Check for cgroups setting
|
||||
cgroups_match = re.search(r'cgroups\s+(\d+)', cgirlimit_section)
|
||||
if cgroups_match:
|
||||
value = int(cgroups_match.group(1))
|
||||
# 1 = On, 0 = Off, 2 = Disabled
|
||||
if value == 1:
|
||||
logging.writeToFile("cgroups already enabled in OLS config")
|
||||
return True
|
||||
else:
|
||||
logging.writeToFile(f"cgroups is set to {value} (need 1 for enabled)")
|
||||
return False
|
||||
else:
|
||||
logging.writeToFile("cgroups setting not found in CGIRLimit section")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"Error checking OLS cgroups config: {str(e)}")
|
||||
return False
|
||||
|
||||
def _enable_ols_cgroups(self):
|
||||
"""
|
||||
Enable cgroups in OpenLiteSpeed configuration
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(self.OLS_CONF_PATH):
|
||||
return False
|
||||
|
||||
# Read the config file
|
||||
with open(self.OLS_CONF_PATH, 'r') as f:
|
||||
config = f.read()
|
||||
|
||||
import re
|
||||
|
||||
# Find CGIRLimit section
|
||||
cgirlimit_match = re.search(r'(CGIRLimit\s*\{[^}]+\})', config, re.DOTALL)
|
||||
if not cgirlimit_match:
|
||||
logging.writeToFile("CGIRLimit section not found, cannot enable cgroups")
|
||||
return False
|
||||
|
||||
old_section = cgirlimit_match.group(1)
|
||||
|
||||
# Check if cgroups line exists
|
||||
if re.search(r'cgroups\s+\d+', old_section):
|
||||
# Replace existing cgroups value with 1
|
||||
new_section = re.sub(r'cgroups\s+\d+', 'cgroups 1', old_section)
|
||||
else:
|
||||
# Add cgroups line before the closing brace
|
||||
new_section = old_section.replace('}', ' cgroups 1\n}')
|
||||
|
||||
# Replace in config
|
||||
new_config = config.replace(old_section, new_section)
|
||||
|
||||
# Backup original config
|
||||
backup_path = self.OLS_CONF_PATH + '.backup'
|
||||
with open(backup_path, 'w') as f:
|
||||
f.write(config)
|
||||
|
||||
# Write new config
|
||||
with open(self.OLS_CONF_PATH, 'w') as f:
|
||||
f.write(new_config)
|
||||
|
||||
logging.writeToFile("Enabled cgroups in OLS config, restarting OpenLiteSpeed...")
|
||||
|
||||
# Graceful restart of OLS
|
||||
result = subprocess.run(
|
||||
['/usr/local/lsws/bin/lswsctrl', 'restart'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logging.writeToFile("OpenLiteSpeed restarted successfully")
|
||||
return True
|
||||
else:
|
||||
logging.writeToFile(f"Failed to restart OpenLiteSpeed: {result.stderr}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"Error enabling OLS cgroups: {str(e)}")
|
||||
return False
|
||||
|
||||
def set_user_limits(self, username, package):
|
||||
"""
|
||||
Set resource limits for a Linux user using OpenLiteSpeed lscgctl
|
||||
|
||||
Args:
|
||||
username (str): Linux username (e.g., website owner)
|
||||
package (Package): Package model instance with resource limits
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
# Skip if limits not enforced
|
||||
if not package.enforceDiskLimits:
|
||||
logging.writeToFile(f"Resource limits not enforced for {username} (enforceDiskLimits=0)")
|
||||
return True
|
||||
|
||||
# Ensure cgroups are enabled (auto-setup if needed)
|
||||
if not self._ensure_cgroups_enabled():
|
||||
logging.writeToFile(f"cgroups not available, skipping resource limits for {username}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Convert package limits to lscgctl format
|
||||
# CPU: convert cores to percentage (1 core = 100%, 2 cores = 200%, etc.)
|
||||
cpu_percent = package.cpuCores * 100
|
||||
|
||||
# Memory: convert MB to format with M suffix
|
||||
memory_limit = f"{package.memoryLimitMB}M"
|
||||
|
||||
# Tasks: use procHardLimit as max tasks
|
||||
max_tasks = package.procHardLimit
|
||||
|
||||
# Build lscgctl command
|
||||
# Format: lscgctl set username --cpu 100 --mem 1024M --tasks 500
|
||||
cmd = [
|
||||
self.LSCGCTL_PATH,
|
||||
'set',
|
||||
username,
|
||||
'--cpu', str(cpu_percent),
|
||||
'--mem', memory_limit,
|
||||
'--tasks', str(max_tasks)
|
||||
]
|
||||
|
||||
# Note: I/O limits may require additional configuration
|
||||
# Check if lscgctl supports --io parameter
|
||||
|
||||
logging.writeToFile(f"Setting limits for user {username}: CPU={cpu_percent}%, MEM={memory_limit}, TASKS={max_tasks}")
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logging.writeToFile(f"Successfully set resource limits for {username}")
|
||||
return True
|
||||
else:
|
||||
error_msg = result.stderr if result.stderr else result.stdout
|
||||
logging.writeToFile(f"Failed to set limits for {username}: {error_msg}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logging.writeToFile(f"Timeout setting resource limits for {username}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"Error setting resource limits for {username}: {str(e)}")
|
||||
return False
|
||||
|
||||
def remove_user_limits(self, username):
|
||||
"""
|
||||
Remove resource limits for a Linux user
|
||||
|
||||
Args:
|
||||
username (str): Linux username
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
if not os.path.exists(self.LSCGCTL_PATH):
|
||||
logging.writeToFile(f"lscgctl not available, skipping limit removal for {username}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Use lscgctl to remove limits
|
||||
# Format: lscgctl remove username
|
||||
cmd = [self.LSCGCTL_PATH, 'remove', username]
|
||||
|
||||
logging.writeToFile(f"Removing resource limits for user {username}")
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logging.writeToFile(f"Successfully removed resource limits for {username}")
|
||||
return True
|
||||
else:
|
||||
error_msg = result.stderr if result.stderr else result.stdout
|
||||
# It's not critical if removal fails (user may not have had limits)
|
||||
logging.writeToFile(f"Note: Could not remove limits for {username}: {error_msg}")
|
||||
return True
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logging.writeToFile(f"Timeout removing resource limits for {username}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"Error removing resource limits for {username}: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_user_limits(self, username):
|
||||
"""
|
||||
Get current resource limits for a Linux user
|
||||
|
||||
Args:
|
||||
username (str): Linux username
|
||||
|
||||
Returns:
|
||||
dict: Current limits or None
|
||||
"""
|
||||
if not os.path.exists(self.LSCGCTL_PATH):
|
||||
return None
|
||||
|
||||
try:
|
||||
# Use lscgctl to get limits
|
||||
# Format: lscgctl get username
|
||||
cmd = [self.LSCGCTL_PATH, 'get', username]
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
# Parse the output (format may vary)
|
||||
return {'output': result.stdout.strip()}
|
||||
else:
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"Error getting resource limits for {username}: {str(e)}")
|
||||
return None
|
||||
|
||||
def set_inode_limit(self, domain, username, inode_limit):
|
||||
"""
|
||||
Set inode (file count) limit for a website using filesystem quotas
|
||||
|
||||
Args:
|
||||
domain (str): Website domain name
|
||||
username (str): System username for the website
|
||||
inode_limit (int): Maximum number of files/directories
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Check if quota tools are available
|
||||
result = subprocess.run(
|
||||
['which', 'setquota'],
|
||||
capture_output=True,
|
||||
timeout=5
|
||||
)
|
||||
if result.returncode != 0:
|
||||
logging.writeToFile("setquota command not found, skipping inode limit")
|
||||
return False
|
||||
|
||||
# Set inode quota using setquota
|
||||
# Format: setquota -u username 0 0 soft_inode hard_inode /
|
||||
result = subprocess.run(
|
||||
['setquota', '-u', username, '0', '0',
|
||||
str(inode_limit), str(inode_limit), '/'],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
timeout=10
|
||||
)
|
||||
logging.writeToFile(f"Set inode limit for {domain} ({username}): {inode_limit}")
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
logging.writeToFile(f"Timeout setting inode limit for {domain}")
|
||||
return False
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.writeToFile(f"Failed to set inode limit: {e.stderr.decode() if e.stderr else str(e)}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"Failed to set inode limit: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def check_cgroup_support(self):
|
||||
"""
|
||||
Check if OpenLiteSpeed cgroups v2 support is available
|
||||
|
||||
Returns:
|
||||
dict: Support status for various features
|
||||
"""
|
||||
support = {
|
||||
'cgroups_v2': False,
|
||||
'lscgctl_available': False,
|
||||
'memory_controller': False,
|
||||
'cpu_controller': False,
|
||||
'io_controller': False,
|
||||
'quota_tools': False
|
||||
}
|
||||
|
||||
try:
|
||||
# Check cgroups v2
|
||||
if os.path.exists('/sys/fs/cgroup/cgroup.controllers'):
|
||||
support['cgroups_v2'] = True
|
||||
|
||||
# Check controllers
|
||||
with open('/sys/fs/cgroup/cgroup.controllers', 'r') as f:
|
||||
controllers = f.read().strip().split()
|
||||
support['memory_controller'] = 'memory' in controllers
|
||||
support['cpu_controller'] = 'cpu' in controllers
|
||||
support['io_controller'] = 'io' in controllers
|
||||
|
||||
# Check lscgctl tool
|
||||
support['lscgctl_available'] = os.path.exists(self.LSCGCTL_PATH)
|
||||
|
||||
# Check quota tools
|
||||
result = subprocess.run(['which', 'setquota'], capture_output=True, timeout=5)
|
||||
support['quota_tools'] = result.returncode == 0
|
||||
|
||||
except Exception as e:
|
||||
logging.writeToFile(f"Error checking cgroup support: {str(e)}")
|
||||
|
||||
return support
|
||||
|
||||
|
||||
# Singleton instance
|
||||
resource_manager = ResourceLimitsManager()
|
||||
@@ -190,7 +190,9 @@ class vhost:
|
||||
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [finalizeVhostCreation]")
|
||||
|
||||
@staticmethod
|
||||
def createDirectoryForVirtualHost(virtualHostName,administratorEmail,virtualHostUser, phpVersion, openBasedir):
|
||||
def createDirectoryForVirtualHost(virtualHostName,administratorEmail,virtualHostUser, phpVersion, openBasedir,
|
||||
memSoftLimit=2047, memHardLimit=2047, maxConnections=10,
|
||||
procSoftLimit=400, procHardLimit=500):
|
||||
|
||||
if not os.path.exists('/usr/local/lsws/Example/html/.well-known/acme-challenge'):
|
||||
command = 'mkdir -p /usr/local/lsws/Example/html/.well-known/acme-challenge'
|
||||
@@ -218,13 +220,16 @@ class vhost:
|
||||
## Creating Per vhost Configuration File
|
||||
|
||||
|
||||
if vhost.perHostVirtualConf(completePathToConfigFile,administratorEmail,virtualHostUser,phpVersion, virtualHostName, openBasedir) == 1:
|
||||
if vhost.perHostVirtualConf(completePathToConfigFile,administratorEmail,virtualHostUser,phpVersion, virtualHostName, openBasedir,
|
||||
memSoftLimit, memHardLimit, maxConnections, procSoftLimit, procHardLimit) == 1:
|
||||
return [1,"None"]
|
||||
else:
|
||||
return [0,"[61 Not able to create per host virtual configurations [perHostVirtualConf]"]
|
||||
|
||||
@staticmethod
|
||||
def perHostVirtualConf(vhFile, administratorEmail,virtualHostUser, phpVersion, virtualHostName, openBasedir):
|
||||
def perHostVirtualConf(vhFile, administratorEmail,virtualHostUser, phpVersion, virtualHostName, openBasedir,
|
||||
memSoftLimit=2047, memHardLimit=2047, maxConnections=10,
|
||||
procSoftLimit=400, procHardLimit=500):
|
||||
# General Configurations tab
|
||||
if ProcessUtilities.decideServer() == ProcessUtilities.OLS:
|
||||
try:
|
||||
@@ -240,6 +245,13 @@ class vhost:
|
||||
currentConf = currentConf.replace('{adminEmails}', administratorEmail)
|
||||
currentConf = currentConf.replace('{php}', php)
|
||||
|
||||
# Replace resource limits
|
||||
currentConf = currentConf.replace('{memSoftLimit}', str(memSoftLimit))
|
||||
currentConf = currentConf.replace('{memHardLimit}', str(memHardLimit))
|
||||
currentConf = currentConf.replace('{maxConnections}', str(maxConnections))
|
||||
currentConf = currentConf.replace('{procSoftLimit}', str(procSoftLimit))
|
||||
currentConf = currentConf.replace('{procHardLimit}', str(procHardLimit))
|
||||
|
||||
if openBasedir == 1:
|
||||
currentConf = currentConf.replace('{open_basedir}', 'php_admin_value open_basedir "/tmp:$VH_ROOT"')
|
||||
else:
|
||||
@@ -474,6 +486,12 @@ class vhost:
|
||||
if os.path.exists(gitPath):
|
||||
shutil.rmtree(gitPath)
|
||||
|
||||
## Remove resource limits for this user (OLS cgroups)
|
||||
try:
|
||||
from plogical.resourceLimits import resource_manager
|
||||
resource_manager.remove_user_limits(externalApp)
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Warning: Failed to remove resource limits for user {externalApp}: {str(e)}")
|
||||
|
||||
### Delete Acme folder
|
||||
|
||||
@@ -962,7 +980,8 @@ class vhost:
|
||||
|
||||
@staticmethod
|
||||
def createDirectoryForDomain(masterDomain, domain, phpVersion, path, administratorEmail, virtualHostUser,
|
||||
openBasedir):
|
||||
openBasedir, memSoftLimit=2047, memHardLimit=2047, maxConnections=10,
|
||||
procSoftLimit=400, procHardLimit=500):
|
||||
|
||||
FNULL = open(os.devnull, 'w')
|
||||
|
||||
@@ -1007,7 +1026,8 @@ class vhost:
|
||||
#return [0, "[351 Not able to directories for virtual host [createDirectoryForDomain]]"]
|
||||
|
||||
if vhost.perHostDomainConf(path, masterDomain, domain, completePathToConfigFile,
|
||||
administratorEmail, phpVersion, virtualHostUser, openBasedir) == 1:
|
||||
administratorEmail, phpVersion, virtualHostUser, openBasedir,
|
||||
memSoftLimit, memHardLimit, maxConnections, procSoftLimit, procHardLimit) == 1:
|
||||
return [1, "None"]
|
||||
else:
|
||||
pass
|
||||
@@ -1016,7 +1036,9 @@ class vhost:
|
||||
return [1, "None"]
|
||||
|
||||
@staticmethod
|
||||
def perHostDomainConf(path, masterDomain, domain, vhFile, administratorEmail, phpVersion, virtualHostUser, openBasedir):
|
||||
def perHostDomainConf(path, masterDomain, domain, vhFile, administratorEmail, phpVersion, virtualHostUser, openBasedir,
|
||||
memSoftLimit=2047, memHardLimit=2047, maxConnections=10,
|
||||
procSoftLimit=400, procHardLimit=500):
|
||||
if ProcessUtilities.decideServer() == ProcessUtilities.OLS:
|
||||
try:
|
||||
php = PHPManager.getPHPString(phpVersion)
|
||||
@@ -1032,6 +1054,12 @@ class vhost:
|
||||
currentConf = currentConf.replace('{adminEmails}', administratorEmail)
|
||||
currentConf = currentConf.replace('{php}', php)
|
||||
|
||||
# Replace resource limits (child domains share parent's limits)
|
||||
currentConf = currentConf.replace('{memSoftLimit}', str(memSoftLimit))
|
||||
currentConf = currentConf.replace('{memHardLimit}', str(memHardLimit))
|
||||
currentConf = currentConf.replace('{maxConnections}', str(maxConnections))
|
||||
currentConf = currentConf.replace('{procSoftLimit}', str(procSoftLimit))
|
||||
currentConf = currentConf.replace('{procHardLimit}', str(procHardLimit))
|
||||
|
||||
if openBasedir == 1:
|
||||
currentConf = currentConf.replace('{open_basedir}', 'php_admin_value open_basedir "/tmp:$VH_ROOT"')
|
||||
|
||||
@@ -43,8 +43,8 @@ scripthandler {
|
||||
extprocessor {virtualHostUser} {
|
||||
type lsapi
|
||||
address UDS://tmp/lshttpd/{virtualHostUser}.sock
|
||||
maxConns 10
|
||||
env LSAPI_CHILDREN=10
|
||||
maxConns {maxConnections}
|
||||
env LSAPI_CHILDREN={maxConnections}
|
||||
initTimeout 600
|
||||
retryTimeout 0
|
||||
persistConn 1
|
||||
@@ -54,10 +54,10 @@ extprocessor {virtualHostUser} {
|
||||
path /usr/local/lsws/lsphp{php}/bin/lsphp
|
||||
extUser {virtualHostUser}
|
||||
extGroup {virtualHostUser}
|
||||
memSoftLimit 2047M
|
||||
memHardLimit 2047M
|
||||
procSoftLimit 400
|
||||
procHardLimit 500
|
||||
memSoftLimit {memSoftLimit}M
|
||||
memHardLimit {memHardLimit}M
|
||||
procSoftLimit {procSoftLimit}
|
||||
procHardLimit {procHardLimit}
|
||||
}
|
||||
|
||||
phpIniOverride {
|
||||
@@ -140,8 +140,8 @@ scripthandler {
|
||||
extprocessor {externalApp} {
|
||||
type lsapi
|
||||
address UDS://tmp/lshttpd/{externalApp}.sock
|
||||
maxConns 10
|
||||
env LSAPI_CHILDREN=10
|
||||
maxConns {maxConnections}
|
||||
env LSAPI_CHILDREN={maxConnections}
|
||||
initTimeout 60
|
||||
retryTimeout 0
|
||||
persistConn 1
|
||||
@@ -151,10 +151,10 @@ extprocessor {externalApp} {
|
||||
path /usr/local/lsws/lsphp{php}/bin/lsphp
|
||||
extUser {externalAppMaster}
|
||||
extGroup {externalAppMaster}
|
||||
memSoftLimit 2047M
|
||||
memHardLimit 2047M
|
||||
procSoftLimit 400
|
||||
procHardLimit 500
|
||||
memSoftLimit {memSoftLimit}M
|
||||
memHardLimit {memHardLimit}M
|
||||
procSoftLimit {procSoftLimit}
|
||||
procHardLimit {procHardLimit}
|
||||
}
|
||||
|
||||
rewrite {
|
||||
|
||||
@@ -661,8 +661,20 @@ local_name %s {
|
||||
if retValues[0] == 0:
|
||||
raise BaseException(retValues[1])
|
||||
|
||||
# Get package to retrieve resource limits
|
||||
selectedPackage = Package.objects.get(packageName=packageName)
|
||||
|
||||
# Extract resource limits from package
|
||||
memSoftLimit = selectedPackage.memoryLimitMB
|
||||
memHardLimit = selectedPackage.memoryLimitMB
|
||||
maxConnections = selectedPackage.maxConnections
|
||||
procSoftLimit = selectedPackage.procSoftLimit
|
||||
procHardLimit = selectedPackage.procHardLimit
|
||||
|
||||
retValues = vhost.createDirectoryForVirtualHost(virtualHostName, administratorEmail,
|
||||
virtualHostUser, phpVersion, openBasedir)
|
||||
virtualHostUser, phpVersion, openBasedir,
|
||||
memSoftLimit, memHardLimit, maxConnections,
|
||||
procSoftLimit, procHardLimit)
|
||||
if retValues[0] == 0:
|
||||
raise BaseException(retValues[1])
|
||||
|
||||
@@ -673,8 +685,6 @@ local_name %s {
|
||||
if retValues[0] == 0:
|
||||
raise BaseException(retValues[1])
|
||||
|
||||
selectedPackage = Package.objects.get(packageName=packageName)
|
||||
|
||||
if LimitsCheck:
|
||||
website = Websites(admin=admin, package=selectedPackage, domain=virtualHostName,
|
||||
adminEmail=administratorEmail,
|
||||
@@ -765,6 +775,23 @@ local_name %s {
|
||||
command = f'setquota -u {virtualHostUser} {spaceString} 0 0 /'
|
||||
ProcessUtilities.executioner(command)
|
||||
|
||||
# Apply OpenLiteSpeed cgroups v2 resource limits and inode quotas
|
||||
if selectedPackage.enforceDiskLimits:
|
||||
try:
|
||||
from plogical.resourceLimits import resource_manager
|
||||
|
||||
# Set per-user resource limits using OLS native cgroups API
|
||||
success = resource_manager.set_user_limits(virtualHostUser, selectedPackage)
|
||||
if not success:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Warning: Failed to set resource limits for user {virtualHostUser}")
|
||||
|
||||
# Set inode limit using filesystem quotas
|
||||
success = resource_manager.set_inode_limit(virtualHostName, virtualHostUser, selectedPackage.inodeLimit)
|
||||
if not success:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Warning: Failed to set inode limit for {virtualHostName}")
|
||||
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Error applying resource limits for {virtualHostName}: {str(e)}")
|
||||
|
||||
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Website successfully created. [200]')
|
||||
|
||||
@@ -1574,8 +1601,18 @@ local_name %s {
|
||||
|
||||
logging.CyberCPLogFileWriter.statusWriter(tempStatusPath, 'Creating configurations..,50')
|
||||
|
||||
# Get resource limits from master website's package (child domains share parent's limits)
|
||||
masterPackage = master.package
|
||||
memSoftLimit = masterPackage.memoryLimitMB
|
||||
memHardLimit = masterPackage.memoryLimitMB
|
||||
maxConnections = masterPackage.maxConnections
|
||||
procSoftLimit = masterPackage.procSoftLimit
|
||||
procHardLimit = masterPackage.procHardLimit
|
||||
|
||||
retValues = vhost.createDirectoryForDomain(masterDomain, virtualHostName, phpVersion, path,
|
||||
master.adminEmail, master.externalApp, openBasedir)
|
||||
master.adminEmail, master.externalApp, openBasedir,
|
||||
memSoftLimit, memHardLimit, maxConnections,
|
||||
procSoftLimit, procHardLimit)
|
||||
if retValues[0] == 0:
|
||||
raise BaseException(retValues[1])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user