fix: Rspamd installer, Email Delivery migrations, dns sys.path

- mailUtilities: insert CyberCP first on sys.path (dnspython dns shadowing); Rspamd log under /var/log/cyberpanel; log before ServiceManager; dnf on EL8/9; append package stderr
- emailPremium: Rspamd admin UI without cloud addon gate; JsonResponse; fetchRspamdSettings unlocked
- emailDelivery: AutoField PKs; 0001_initial SeparateDatabaseAndState for int FK to loginSystem
This commit is contained in:
master3395
2026-04-09 22:39:49 +02:00
parent 90fd3b7dfb
commit a3dace73bb
5 changed files with 227 additions and 97 deletions

View File

@@ -0,0 +1,128 @@
# CyberMail Email Delivery — initial schema.
# Database DDL uses int(11) FK to legacy loginSystem_administrator.id (MariaDB rejects bigint FK -> int PK).
from django.db import migrations, models
import django.db.models.deletion
CREATE_CYBERMAIL_TABLES = '''
CREATE TABLE `cybermail_accounts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`platform_account_id` int(11) DEFAULT NULL,
`api_key` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`plan_name` varchar(100) NOT NULL,
`plan_slug` varchar(50) NOT NULL,
`emails_per_month` int(11) NOT NULL,
`is_connected` tinyint(1) NOT NULL,
`relay_enabled` tinyint(1) NOT NULL,
`smtp_credential_id` int(11) DEFAULT NULL,
`smtp_username` varchar(255) NOT NULL,
`smtp_host` varchar(255) NOT NULL,
`smtp_port` int(11) NOT NULL,
`created_at` datetime(6) NOT NULL,
`updated_at` datetime(6) NOT NULL,
`admin_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `cybermail_accounts_admin_id_uniq` (`admin_id`),
CONSTRAINT `cybermail_accounts_admin_id_fk` FOREIGN KEY (`admin_id`)
REFERENCES `loginSystem_administrator` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `cybermail_domains` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`domain` varchar(255) NOT NULL,
`platform_domain_id` int(11) DEFAULT NULL,
`status` varchar(50) NOT NULL,
`spf_verified` tinyint(1) NOT NULL,
`dkim_verified` tinyint(1) NOT NULL,
`dmarc_verified` tinyint(1) NOT NULL,
`dns_configured` tinyint(1) NOT NULL,
`created_at` datetime(6) NOT NULL,
`account_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `cybermail_domains_account_id_fk` (`account_id`),
CONSTRAINT `cybermail_domains_account_id_fk` FOREIGN KEY (`account_id`)
REFERENCES `cybermail_accounts` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
'''
DROP_CYBERMAIL_TABLES = '''
DROP TABLE IF EXISTS `cybermail_domains`;
DROP TABLE IF EXISTS `cybermail_accounts`;
'''
class Migration(migrations.Migration):
initial = True
dependencies = [
('loginSystem', '0001_initial'),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[
migrations.RunSQL(CREATE_CYBERMAIL_TABLES, DROP_CYBERMAIL_TABLES),
],
state_operations=[
migrations.CreateModel(
name='CyberMailAccount',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('platform_account_id', models.IntegerField(null=True)),
('api_key', models.CharField(blank=True, max_length=255)),
('email', models.CharField(max_length=255)),
('plan_name', models.CharField(default='Free', max_length=100)),
('plan_slug', models.CharField(default='free', max_length=50)),
('emails_per_month', models.IntegerField(default=15000)),
('is_connected', models.BooleanField(default=False)),
('relay_enabled', models.BooleanField(default=False)),
('smtp_credential_id', models.IntegerField(null=True)),
('smtp_username', models.CharField(blank=True, max_length=255)),
('smtp_host', models.CharField(default='mail.cyberpersons.com', max_length=255)),
('smtp_port', models.IntegerField(default=587)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
(
'admin',
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name='cybermail_account',
to='loginSystem.administrator',
),
),
],
options={
'db_table': 'cybermail_accounts',
},
),
migrations.CreateModel(
name='CyberMailDomain',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('domain', models.CharField(max_length=255)),
('platform_domain_id', models.IntegerField(null=True)),
('status', models.CharField(default='pending', max_length=50)),
('spf_verified', models.BooleanField(default=False)),
('dkim_verified', models.BooleanField(default=False)),
('dmarc_verified', models.BooleanField(default=False)),
('dns_configured', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
(
'account',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='domains',
to='emailDelivery.cybermailaccount',
),
),
],
options={
'db_table': 'cybermail_domains',
},
),
],
),
]

