mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-11-16 10:16:12 +01:00
Add FTP quota management features: Implement getFTPQuotaUsage and migrateFTPQuotas methods in FTPManager for retrieving quota usage and migrating existing users to the new quota system. Update views and URLs to support these new functionalities. Enhance FTPUtilities with methods for applying quotas to the filesystem and retrieving current quota usage.
This commit is contained in:
@@ -336,6 +336,87 @@ class FTPManager:
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def getFTPQuotaUsage(self):
|
||||
"""
|
||||
Get quota usage information for an FTP user
|
||||
"""
|
||||
try:
|
||||
userID = self.request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if ACLManager.currentContextPermission(currentACL, 'listFTPAccounts') == 0:
|
||||
return ACLManager.loadErrorJson('getQuotaUsage', 0)
|
||||
|
||||
data = json.loads(self.request.body)
|
||||
userName = data['ftpUserName']
|
||||
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
ftp = Users.objects.get(user=userName)
|
||||
|
||||
if currentACL['admin'] == 1:
|
||||
pass
|
||||
elif ftp.domain.admin != admin:
|
||||
return ACLManager.loadErrorJson()
|
||||
|
||||
result = FTPUtilities.getFTPQuotaUsage(userName)
|
||||
|
||||
if isinstance(result, dict):
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'getQuotaUsage': 1,
|
||||
'error_message': "None",
|
||||
'quota_usage': result
|
||||
}
|
||||
else:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'getQuotaUsage': 0,
|
||||
'error_message': result[1] if isinstance(result, tuple) else str(result)
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except BaseException as msg:
|
||||
data_ret = {'status': 0, 'getQuotaUsage': 0, 'error_message': str(msg)}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def migrateFTPQuotas(self):
|
||||
"""
|
||||
Migrate existing FTP users to the new quota system
|
||||
"""
|
||||
try:
|
||||
userID = self.request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] != 1:
|
||||
return ACLManager.loadErrorJson('migrateQuotas', 0)
|
||||
|
||||
result = FTPUtilities.migrateExistingFTPUsers()
|
||||
|
||||
if result[0] == 1:
|
||||
data_ret = {
|
||||
'status': 1,
|
||||
'migrateQuotas': 1,
|
||||
'error_message': "None",
|
||||
'message': result[1]
|
||||
}
|
||||
else:
|
||||
data_ret = {
|
||||
'status': 0,
|
||||
'migrateQuotas': 0,
|
||||
'error_message': result[1]
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except BaseException as msg:
|
||||
data_ret = {'status': 0, 'migrateQuotas': 0, 'error_message': str(msg)}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def installPureFTPD(self):
|
||||
|
||||
def pureFTPDServiceName():
|
||||
|
||||
@@ -16,4 +16,6 @@ urlpatterns = [
|
||||
path('getAllFTPAccounts', views.getAllFTPAccounts, name='getAllFTPAccounts'),
|
||||
path('changePassword', views.changePassword, name='changePassword'),
|
||||
path('updateFTPQuota', views.updateFTPQuota, name='updateFTPQuota'),
|
||||
path('getFTPQuotaUsage', views.getFTPQuotaUsage, name='getFTPQuotaUsage'),
|
||||
path('migrateFTPQuotas', views.migrateFTPQuotas, name='migrateFTPQuotas'),
|
||||
]
|
||||
|
||||
14
ftp/views.py
14
ftp/views.py
@@ -223,3 +223,17 @@ def updateFTPQuota(request):
|
||||
return fm.updateFTPQuota()
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def getFTPQuotaUsage(request):
|
||||
try:
|
||||
fm = FTPManager(request)
|
||||
return fm.getFTPQuotaUsage()
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
def migrateFTPQuotas(request):
|
||||
try:
|
||||
fm = FTPManager(request)
|
||||
return fm.migrateFTPQuotas()
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
@@ -31,4 +31,6 @@ MaxDiskUsage 99
|
||||
CustomerProof yes
|
||||
TLS 1
|
||||
PassivePortRange 40110 40210
|
||||
# Quota enforcement
|
||||
Quota yes
|
||||
|
||||
|
||||
@@ -7,5 +7,8 @@ MYSQLGetDir SELECT Dir FROM users WHERE User='\L'
|
||||
MYSQLGetGID SELECT Gid FROM users WHERE User='\L'
|
||||
MYSQLGetPW SELECT Password FROM users WHERE User='\L'
|
||||
MYSQLGetUID SELECT Uid FROM users WHERE User='\L'
|
||||
# Quota enforcement queries
|
||||
MYSQLGetQTAFS SELECT QuotaSize FROM users WHERE User='\L'
|
||||
MYSQLGetQTAUS SELECT 0 FROM users WHERE User='\L'
|
||||
MYSQLPassword 1qaz@9xvps
|
||||
MYSQLUser cyberpanel
|
||||
|
||||
@@ -31,4 +31,6 @@ MaxDiskUsage 99
|
||||
CustomerProof yes
|
||||
TLS 1
|
||||
PassivePortRange 40110 40210
|
||||
# Quota enforcement
|
||||
Quota yes
|
||||
|
||||
|
||||
@@ -7,5 +7,8 @@ MYSQLGetDir SELECT Dir FROM users WHERE User='\L'
|
||||
MYSQLGetGID SELECT Gid FROM users WHERE User='\L'
|
||||
MYSQLGetPW SELECT Password FROM users WHERE User='\L'
|
||||
MYSQLGetUID SELECT Uid FROM users WHERE User='\L'
|
||||
# Quota enforcement queries
|
||||
MYSQLGetQTAFS SELECT QuotaSize FROM users WHERE User='\L'
|
||||
MYSQLGetQTAUS SELECT 0 FROM users WHERE User='\L'
|
||||
MYSQLPassword 1qaz@9xvps
|
||||
MYSQLUser cyberpanel
|
||||
|
||||
@@ -266,6 +266,9 @@ class FTPUtilities:
|
||||
|
||||
ftp.save()
|
||||
|
||||
# Apply quota to filesystem if needed
|
||||
FTPUtilities.applyQuotaToFilesystem(ftp)
|
||||
|
||||
return 1, "FTP quota updated successfully"
|
||||
|
||||
except Users.DoesNotExist:
|
||||
@@ -274,6 +277,107 @@ class FTPUtilities:
|
||||
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [updateFTPQuota]")
|
||||
return 0, str(msg)
|
||||
|
||||
@staticmethod
|
||||
def applyQuotaToFilesystem(ftp_user):
|
||||
"""
|
||||
Apply quota settings to the filesystem level
|
||||
"""
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
# Get the user's directory
|
||||
user_dir = ftp_user.dir
|
||||
if not user_dir or not os.path.exists(user_dir):
|
||||
return False, "User directory not found"
|
||||
|
||||
# Convert quota from MB to KB for setquota command
|
||||
quota_kb = ftp_user.quotasize * 1024
|
||||
|
||||
# Apply quota using setquota command
|
||||
# Note: This requires quota tools to be installed
|
||||
try:
|
||||
# Set both soft and hard limits to the same value
|
||||
subprocess.run([
|
||||
'setquota', '-u', str(ftp_user.uid),
|
||||
f'{quota_kb}K', f'{quota_kb}K',
|
||||
'0', '0', # inode limits (unlimited)
|
||||
user_dir
|
||||
], check=True, capture_output=True)
|
||||
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Applied quota {quota_kb}KB to user {ftp_user.user} in {user_dir}")
|
||||
return True, "Quota applied successfully"
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Failed to apply quota: {e}")
|
||||
return False, f"Failed to apply quota: {e}"
|
||||
except FileNotFoundError:
|
||||
# setquota command not found, quota tools not installed
|
||||
logging.CyberCPLogFileWriter.writeToFile("setquota command not found - quota tools may not be installed")
|
||||
return False, "Quota tools not installed"
|
||||
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Error applying quota to filesystem: {str(e)}")
|
||||
return False, str(e)
|
||||
|
||||
@staticmethod
|
||||
def getFTPQuotaUsage(ftpUsername):
|
||||
"""
|
||||
Get current quota usage for an FTP user
|
||||
"""
|
||||
try:
|
||||
ftp = Users.objects.get(user=ftpUsername)
|
||||
user_dir = ftp.dir
|
||||
|
||||
if not user_dir or not os.path.exists(user_dir):
|
||||
return 0, "User directory not found"
|
||||
|
||||
# Get directory size in MB
|
||||
import subprocess
|
||||
result = subprocess.run(['du', '-sm', user_dir], capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
usage_mb = int(result.stdout.split()[0])
|
||||
quota_mb = ftp.quotasize
|
||||
usage_percent = (usage_mb / quota_mb * 100) if quota_mb > 0 else 0
|
||||
|
||||
return {
|
||||
'usage_mb': usage_mb,
|
||||
'quota_mb': quota_mb,
|
||||
'usage_percent': round(usage_percent, 2),
|
||||
'remaining_mb': max(0, quota_mb - usage_mb)
|
||||
}
|
||||
else:
|
||||
return 0, "Failed to get directory size"
|
||||
|
||||
except Users.DoesNotExist:
|
||||
return 0, "FTP user not found"
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Error getting quota usage: {str(e)}")
|
||||
return 0, str(e)
|
||||
|
||||
@staticmethod
|
||||
def migrateExistingFTPUsers():
|
||||
"""
|
||||
Migrate existing FTP users to use the new quota system
|
||||
"""
|
||||
try:
|
||||
migrated_count = 0
|
||||
|
||||
for ftp_user in Users.objects.all():
|
||||
# If custom_quota_enabled is not set, set it to False and use package default
|
||||
if not hasattr(ftp_user, 'custom_quota_enabled') or ftp_user.custom_quota_enabled is None:
|
||||
ftp_user.custom_quota_enabled = False
|
||||
ftp_user.custom_quota_size = 0
|
||||
ftp_user.quotasize = ftp_user.domain.package.diskSpace
|
||||
ftp_user.save()
|
||||
migrated_count += 1
|
||||
|
||||
return 1, f"Migrated {migrated_count} FTP users to new quota system"
|
||||
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f"Error migrating FTP users: {str(e)}")
|
||||
return 0, str(e)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
|
||||
121
scripts/enable_ftp_quota.sh
Normal file
121
scripts/enable_ftp_quota.sh
Normal file
@@ -0,0 +1,121 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Enable FTP User Quota Feature
|
||||
# This script applies the quota configuration and restarts Pure-FTPd
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging function
|
||||
log_message() {
|
||||
echo -e "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a /var/log/cyberpanel_ftp_quota.log
|
||||
}
|
||||
|
||||
log_message "${BLUE}Starting FTP Quota Feature Setup...${NC}"
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
log_message "${RED}Please run as root${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Backup existing configurations
|
||||
log_message "${YELLOW}Backing up existing Pure-FTPd configurations...${NC}"
|
||||
|
||||
if [ -f /etc/pure-ftpd/pure-ftpd.conf ]; then
|
||||
cp /etc/pure-ftpd/pure-ftpd.conf /etc/pure-ftpd/pure-ftpd.conf.backup.$(date +%Y%m%d_%H%M%S)
|
||||
log_message "${GREEN}Backed up pure-ftpd.conf${NC}"
|
||||
fi
|
||||
|
||||
if [ -f /etc/pure-ftpd/pureftpd-mysql.conf ]; then
|
||||
cp /etc/pure-ftpd/pureftpd-mysql.conf /etc/pure-ftpd/pureftpd-mysql.conf.backup.$(date +%Y%m%d_%H%M%S)
|
||||
log_message "${GREEN}Backed up pureftpd-mysql.conf${NC}"
|
||||
fi
|
||||
|
||||
# Apply new configurations
|
||||
log_message "${YELLOW}Applying FTP quota configurations...${NC}"
|
||||
|
||||
# Copy the updated configurations
|
||||
if [ -f /usr/local/CyberCP/install/pure-ftpd/pure-ftpd.conf ]; then
|
||||
cp /usr/local/CyberCP/install/pure-ftpd/pure-ftpd.conf /etc/pure-ftpd/pure-ftpd.conf
|
||||
log_message "${GREEN}Updated pure-ftpd.conf${NC}"
|
||||
fi
|
||||
|
||||
if [ -f /usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf ]; then
|
||||
cp /usr/local/CyberCP/install/pure-ftpd/pureftpd-mysql.conf /etc/pure-ftpd/pureftpd-mysql.conf
|
||||
log_message "${GREEN}Updated pureftpd-mysql.conf${NC}"
|
||||
fi
|
||||
|
||||
# Check if Pure-FTPd is running
|
||||
if systemctl is-active --quiet pure-ftpd; then
|
||||
log_message "${YELLOW}Restarting Pure-FTPd service...${NC}"
|
||||
systemctl restart pure-ftpd
|
||||
|
||||
if systemctl is-active --quiet pure-ftpd; then
|
||||
log_message "${GREEN}Pure-FTPd restarted successfully${NC}"
|
||||
else
|
||||
log_message "${RED}Failed to restart Pure-FTPd${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_message "${YELLOW}Starting Pure-FTPd service...${NC}"
|
||||
systemctl start pure-ftpd
|
||||
|
||||
if systemctl is-active --quiet pure-ftpd; then
|
||||
log_message "${GREEN}Pure-FTPd started successfully${NC}"
|
||||
else
|
||||
log_message "${RED}Failed to start Pure-FTPd${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify quota enforcement is working
|
||||
log_message "${YELLOW}Verifying quota enforcement...${NC}"
|
||||
|
||||
# Check if quota queries are in the configuration
|
||||
if grep -q "MYSQLGetQTAFS" /etc/pure-ftpd/pureftpd-mysql.conf; then
|
||||
log_message "${GREEN}Quota queries found in MySQL configuration${NC}"
|
||||
else
|
||||
log_message "${RED}Quota queries not found in MySQL configuration${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -q "Quota.*yes" /etc/pure-ftpd/pure-ftpd.conf; then
|
||||
log_message "${GREEN}Quota enforcement enabled in Pure-FTPd configuration${NC}"
|
||||
else
|
||||
log_message "${RED}Quota enforcement not enabled in Pure-FTPd configuration${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test database connection
|
||||
log_message "${YELLOW}Testing database connection...${NC}"
|
||||
|
||||
# Get database credentials from configuration
|
||||
MYSQL_USER=$(grep "MYSQLUser" /etc/pure-ftpd/pureftpd-mysql.conf | cut -d' ' -f2)
|
||||
MYSQL_PASS=$(grep "MYSQLPassword" /etc/pure-ftpd/pureftpd-mysql.conf | cut -d' ' -f2)
|
||||
MYSQL_DB=$(grep "MYSQLDatabase" /etc/pure-ftpd/pureftpd-mysql.conf | cut -d' ' -f2)
|
||||
|
||||
if mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "USE $MYSQL_DB; SELECT COUNT(*) FROM users;" >/dev/null 2>&1; then
|
||||
log_message "${GREEN}Database connection successful${NC}"
|
||||
else
|
||||
log_message "${RED}Database connection failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_message "${GREEN}FTP User Quota feature has been successfully enabled!${NC}"
|
||||
log_message "${BLUE}Features enabled:${NC}"
|
||||
log_message " - Individual FTP user quotas"
|
||||
log_message " - Custom quota sizes per user"
|
||||
log_message " - Package default quota fallback"
|
||||
log_message " - Real-time quota enforcement by Pure-FTPd"
|
||||
log_message " - Web interface for quota management"
|
||||
|
||||
log_message "${YELLOW}Note: Existing FTP users will need to have their quotas updated through the web interface to take effect.${NC}"
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user