mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-07 14:45:56 +02:00
pluginInstaller: privileged write for settings/urls, skip duplicate pluginHolder urls
- _write_lines_to_protected_file: temp file + ProcessUtilities cp when direct write fails - upgradingSettingsFile: idempotent; fallback anchor pluginHolder; clearer warnings - upgradingURLs: no-op when pluginHolder.urls present (avoids duplicate routes and root-only edits) - add/removeInterfaceLink and removeFromSettings/URLs use protected writer
This commit is contained in:
@@ -57,6 +57,38 @@ class pluginInstaller:
|
||||
pluginHome = '/usr/local/CyberCP/' + pluginName
|
||||
return os.path.exists(pluginHome + '/enable_migrations')
|
||||
|
||||
@staticmethod
|
||||
def _write_lines_to_protected_file(target_path, lines):
|
||||
"""
|
||||
Write UTF-8 lines to a file. Core panel files are often root:root 644; the panel
|
||||
process may need a privileged copy (lscpd/sudo) to update them.
|
||||
"""
|
||||
try:
|
||||
with open(target_path, 'w', encoding='utf-8') as wf:
|
||||
wf.writelines(lines)
|
||||
return
|
||||
except (PermissionError, OSError) as e:
|
||||
pluginInstaller.stdOut('Direct write failed for %s: %s' % (target_path, str(e)))
|
||||
fd, tmp_path = tempfile.mkstemp(prefix='cpwr_', suffix='.txt', dir='/tmp')
|
||||
try:
|
||||
with os.fdopen(fd, 'w', encoding='utf-8') as wf:
|
||||
wf.writelines(lines)
|
||||
cmd = 'cp %s %s' % (shlex.quote(tmp_path), shlex.quote(target_path))
|
||||
if ProcessUtilities.executioner(cmd) == 1:
|
||||
pluginInstaller.stdOut('Wrote %s via privileged copy' % target_path)
|
||||
return
|
||||
except Exception as ex:
|
||||
pluginInstaller.stdOut('Privileged write failed: %s' % str(ex))
|
||||
finally:
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except OSError:
|
||||
pass
|
||||
raise PermissionError(
|
||||
'Cannot write %s. As root: chgrp lscpd %s && chmod 664 %s'
|
||||
% (target_path, target_path, target_path)
|
||||
)
|
||||
|
||||
### Functions Related to plugin installation.
|
||||
|
||||
@staticmethod
|
||||
@@ -153,53 +185,80 @@ class pluginInstaller:
|
||||
|
||||
@staticmethod
|
||||
def upgradingSettingsFile(pluginName):
|
||||
data = open("/usr/local/CyberCP/CyberCP/settings.py", 'r', encoding='utf-8').readlines()
|
||||
writeToFile = open("/usr/local/CyberCP/CyberCP/settings.py", 'w', encoding='utf-8')
|
||||
|
||||
settings_path = "/usr/local/CyberCP/CyberCP/settings.py"
|
||||
with open(settings_path, 'r', encoding='utf-8') as rf:
|
||||
data = rf.readlines()
|
||||
line_plugin = " '" + pluginName + "',\n"
|
||||
if any(line.strip() in ("'" + pluginName + "',", '"' + pluginName + '",') for line in data):
|
||||
pluginInstaller.stdOut(
|
||||
'Plugin %s already listed in settings.py; skipping INSTALLED_APPS insert.' % pluginName
|
||||
)
|
||||
return
|
||||
out = []
|
||||
inserted = False
|
||||
for items in data:
|
||||
if items.find("'emailPremium',") > -1:
|
||||
writeToFile.writelines(items)
|
||||
writeToFile.writelines(" '" + pluginName + "',\n")
|
||||
out.append(items)
|
||||
out.append(line_plugin)
|
||||
inserted = True
|
||||
else:
|
||||
writeToFile.writelines(items)
|
||||
|
||||
writeToFile.close()
|
||||
out.append(items)
|
||||
if not inserted:
|
||||
out = []
|
||||
for items in data:
|
||||
if "'pluginHolder'," in items or '"pluginHolder",' in items:
|
||||
out.append(items)
|
||||
out.append(line_plugin)
|
||||
inserted = True
|
||||
else:
|
||||
out.append(items)
|
||||
if not inserted:
|
||||
pluginInstaller.stdOut(
|
||||
'Warning: no emailPremium or pluginHolder anchor in settings.py; '
|
||||
'add %r to INSTALLED_APPS manually or upgrade CyberPanel (auto-sync plugins on disk).'
|
||||
% pluginName
|
||||
)
|
||||
return
|
||||
pluginInstaller._write_lines_to_protected_file(settings_path, out)
|
||||
|
||||
@staticmethod
|
||||
def upgradingURLs(pluginName):
|
||||
"""
|
||||
Add plugin URL pattern to urls.py
|
||||
Plugin URLs must be inserted BEFORE the generic 'plugins/' line
|
||||
to ensure proper route matching (more specific routes first)
|
||||
Legacy: add explicit path('plugins/<name>/', ...). Modern CyberPanel uses
|
||||
pluginHolder.urls for all plugins — skip to avoid duplicate routes and root-only writes.
|
||||
"""
|
||||
data = open("/usr/local/CyberCP/CyberCP/urls.py", 'r', encoding='utf-8').readlines()
|
||||
writeToFile = open("/usr/local/CyberCP/CyberCP/urls.py", 'w', encoding='utf-8')
|
||||
urls_path = "/usr/local/CyberCP/CyberCP/urls.py"
|
||||
with open(urls_path, 'r', encoding='utf-8') as rf:
|
||||
content = rf.read()
|
||||
if "include('pluginHolder.urls')" in content or 'include("pluginHolder.urls")' in content:
|
||||
pluginInstaller.stdOut(
|
||||
'pluginHolder.urls found; skipping per-plugin urls.py line for %s (dynamic routes).' % pluginName
|
||||
)
|
||||
return
|
||||
data = content.splitlines(keepends=True)
|
||||
out = []
|
||||
urlPatternAdded = False
|
||||
|
||||
for items in data:
|
||||
# Insert plugin URL BEFORE the generic 'plugins/' line
|
||||
# This ensures more specific routes are matched first
|
||||
if items.find("path('plugins/', include('pluginHolder.urls'))") > -1 or items.find("path(\"plugins/\", include('pluginHolder.urls'))") > -1:
|
||||
if items.find("path('plugins/', include('pluginHolder.urls'))") > -1 or items.find(
|
||||
"path(\"plugins/\", include('pluginHolder.urls'))"
|
||||
) > -1:
|
||||
if not urlPatternAdded:
|
||||
writeToFile.writelines(pluginInstaller.getUrlPattern(pluginName))
|
||||
out.append(pluginInstaller.getUrlPattern(pluginName))
|
||||
urlPatternAdded = True
|
||||
writeToFile.writelines(items)
|
||||
out.append(items)
|
||||
else:
|
||||
writeToFile.writelines(items)
|
||||
|
||||
# Fallback: if 'plugins/' line not found, insert after 'manageservices'
|
||||
out.append(items)
|
||||
if not urlPatternAdded:
|
||||
pluginInstaller.stdOut(f"Warning: 'plugins/' line not found, using fallback insertion after 'manageservices'")
|
||||
writeToFile.close()
|
||||
writeToFile = open("/usr/local/CyberCP/CyberCP/urls.py", 'w', encoding='utf-8')
|
||||
pluginInstaller.stdOut("Warning: 'plugins/' line not found, using fallback insertion after 'manageservices'")
|
||||
out = []
|
||||
for items in data:
|
||||
if items.find("manageservices") > -1:
|
||||
writeToFile.writelines(items)
|
||||
writeToFile.writelines(pluginInstaller.getUrlPattern(pluginName))
|
||||
out.append(items)
|
||||
out.append(pluginInstaller.getUrlPattern(pluginName))
|
||||
urlPatternAdded = True
|
||||
else:
|
||||
writeToFile.writelines(items)
|
||||
|
||||
writeToFile.close()
|
||||
out.append(items)
|
||||
pluginInstaller._write_lines_to_protected_file(urls_path, out)
|
||||
|
||||
@staticmethod
|
||||
def informCyberPanel(pluginName):
|
||||
@@ -214,19 +273,20 @@ class pluginInstaller:
|
||||
|
||||
@staticmethod
|
||||
def addInterfaceLink(pluginName):
|
||||
data = open("/usr/local/CyberCP/baseTemplate/templates/baseTemplate/index.html", 'r', encoding='utf-8').readlines()
|
||||
writeToFile = open("/usr/local/CyberCP/baseTemplate/templates/baseTemplate/index.html", 'w', encoding='utf-8')
|
||||
|
||||
path_html = "/usr/local/CyberCP/baseTemplate/templates/baseTemplate/index.html"
|
||||
with open(path_html, 'r', encoding='utf-8') as rf:
|
||||
data = rf.readlines()
|
||||
out = []
|
||||
for items in data:
|
||||
if items.find("{# pluginsList #}") > -1:
|
||||
writeToFile.writelines(items)
|
||||
writeToFile.writelines(" ")
|
||||
writeToFile.writelines(
|
||||
'<li><a href="{% url \'' + pluginName + '\' %}" title="{% trans \'' + pluginName + '\' %}"><span>{% trans "' + pluginName + '" %}</span></a></li>\n')
|
||||
out.append(items)
|
||||
out.append(" ")
|
||||
out.append(
|
||||
'<li><a href="{% url \'' + pluginName + '\' %}" title="{% trans \'' + pluginName + '\' %}"><span>{% trans "' + pluginName + '" %}</span></a></li>\n'
|
||||
)
|
||||
else:
|
||||
writeToFile.writelines(items)
|
||||
|
||||
writeToFile.close()
|
||||
out.append(items)
|
||||
pluginInstaller._write_lines_to_protected_file(path_html, out)
|
||||
|
||||
@staticmethod
|
||||
def staticContent():
|
||||
@@ -484,9 +544,8 @@ class pluginInstaller:
|
||||
continue
|
||||
out_lines.append(items)
|
||||
try:
|
||||
with open(settings_path, 'w', encoding='utf-8') as writeToFile:
|
||||
writeToFile.writelines(out_lines)
|
||||
except (OSError, IOError) as e:
|
||||
pluginInstaller._write_lines_to_protected_file(settings_path, out_lines)
|
||||
except (OSError, IOError, PermissionError) as e:
|
||||
raise Exception(
|
||||
f'Cannot write {settings_path}: {e}. '
|
||||
'Ensure the file is writable by the panel user (e.g. chgrp lscpd ... ; chmod g+w ...).'
|
||||
@@ -507,9 +566,8 @@ class pluginInstaller:
|
||||
continue
|
||||
out_lines.append(items)
|
||||
try:
|
||||
with open(urls_path, 'w', encoding='utf-8') as f:
|
||||
f.writelines(out_lines)
|
||||
except (OSError, IOError) as e:
|
||||
pluginInstaller._write_lines_to_protected_file(urls_path, out_lines)
|
||||
except (OSError, IOError, PermissionError) as e:
|
||||
raise Exception(f'Cannot write {urls_path}: {e}. Ensure the file is writable by the panel user (chgrp lscpd; chmod g+w).')
|
||||
|
||||
@staticmethod
|
||||
@@ -521,15 +579,15 @@ class pluginInstaller:
|
||||
|
||||
@staticmethod
|
||||
def removeInterfaceLink(pluginName):
|
||||
data = open("/usr/local/CyberCP/baseTemplate/templates/baseTemplate/index.html", 'r', encoding='utf-8').readlines()
|
||||
writeToFile = open("/usr/local/CyberCP/baseTemplate/templates/baseTemplate/index.html", 'w', encoding='utf-8')
|
||||
|
||||
path_html = "/usr/local/CyberCP/baseTemplate/templates/baseTemplate/index.html"
|
||||
with open(path_html, 'r', encoding='utf-8') as rf:
|
||||
data = rf.readlines()
|
||||
out = []
|
||||
for items in data:
|
||||
if items.find(pluginName) > -1 and items.find('<li>') > -1:
|
||||
continue
|
||||
else:
|
||||
writeToFile.writelines(items)
|
||||
writeToFile.close()
|
||||
out.append(items)
|
||||
pluginInstaller._write_lines_to_protected_file(path_html, out)
|
||||
|
||||
@staticmethod
|
||||
def removeMigrations(pluginName):
|
||||
|
||||
Reference in New Issue
Block a user