View File

@@ -0,0 +1 @@
# CyberMail Email Delivery migrations

View File

@@ -3,6 +3,8 @@ from loginSystem.models import Administrator
class CyberMailAccount(models.Model):
# int(11) PK/FK to match legacy loginSystem_administrator.id (not DEFAULT_AUTO_FIELD BigAutoField)
id = models.AutoField(primary_key=True)
admin = models.OneToOneField(Administrator, on_delete=models.CASCADE, related_name='cybermail_account')
platform_account_id = models.IntegerField(null=True)
api_key = models.CharField(max_length=255, blank=True)
@@ -27,6 +29,7 @@ class CyberMailAccount(models.Model):
class CyberMailDomain(models.Model):
id = models.AutoField(primary_key=True)
account = models.ForeignKey(CyberMailAccount, on_delete=models.CASCADE, related_name='domains')
domain = models.CharField(max_length=255)
platform_domain_id = models.IntegerField(null=True)

View File

@@ -3,7 +3,7 @@ import os
import time
from django.shortcuts import redirect
from django.http import HttpResponse
from django.http import HttpResponse, JsonResponse
from loginSystem.models import Administrator
from mailServer.models import Domains, EUsers
@@ -1233,32 +1233,28 @@ def installStatusMailScanner(request):
###Rspamd
def Rspamd(request):
url = "https://platform.cyberpersons.com/CyberpanelAdOns/Adonpermission"
data = {
"name": "email-debugger",
"IP": ACLManager.GetServerIP()
}
"""Rspamd UI — allow for any logged-in admin (do not require cloud addon permission)."""
try:
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return ACLManager.loadError()
except KeyError:
return redirect(loadLoginPage)
import requests
response = requests.post(url, data=json.dumps(data))
Status = response.json()['status']
checkIfRspamdInstalled = 0
if (Status == 1) or ProcessUtilities.decideServer() == ProcessUtilities.ent:
checkIfRspamdInstalled = 0
ipFile = "/etc/cyberpanel/machineIP"
f = open(ipFile)
ipData = f.read()
ipAddress = ipData.split('\n', 1)[0]
ipFile = "/etc/cyberpanel/machineIP"
f = open(ipFile)
ipData = f.read()
ipAddress = ipData.split('\n', 1)[0]
if mailUtilities.checkIfRspamdInstalled() == 1:
checkIfRspamdInstalled = 1
if mailUtilities.checkIfRspamdInstalled() == 1:
checkIfRspamdInstalled = 1
proc = httpProc(request, 'emailPremium/Rspamd.html',
{'checkIfRspamdInstalled': checkIfRspamdInstalled, 'ipAddress': ipAddress}, 'admin')
return proc.render()
else:
return redirect("https://cyberpanel.net/cyberpanel-addons")
proc = httpProc(request, 'emailPremium/Rspamd.html',
{'checkIfRspamdInstalled': checkIfRspamdInstalled, 'ipAddress': ipAddress}, 'admin')
return proc.render()
def installRspamd(request):
try:
@@ -1268,35 +1264,20 @@ def installRspamd(request):
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson()
return JsonResponse({'status': 0, 'error_message': 'Admin access required.'})
url = "https://platform.cyberpersons.com/CyberpanelAdOns/Adonpermission"
data = {
"name": "email-debugger",
"IP": ACLManager.GetServerIP()
}
import requests
response = requests.post(url, data=json.dumps(data))
Status = response.json()['status']
if (Status == 1) or ProcessUtilities.decideServer() == ProcessUtilities.ent:
try:
execPath = "/usr/local/CyberCP/bin/python " + virtualHostUtilities.cyberPanel + "/plogical/mailUtilities.py"
execPath = execPath + " installRspamd"
ProcessUtilities.popenExecutioner(execPath)
final_json = json.dumps({'status': 1, 'error_message': "None"})
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
try:
execPath = "/usr/local/CyberCP/bin/python " + virtualHostUtilities.cyberPanel + "/plogical/mailUtilities.py"
execPath = execPath + " installRspamd"
ProcessUtilities.popenExecutioner(execPath)
return JsonResponse({'status': 1, 'error_message': 'None'})
except BaseException as msg:
return JsonResponse({'status': 0, 'error_message': str(msg)})
except KeyError:
final_dic = {'status': 0, 'error_message': "Not Logged In, please refresh the page or login again."}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return JsonResponse({
'status': 0,
'error_message': 'Not Logged In, please refresh the page or login again.',
})
def installStatusRspamd(request):
try:
@@ -1310,8 +1291,15 @@ def installStatusRspamd(request):
try:
if request.method == 'POST':
command = "sudo cat " + mailUtilities.RspamdInstallLogPath
installStatus = ProcessUtilities.outputExecutioner(command)
if not os.path.isfile(mailUtilities.RspamdInstallLogPath):
installStatus = (
'Waiting for installation to start... '
'(Progress file: /var/log/cyberpanel/rspamd-install.log — if this persists, '
'click Install again or run as root: tail -f /var/log/cyberpanel/rspamd-install.log)\n'
)
else:
command = "sudo cat " + mailUtilities.RspamdInstallLogPath
installStatus = ProcessUtilities.outputExecutioner(command)
if installStatus.find("[200]") > -1:
@@ -1365,19 +1353,8 @@ def fetchRspamdSettings(request):
else:
return ACLManager.loadErrorJson('fetchStatus', 0)
url = "https://platform.cyberpersons.com/CyberpanelAdOns/Adonpermission"
data = {
"name": "email-debugger",
"IP": ACLManager.GetServerIP()
}
import requests
response = requests.post(url, data=json.dumps(data))
Status = response.json()['status']
if (Status == 1) or ProcessUtilities.decideServer() == ProcessUtilities.ent:
try:
if request.method == 'POST':
try:
if request.method == 'POST':
enabled = True
action = ''
@@ -1577,10 +1554,10 @@ def fetchRspamdSettings(request):
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'fetchStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'fetchStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except KeyError:
return redirect(loadLoginPage)
@@ -1747,21 +1724,17 @@ def unistallRspamd(request):
ProcessUtilities.popenExecutioner(execPath)
final_json = json.dumps({'status': 1, 'error_message': "None"})
return HttpResponse(final_json)
return JsonResponse({'status': 1, 'error_message': 'None'})
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return JsonResponse({'status': 0, 'error_message': str(msg)})
except KeyError:
final_dic = {'status': 0, 'error_message': "Not Logged In, please refresh the page or login again."}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return JsonResponse({
'status': 0,
'error_message': 'Not Logged In, please refresh the page or login again.',
})
def uninstallStatusRspamd(request):
try:
@@ -1775,8 +1748,14 @@ def uninstallStatusRspamd(request):
try:
if request.method == 'POST':
command = "sudo cat " + mailUtilities.RspamdUnInstallLogPath
installStatus = ProcessUtilities.outputExecutioner(command)
if not os.path.isfile(mailUtilities.RspamdUnInstallLogPath):
installStatus = (
'Waiting for uninstall to start... '
'(Progress file: /var/log/cyberpanel/rspamd-uninstall.log)\n'
)
else:
command = "sudo cat " + mailUtilities.RspamdUnInstallLogPath
installStatus = ProcessUtilities.outputExecutioner(command)
if installStatus.find("[200]") > -1:

View File

@@ -1,12 +1,13 @@
import json
import os,sys
import os, sys
import time
# CyberPanel Django app "dns" must win over PyPI "dns" (dnspython). append() leaves site-packages first.
_cybercp_root = '/usr/local/CyberCP'
if _cybercp_root not in sys.path:
sys.path.insert(0, _cybercp_root)
from django.http import HttpResponse
sys.path.append('/usr/local/CyberCP')
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
try:
@@ -37,8 +38,10 @@ class mailUtilities:
installLogPath = "/home/cyberpanel/openDKIMInstallLog"
spamassassinInstallLogPath = "/home/cyberpanel/spamassassinInstallLogPath"
RspamdInstallLogPath = "/home/cyberpanel/RspamdInstallLogPath"
RspamdUnInstallLogPath = "/home/cyberpanel/RspamdUnInstallLogPath"
# Use /var/log (not /home/cyberpanel mode 0700) so lscpd workers can always write install progress.
rspamdLogDir = "/var/log/cyberpanel"
RspamdInstallLogPath = "/var/log/cyberpanel/rspamd-install.log"
RspamdUnInstallLogPath = "/var/log/cyberpanel/rspamd-uninstall.log"
cyberPanelHome = "/home/cyberpanel"
mailScannerInstallLogPath = "/home/cyberpanel/mailScannerInstallLogPath"
RSpamdLogPath = '/var/log/rspamd/rspamd.log'
@@ -822,12 +825,16 @@ return custom_keywords
@staticmethod
def installRspamd(install, rspamd):
# Progress file before ServiceManager import (import can fail; panel polls this path).
os.makedirs(mailUtilities.rspamdLogDir, mode=0o755, exist_ok=True)
if not os.path.isdir(mailUtilities.cyberPanelHome):
os.makedirs(mailUtilities.cyberPanelHome, mode=0o750, exist_ok=True)
with open(mailUtilities.RspamdInstallLogPath, 'w') as lf:
lf.write('Starting Rspamd installation (preparing Redis and packages)...\n')
lf.flush()
from manageServices.serviceManager import ServiceManager
try:
if os.path.exists(mailUtilities.RspamdInstallLogPath):
os.remove(mailUtilities.RspamdInstallLogPath)
####Frist install redis
ServiceManager.InstallRedis()
@@ -862,16 +869,18 @@ return custom_keywords
command = 'rpm --import https://rspamd.com/rpm-stable/gpg.key'
ProcessUtilities.normalExecutioner(command, True)
command = 'yum update'
command = 'dnf update -y'
ProcessUtilities.normalExecutioner(command, True)
command = 'sudo yum install rspamd clamav clamd clamav-update -y'
command = 'sudo dnf install -y rspamd clamav clamd clamav-update'
else:
command = 'DEBIAN_FRONTEND=noninteractive apt-get install rspamd clamav clamav-daemon -y'
with open(mailUtilities.RspamdInstallLogPath, 'w') as f:
res = subprocess.call(command, stdout=f, shell=True)
with open(mailUtilities.RspamdInstallLogPath, 'a') as f:
f.write('\n--- Package install ---\n')
f.flush()
res = subprocess.call(command, stdout=f, stderr=f, shell=True)
###### makefile
@@ -1119,6 +1128,16 @@ LogFile /var/log/clamav/clamav.log
if os.path.exists(mailUtilities.RspamdUnInstallLogPath):
os.remove(mailUtilities.RspamdUnInstallLogPath)
try:
os.makedirs(mailUtilities.rspamdLogDir, mode=0o755, exist_ok=True)
if not os.path.isdir(mailUtilities.cyberPanelHome):
os.makedirs(mailUtilities.cyberPanelHome, mode=0o750, exist_ok=True)
with open(mailUtilities.RspamdUnInstallLogPath, 'w') as lf:
lf.write('Starting Rspamd removal...\n')
lf.flush()
except BaseException as log_err:
logging.CyberCPLogFileWriter.writeToFile(
str(log_err) + ' [uninstallRspamd init log]')
if ProcessUtilities.decideDistro() == ProcessUtilities.centos or ProcessUtilities.decideDistro() == ProcessUtilities.cent8:
command = 'sudo yum remove rspamd clamav clamav-daemon -y'
@@ -1129,7 +1148,7 @@ LogFile /var/log/clamav/clamav.log
with open(mailUtilities.RspamdUnInstallLogPath, 'w') as f:
with open(mailUtilities.RspamdUnInstallLogPath, 'a') as f:
res = subprocess.call(cmd, stdout=f)
if res == 1:
writeToFile = open(mailUtilities.RspamdUnInstallLogPath, 'a')