mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-07 17:35:52 +02:00
@@ -38,8 +38,13 @@ class secMiddleware:
|
||||
import re
|
||||
webhook_pattern = re.compile(r'^/websites/[^/]+/(webhook|gitNotify)/?$')
|
||||
|
||||
if pathActual == "/backup/localInitiate" or pathActual == '/' or pathActual == '/verifyLogin' or pathActual == '/logout' or pathActual.startswith('/api')\
|
||||
or webhook_pattern.match(pathActual) or pathActual.startswith('/cloudAPI') or pathActual.startswith('/static/'):
|
||||
# Public one-time phpMyAdmin launch links (limitedPhpmyAdmin plugin); must work when admin is logged out.
|
||||
_lpma_public_launch = pathActual.startswith('/plugins/limitedPhpmyAdmin/launch/')
|
||||
_lpma_pma_signon = pathActual == '/phpmyadmin/phpmyadminsignin.php'
|
||||
|
||||
if pathActual == "/backup/localInitiate" or pathActual == '/' or pathActual == '/verifyLogin' or pathActual == '/logout' or pathActual.startswith('/api')\
|
||||
or webhook_pattern.match(pathActual) or pathActual.startswith('/cloudAPI') or pathActual.startswith('/static/')\
|
||||
or _lpma_public_launch or _lpma_pma_signon:
|
||||
pass
|
||||
else:
|
||||
# Session check logging removed
|
||||
@@ -108,6 +113,11 @@ class secMiddleware:
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
||||
# phpMyAdmin sign-on POST carries MySQL password; skip character filter (may contain $ ( ) etc.).
|
||||
if pathActual == '/phpmyadmin/phpmyadminsignin.php':
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
||||
# logging.writeToFile(request.body)
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
|
||||
@@ -159,6 +159,7 @@ TEMPLATES = [
|
||||
'baseTemplate.context_processors.notification_preferences_context',
|
||||
'baseTemplate.context_processors.firewall_static_context',
|
||||
'baseTemplate.context_processors.dns_static_context',
|
||||
'baseTemplate.context_processors.plugin_sidebar_context',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -102,4 +102,84 @@ def dns_static_context(request):
|
||||
version = int(time.time())
|
||||
return {
|
||||
'DNS_STATIC_VERSION': version
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def plugin_sidebar_context(request):
|
||||
"""
|
||||
Sidebar Plugins menu: Installed / Plugin Store (ACL), Limited phpMyAdmin link
|
||||
for admins, plugin managers, or cpuser grantees; grant-only users see LPMA only.
|
||||
"""
|
||||
defaults = {
|
||||
'lpma_plugin_installed': False,
|
||||
'lpma_has_cpuser_grant': False,
|
||||
'show_plugin_management_links': False,
|
||||
'show_lpma_sidebar_link': False,
|
||||
'show_plugins_menu': False,
|
||||
'grant_only_lpma_sidebar': False,
|
||||
'plugin_sidebar_extra_links': [],
|
||||
'lpma_sidebar_url': '/plugins/limitedPhpmyAdmin/',
|
||||
}
|
||||
try:
|
||||
if 'userID' not in request.session:
|
||||
return defaults
|
||||
uid = request.session['userID']
|
||||
from plogical.acl import ACLManager
|
||||
|
||||
acl = ACLManager.loadedACL(uid)
|
||||
admin = bool(acl.get('admin'))
|
||||
manage_plugins = bool(acl.get('managePlugins'))
|
||||
show_plugin_management_links = admin or manage_plugins
|
||||
|
||||
lpma_plugin_installed = os.path.isdir('/usr/local/CyberCP/limitedPhpmyAdmin')
|
||||
|
||||
lpma_has_cpuser_grant = False
|
||||
if lpma_plugin_installed:
|
||||
try:
|
||||
from django.apps import apps
|
||||
|
||||
if apps.is_installed('limitedPhpmyAdmin'):
|
||||
from limitedPhpmyAdmin.models import LimitedPhpmyAdminGrant
|
||||
|
||||
lpma_has_cpuser_grant = LimitedPhpmyAdminGrant.objects.filter(
|
||||
enabled=True,
|
||||
subject_type='cpuser',
|
||||
administrator_id=uid,
|
||||
).exists()
|
||||
except Exception:
|
||||
lpma_has_cpuser_grant = False
|
||||
|
||||
show_lpma_sidebar_link = lpma_plugin_installed and (
|
||||
show_plugin_management_links or lpma_has_cpuser_grant
|
||||
)
|
||||
show_plugins_menu = show_plugin_management_links or (
|
||||
lpma_has_cpuser_grant and lpma_plugin_installed
|
||||
)
|
||||
grant_only_lpma_sidebar = (
|
||||
lpma_has_cpuser_grant and lpma_plugin_installed and not show_plugin_management_links
|
||||
)
|
||||
|
||||
extra = []
|
||||
if show_lpma_sidebar_link:
|
||||
extra.append(
|
||||
{
|
||||
'url': '/plugins/limitedPhpmyAdmin/',
|
||||
'label_key': 'limited_phpmyadmin_sidebar',
|
||||
}
|
||||
)
|
||||
|
||||
defaults.update(
|
||||
{
|
||||
'lpma_plugin_installed': lpma_plugin_installed,
|
||||
'lpma_has_cpuser_grant': lpma_has_cpuser_grant,
|
||||
'show_plugin_management_links': show_plugin_management_links,
|
||||
'show_lpma_sidebar_link': show_lpma_sidebar_link,
|
||||
'show_plugins_menu': show_plugins_menu,
|
||||
'grant_only_lpma_sidebar': grant_only_lpma_sidebar,
|
||||
'plugin_sidebar_extra_links': extra,
|
||||
'lpma_sidebar_url': '/plugins/limitedPhpmyAdmin/',
|
||||
}
|
||||
)
|
||||
return defaults
|
||||
except Exception:
|
||||
return defaults
|
||||
@@ -2396,8 +2396,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if admin or managePlugins %}
|
||||
{% if managePlugins and not admin %}
|
||||
{% if show_plugins_menu %}
|
||||
{% if grant_only_lpma_sidebar or managePlugins and not admin %}
|
||||
<div class="section-header">{% trans "PLUGINS" %}</div>
|
||||
{% endif %}
|
||||
<a href="#" class="menu-item" onclick="toggleSubmenu('plugins-submenu', this); return false;">
|
||||
@@ -2408,12 +2408,19 @@
|
||||
<i class="fas fa-chevron-right chevron"></i>
|
||||
</a>
|
||||
<div id="plugins-submenu" class="submenu">
|
||||
{% if show_plugin_management_links %}
|
||||
<a href="{% url 'installed' %}" class="menu-item">
|
||||
<span>Installed</span>
|
||||
</a>
|
||||
<a href="{% url 'installed' %}?view=store" class="menu-item">
|
||||
<span>{% trans "Plugin Store" %}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if show_lpma_sidebar_link %}
|
||||
<a href="{{ lpma_sidebar_url }}" class="menu-item">
|
||||
<span>{% trans "Limited phpMyAdmin" %}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -165,6 +165,72 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: var(--bg-hover, #eef0ff);
|
||||
border: 1px solid var(--border-color, #c7c9f0);
|
||||
color: var(--text-primary, #2f3640);
|
||||
}
|
||||
|
||||
.alert-info a {
|
||||
color: var(--accent-color, #5b5fcf);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.version-section {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #2f3640);
|
||||
}
|
||||
|
||||
.version-section:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.version-meta-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
margin-bottom: 14px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color, #e8e9ff);
|
||||
background: var(--bg-hover, #f8f9ff);
|
||||
}
|
||||
|
||||
.version-meta-row-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary, #8893a7);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.version-meta-row-value {
|
||||
font-size: 14px;
|
||||
color: var(--text-primary, #2f3640);
|
||||
word-break: break-word;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.version-meta-row-value code,
|
||||
.version-sha {
|
||||
font-family: 'SF Mono', Monaco, monospace;
|
||||
font-size: 13px;
|
||||
background: var(--bg-secondary, white);
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color, #e8e9ff);
|
||||
}
|
||||
|
||||
.version-meta-row-value a {
|
||||
font-weight: 600;
|
||||
color: var(--accent-color, #5b5fcf);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* Info grid */
|
||||
.info-grid {
|
||||
display: grid;
|
||||
@@ -269,7 +335,21 @@
|
||||
{% if Notecheck %}
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<span>{% trans "Note: Latest commit does not match, please upgrade CyberPanel." %} <a href="https://cyberpanel.net/KnowledgeBase/home/upgrading-cyberpanel/">Learn how to upgrade CyberPanel.</a></span>
|
||||
<span>
|
||||
{% blocktrans with cmp=notecheck_compare_remote %}Note: The current commit does not match the latest commit on {{ cmp }}. Please upgrade CyberPanel.{% endblocktrans %}
|
||||
<a href="https://cyberpanel.net/KnowledgeBase/home/upgrading-cyberpanel/" target="_blank" rel="noopener noreferrer">{% trans "Learn how to upgrade CyberPanel." %}</a>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if show_fork_block and fork_drift_upstream %}
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<span>{% trans "Info: Your fork branch tip on GitHub differs from the official upstream tip on the same branch. That is normal if you have not merged upstream yet." %}</span>
|
||||
</div>
|
||||
{% elif local_behind_official and not Notecheck %}
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<span>{% trans "Your commit differs from the latest on the official CyberPanel repository (usmannasir/cyberpanel). This is informational if you intentionally track a fork." %}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -294,8 +374,9 @@
|
||||
</div>
|
||||
|
||||
<div class="content-card">
|
||||
<h2 class="card-title">Version Information</h2>
|
||||
<h2 class="card-title">{% trans "Version Information" %}</h2>
|
||||
|
||||
<p class="version-section">{% trans "This installation" %}</p>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">{% trans "Current Version" %}</span>
|
||||
@@ -306,22 +387,61 @@
|
||||
<span class="info-value">{{ build }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{% trans "Current Commit" %}</span>
|
||||
<span class="info-value">{{ Currentcomt }}</span>
|
||||
<span class="info-label">{% trans "Current commit" %}</span>
|
||||
<span class="info-value" title="{{ Currentcomt }}">
|
||||
{% if Currentcomt_short %}<code class="version-sha">{{ Currentcomt_short }}</code>{% else %}—{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{% trans "Latest Version" %}</span>
|
||||
<span class="info-label">{% trans "Published latest version" %}</span>
|
||||
<span class="info-value">{{ latestVersion }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{% trans "Latest Build" %}</span>
|
||||
<span class="info-label">{% trans "Published latest build" %}</span>
|
||||
<span class="info-value">{{ latestBuild }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">{% trans "Latest Commit" %}</span>
|
||||
<span class="info-value">{{ latestcomit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="version-section">{% trans "Git remotes and branch tips" %}</p>
|
||||
<div class="version-meta-row">
|
||||
<span class="version-meta-row-label">{% trans "Origin (git remote)" %}</span>
|
||||
<span class="version-meta-row-value">{{ remote_display|default:"—" }}</span>
|
||||
</div>
|
||||
<div class="version-meta-row">
|
||||
<span class="version-meta-row-label">{% trans "Tracking branch" %}</span>
|
||||
<span class="version-meta-row-value"><code class="version-sha">{{ branch_ref }}</code></span>
|
||||
</div>
|
||||
|
||||
{% if fork_remote_commit %}
|
||||
<div class="version-meta-row">
|
||||
<span class="version-meta-row-label">{% trans "Your fork — latest on GitHub" %}</span>
|
||||
<span class="version-meta-row-value">
|
||||
<code class="version-sha" title="{{ fork_remote_commit }}">{{ fork_remote_commit_short }}</code>
|
||||
{% if fork_commit_url %}
|
||||
<a href="{{ fork_commit_url }}" target="_blank" rel="noopener noreferrer">{% trans "View commit" %}</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if upstream_commit %}
|
||||
<div class="version-meta-row">
|
||||
<span class="version-meta-row-label">{% trans "Official upstream (usmannasir/cyberpanel) — latest on GitHub" %}</span>
|
||||
<span class="version-meta-row-value">
|
||||
<code class="version-sha" title="{{ upstream_commit }}">{{ upstream_commit_short }}</code>
|
||||
{% if upstream_commit_url %}
|
||||
<a href="{{ upstream_commit_url }}" target="_blank" rel="noopener noreferrer">{% trans "View commit" %}</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% elif latestcomit and not fork_remote_commit and not on_dev_branch %}
|
||||
<div class="version-meta-row">
|
||||
<span class="version-meta-row-label">{% trans "Latest commit on origin (comparison)" %}</span>
|
||||
<span class="version-meta-row-value">
|
||||
<code class="version-sha" title="{{ latestcomit }}">{{ latestcomit_short }}</code>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="content-card" ng-hide="upgradelogBox">
|
||||
|
||||
@@ -55,6 +55,34 @@ def _version_compare(a, b):
|
||||
return 0
|
||||
|
||||
|
||||
def _parse_github_origin(remote_out):
|
||||
"""Return (owner, repo) or (None, None) if unparseable."""
|
||||
if not remote_out:
|
||||
return None, None
|
||||
m = re.search(r'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$', remote_out.strip())
|
||||
if not m:
|
||||
return None, None
|
||||
owner, repo = m.group(1), m.group(2).rstrip('.git')
|
||||
return owner, repo
|
||||
|
||||
|
||||
def _github_branch_tip_sha(owner, repo, branch_ref):
|
||||
"""First commit SHA on branch via GitHub API, or empty string on failure."""
|
||||
try:
|
||||
u = 'https://api.github.com/repos/%s/%s/commits?sha=%s' % (owner, repo, branch_ref)
|
||||
r = requests.get(u, timeout=10)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
if not data:
|
||||
return ''
|
||||
sha = data[0].get('sha', '') or ''
|
||||
return sha
|
||||
except (requests.RequestException, IndexError, KeyError, TypeError) as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
'[versionManagment] GitHub API %s/%s @%s failed: %s' % (owner, repo, branch_ref, str(e)))
|
||||
return ''
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def renderBase(request):
|
||||
template = 'baseTemplate/homePage.html'
|
||||
@@ -67,49 +95,8 @@ def renderBase(request):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def versionManagement(request):
|
||||
currentVersion = VERSION
|
||||
currentBuild = str(BUILD)
|
||||
|
||||
getVersion = requests.get('https://cyberpanel.net/version.txt')
|
||||
latest = getVersion.json()
|
||||
latestVersion = latest['version']
|
||||
latestBuild = latest['build']
|
||||
branch_ref = 'v%s.%s' % (latestVersion, latestBuild)
|
||||
|
||||
notechk = True
|
||||
Currentcomt = ''
|
||||
latestcomit = ''
|
||||
|
||||
if _version_compare(currentVersion, latestVersion) > 0:
|
||||
notechk = False
|
||||
else:
|
||||
remote_cmd = 'git -C /usr/local/CyberCP remote get-url origin 2>/dev/null || true'
|
||||
remote_out = ProcessUtilities.outputExecutioner(remote_cmd)
|
||||
is_usmannasir = 'usmannasir/cyberpanel' in (remote_out or '')
|
||||
|
||||
if is_usmannasir:
|
||||
u = "https://api.github.com/repos/usmannasir/cyberpanel/commits?sha=%s" % branch_ref
|
||||
logging.CyberCPLogFileWriter.writeToFile(u)
|
||||
try:
|
||||
r = requests.get(u, timeout=10)
|
||||
r.raise_for_status()
|
||||
latestcomit = r.json()[0]['sha']
|
||||
except (requests.RequestException, IndexError, KeyError) as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile('[versionManagement] GitHub API failed: %s' % str(e))
|
||||
head_cmd = 'git -C /usr/local/CyberCP rev-parse HEAD 2>/dev/null || true'
|
||||
Currentcomt = ProcessUtilities.outputExecutioner(head_cmd).rstrip('\n')
|
||||
if latestcomit and Currentcomt == latestcomit:
|
||||
notechk = False
|
||||
else:
|
||||
notechk = False
|
||||
|
||||
template = 'baseTemplate/versionManagment.html'
|
||||
finalData = {'build': currentBuild, 'currentVersion': currentVersion, 'latestVersion': latestVersion,
|
||||
'latestBuild': latestBuild, 'latestcomit': latestcomit, "Currentcomt": Currentcomt,
|
||||
"Notecheck": notechk}
|
||||
|
||||
proc = httpProc(request, template, finalData, 'versionManagement')
|
||||
return proc.render()
|
||||
"""Legacy entrypoint; same UI as versionManagment (URLs use versionManagment)."""
|
||||
return versionManagment(request)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -297,6 +284,13 @@ def versionManagment(request):
|
||||
latestcomit = ''
|
||||
latestVersion = '0'
|
||||
latestBuild = '0'
|
||||
upstream_latest_sha = ''
|
||||
fork_latest_sha = ''
|
||||
show_fork_block = False
|
||||
fork_display = ''
|
||||
fork_commit_url = ''
|
||||
upstream_commit_url = ''
|
||||
fork_drift_upstream = False
|
||||
|
||||
on_dev_branch = (currentVersion == '2.5.5' and currentBuild == 'dev')
|
||||
|
||||
@@ -311,58 +305,109 @@ def versionManagment(request):
|
||||
if on_dev_branch:
|
||||
latestVersion, latestBuild = '2.5.5', 'dev'
|
||||
|
||||
# Dev branch: compare against v2.5.5-dev, show dev version info
|
||||
if on_dev_branch:
|
||||
branch_ref = 'v2.5.5-dev'
|
||||
latestVersion, latestBuild = '2.5.5', 'dev'
|
||||
else:
|
||||
branch_ref = 'v%s.%s' % (latestVersion, latestBuild)
|
||||
|
||||
# Always fetch local HEAD for display
|
||||
head_cmd = 'git -C /usr/local/CyberCP rev-parse HEAD 2>/dev/null || true'
|
||||
Currentcomt = (ProcessUtilities.outputExecutioner(head_cmd) or '').rstrip('\n')
|
||||
|
||||
remote_cmd = 'git -C /usr/local/CyberCP remote get-url origin 2>/dev/null || true'
|
||||
remote_out = ProcessUtilities.outputExecutioner(remote_cmd)
|
||||
is_usmannasir = 'usmannasir/cyberpanel' in (remote_out or '')
|
||||
remote_out = (ProcessUtilities.outputExecutioner(remote_cmd) or '')
|
||||
origin_owner, origin_repo = _parse_github_origin(remote_out)
|
||||
if origin_owner and origin_repo:
|
||||
remote_display = '%s/%s' % (origin_owner, origin_repo)
|
||||
is_official = (origin_owner.lower() == 'usmannasir' and origin_repo.lower() == 'cyberpanel')
|
||||
show_fork_block = not is_official
|
||||
if show_fork_block:
|
||||
fork_display = remote_display
|
||||
else:
|
||||
remote_display = (remote_out.strip() or '—')
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
'[versionManagment] Unparseable git origin, upstream-only UI: %s'
|
||||
% (remote_out[:200] if remote_out else '(empty)'))
|
||||
show_fork_block = False
|
||||
|
||||
upstream_latest_sha = _github_branch_tip_sha('usmannasir', 'cyberpanel', branch_ref)
|
||||
latestcomit = upstream_latest_sha
|
||||
if upstream_latest_sha:
|
||||
upstream_commit_url = 'https://github.com/usmannasir/cyberpanel/commit/%s' % upstream_latest_sha
|
||||
|
||||
if show_fork_block and origin_owner and origin_repo:
|
||||
fork_latest_sha = _github_branch_tip_sha(origin_owner, origin_repo, branch_ref)
|
||||
if fork_latest_sha:
|
||||
fork_commit_url = 'https://github.com/%s/%s/commit/%s' % (
|
||||
origin_owner, origin_repo, fork_latest_sha)
|
||||
|
||||
if (fork_latest_sha and upstream_latest_sha and fork_latest_sha != upstream_latest_sha):
|
||||
fork_drift_upstream = True
|
||||
|
||||
# Stable: newer than cyberpanel.net = up to date; dev: compare commits
|
||||
if not on_dev_branch and notechk and _version_compare(currentVersion, latestVersion) > 0:
|
||||
notechk = False
|
||||
elif notechk:
|
||||
# Dev branch: always use usmannasir v2.5.5-dev as canonical "latest"
|
||||
# Forks: use usmannasir for Latest Commit so all dev users compare to same upstream
|
||||
fetch_branch = branch_ref if (is_usmannasir or on_dev_branch) else None
|
||||
if fetch_branch:
|
||||
u = "https://api.github.com/repos/usmannasir/cyberpanel/commits?sha=%s" % fetch_branch
|
||||
logging.CyberCPLogFileWriter.writeToFile(u)
|
||||
try:
|
||||
r = requests.get(u, timeout=10)
|
||||
r.raise_for_status()
|
||||
latestcomit = r.json()[0]['sha']
|
||||
if Currentcomt and latestcomit and Currentcomt == latestcomit:
|
||||
notechk = False
|
||||
except (requests.RequestException, IndexError, KeyError) as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile('[versionManagment] GitHub API failed: %s' % str(e))
|
||||
elif not on_dev_branch:
|
||||
# Stable fork: fetch from fork's branch
|
||||
m = re.search(r'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$', (remote_out or '').strip())
|
||||
if m:
|
||||
owner, repo = m.group(1), m.group(2).rstrip('.git')
|
||||
try:
|
||||
u = "https://api.github.com/repos/%s/%s/commits?sha=%s" % (owner, repo, branch_ref)
|
||||
r = requests.get(u, timeout=10)
|
||||
r.raise_for_status()
|
||||
latestcomit = r.json()[0]['sha']
|
||||
if Currentcomt and latestcomit and Currentcomt == latestcomit:
|
||||
notechk = False
|
||||
except (requests.RequestException, IndexError, KeyError):
|
||||
pass
|
||||
cur = (Currentcomt or '').strip().lower()
|
||||
if show_fork_block:
|
||||
fk = (fork_latest_sha or '').strip().lower()
|
||||
up = (upstream_latest_sha or '').strip().lower()
|
||||
if fk:
|
||||
notechk = not (bool(cur) and cur == fk)
|
||||
elif up:
|
||||
notechk = not (bool(cur) and cur == up)
|
||||
else:
|
||||
notechk = False
|
||||
else:
|
||||
up = (upstream_latest_sha or '').strip().lower()
|
||||
if up:
|
||||
notechk = not (bool(cur) and cur == up)
|
||||
else:
|
||||
notechk = False
|
||||
|
||||
is_usmannasir = not show_fork_block
|
||||
fork_remote_commit = fork_latest_sha if show_fork_block else ''
|
||||
upstream_commit = upstream_latest_sha
|
||||
notecheck_compare_remote = fork_display if show_fork_block else 'usmannasir/cyberpanel'
|
||||
local_behind_official = bool(
|
||||
on_dev_branch and Currentcomt and upstream_commit and Currentcomt != upstream_commit)
|
||||
|
||||
def _short_sha(commit_hash):
|
||||
if not commit_hash or len(commit_hash) < 7:
|
||||
return commit_hash or ''
|
||||
return commit_hash[:7]
|
||||
|
||||
template = 'baseTemplate/versionManagment.html'
|
||||
finalData = {'build': currentBuild, 'currentVersion': currentVersion, 'latestVersion': latestVersion,
|
||||
'latestBuild': latestBuild, 'latestcomit': latestcomit, "Currentcomt": Currentcomt,
|
||||
"Notecheck": notechk}
|
||||
finalData = {
|
||||
'build': currentBuild,
|
||||
'currentVersion': currentVersion,
|
||||
'latestVersion': latestVersion,
|
||||
'latestBuild': latestBuild,
|
||||
'latestcomit': latestcomit,
|
||||
'Currentcomt': Currentcomt,
|
||||
'Notecheck': notechk,
|
||||
'show_fork_block': show_fork_block,
|
||||
'tracking_branch': branch_ref,
|
||||
'branch_ref': branch_ref,
|
||||
'fork_display': fork_display,
|
||||
'fork_latest_sha': fork_latest_sha,
|
||||
'upstream_latest_sha': upstream_latest_sha,
|
||||
'fork_commit_url': fork_commit_url,
|
||||
'upstream_commit_url': upstream_commit_url,
|
||||
'fork_drift_upstream': fork_drift_upstream,
|
||||
'remote_display': remote_display,
|
||||
'is_usmannasir': is_usmannasir,
|
||||
'fork_remote_commit': fork_remote_commit,
|
||||
'upstream_commit': upstream_commit,
|
||||
'notecheck_compare_remote': notecheck_compare_remote,
|
||||
'local_behind_official': local_behind_official,
|
||||
'on_dev_branch': on_dev_branch,
|
||||
'Currentcomt_short': _short_sha(Currentcomt),
|
||||
'latestcomit_short': _short_sha(latestcomit),
|
||||
'fork_remote_commit_short': _short_sha(fork_remote_commit),
|
||||
'upstream_commit_short': _short_sha(upstream_commit),
|
||||
'fork_latest_sha_short': _short_sha(fork_latest_sha),
|
||||
'upstream_latest_sha_short': _short_sha(upstream_latest_sha),
|
||||
}
|
||||
|
||||
proc = httpProc(request, template, finalData, 'versionManagement')
|
||||
return proc.render()
|
||||
|
||||
@@ -102,6 +102,15 @@ Pre_Upgrade_Setup_Repository
|
||||
Pre_Upgrade_Setup_Git_URL
|
||||
Pre_Upgrade_Required_Components
|
||||
Main_Upgrade
|
||||
Sync_CyberCP_To_Latest
|
||||
CYBERPANEL_GIT_SYNC_OK=0
|
||||
if Sync_CyberCP_To_Latest; then
|
||||
CYBERPANEL_GIT_SYNC_OK=1
|
||||
else
|
||||
echo -e "\e[31m[$(date +"%Y-%m-%d %H:%M:%S")] ERROR: Git sync of /usr/local/CyberCP to origin/$Branch_Name failed. Panel code may be outdated. See /var/log/cyberpanel_upgrade_debug.log and /etc/cyberpanel/last_git_sync_failed\e[0m" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
fi
|
||||
export CYBERPANEL_GIT_SYNC_OK
|
||||
Post_Upgrade_System_Tweak
|
||||
Post_Install_Display_Final_Info
|
||||
if [[ "$CYBERPANEL_GIT_SYNC_OK" -ne 1 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -39,6 +39,174 @@ class FirewallManager:
|
||||
def __init__(self, request = None):
|
||||
self.request = request
|
||||
|
||||
@staticmethod
|
||||
def _normalize_banned_ip_key(ip):
|
||||
return (ip or '').strip().lower()
|
||||
|
||||
def _autoban_duration_seconds(self, duration):
|
||||
duration_map = {'1h': 3600, '24h': 86400, '7d': 604800, '30d': 2592000}
|
||||
d = (duration or '').strip().lower()
|
||||
if d == 'permanent' or not d:
|
||||
return None
|
||||
return duration_map.get(d, 86400)
|
||||
|
||||
def _autoban_log_expired(self, log, current_time):
|
||||
"""True if timed ban from AutoBanLog is past expiry (permanent never expires here)."""
|
||||
d = (log.ban_duration or '').strip().lower()
|
||||
if d == 'permanent' or not d:
|
||||
return False
|
||||
sec = self._autoban_duration_seconds(log.ban_duration)
|
||||
if sec is None:
|
||||
return False
|
||||
if not log.banned_at:
|
||||
return False
|
||||
try:
|
||||
ts = int(log.banned_at.timestamp())
|
||||
except Exception:
|
||||
return False
|
||||
return (ts + sec) <= int(current_time)
|
||||
|
||||
def _merge_autoban_security_alerts_logs(self, active_banned_ips, current_time):
|
||||
"""
|
||||
List Auto Ban Security Alerts log rows whose IP is not already in the firewall
|
||||
merged list (DB + JSON), so /firewall/#banned-ips matches the plugin settings view.
|
||||
"""
|
||||
try:
|
||||
from django.apps import apps
|
||||
|
||||
if not apps.is_installed('autoBanSecurityAlerts'):
|
||||
return
|
||||
from autoBanSecurityAlerts.models import AutoBanLog
|
||||
except Exception:
|
||||
return
|
||||
|
||||
seen = set()
|
||||
for row in active_banned_ips:
|
||||
k = self._normalize_banned_ip_key(row.get('ip') or row.get('ip_address'))
|
||||
if k:
|
||||
seen.add(k)
|
||||
|
||||
try:
|
||||
autoban_by_ip = {}
|
||||
for log in AutoBanLog.objects.order_by('-banned_at'):
|
||||
k = self._normalize_banned_ip_key(str(log.ip_address))
|
||||
if not k:
|
||||
continue
|
||||
if k not in autoban_by_ip:
|
||||
autoban_by_ip[k] = log
|
||||
|
||||
for _k, log in autoban_by_ip.items():
|
||||
ip_key = self._normalize_banned_ip_key(str(log.ip_address))
|
||||
if ip_key in seen:
|
||||
continue
|
||||
if self._autoban_log_expired(log, current_time):
|
||||
continue
|
||||
banned_on_str = 'N/A'
|
||||
if log.banned_at:
|
||||
try:
|
||||
banned_on_str = log.banned_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
except Exception:
|
||||
banned_on_str = str(log.banned_at)
|
||||
expires_display = 'Never'
|
||||
dlow = (log.ban_duration or '').strip().lower()
|
||||
if dlow != 'permanent' and dlow:
|
||||
sec = self._autoban_duration_seconds(log.ban_duration)
|
||||
if sec is not None and log.banned_at:
|
||||
try:
|
||||
from datetime import datetime
|
||||
|
||||
exp_ts = int(log.banned_at.timestamp()) + sec
|
||||
expires_display = datetime.fromtimestamp(exp_ts).strftime(
|
||||
'%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
except Exception:
|
||||
expires_display = 'Never'
|
||||
active_banned_ips.append(
|
||||
{
|
||||
'id': 'ablog-%s' % log.pk,
|
||||
'ip': str(log.ip_address),
|
||||
'reason': log.ban_reason or '',
|
||||
'duration': log.ban_duration or 'permanent',
|
||||
'banned_on': banned_on_str,
|
||||
'expires': expires_display,
|
||||
'active': True,
|
||||
'ban_source': 'autoBanSecurityAlerts',
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
'getBannedIPs: AutoBanSecurityAlerts merge failed: %s' % str(e)
|
||||
)
|
||||
|
||||
def _remove_autoban_log_by_suffix_id(self, userID, ablog_id_str):
|
||||
"""Handle unban when UI sends id like ablog-123 (orphan log-only row)."""
|
||||
try:
|
||||
pk = int(str(ablog_id_str).split('-', 1)[1])
|
||||
except Exception:
|
||||
final_dic = {'status': 0, 'error_message': 'Invalid auto-ban log id'}
|
||||
return HttpResponse(json.dumps(final_dic), content_type='application/json')
|
||||
try:
|
||||
admin = Administrator.objects.get(pk=userID)
|
||||
if admin.acl.adminStatus != 1:
|
||||
return ACLManager.loadError()
|
||||
from django.apps import apps
|
||||
|
||||
if not apps.is_installed('autoBanSecurityAlerts'):
|
||||
final_dic = {'status': 0, 'error_message': 'Auto Ban plugin is not installed'}
|
||||
return HttpResponse(json.dumps(final_dic), content_type='application/json')
|
||||
from autoBanSecurityAlerts.models import AutoBanLog
|
||||
|
||||
log = AutoBanLog.objects.filter(pk=pk).first()
|
||||
if not log:
|
||||
final_dic = {'status': 0, 'error_message': 'Auto-ban log entry not found'}
|
||||
return HttpResponse(json.dumps(final_dic), content_type='application/json')
|
||||
ip = str(log.ip_address).strip()
|
||||
admin0 = Administrator.objects.filter(acl__adminStatus=1).first()
|
||||
if not admin0:
|
||||
final_dic = {'status': 0, 'error_message': 'No admin user found'}
|
||||
return HttpResponse(json.dumps(final_dic), content_type='application/json')
|
||||
|
||||
result = self.removeBannedIP(admin0.pk, {'ip': ip})
|
||||
raw = getattr(result, 'content', None)
|
||||
parsed = {}
|
||||
if raw is not None:
|
||||
try:
|
||||
if isinstance(raw, bytes):
|
||||
raw = raw.decode('utf-8', errors='replace')
|
||||
parsed = json.loads(raw)
|
||||
except Exception:
|
||||
parsed = {}
|
||||
err_raw = (parsed.get('error_message') or parsed.get('error') or '').strip()
|
||||
err_msg = err_raw.lower()
|
||||
|
||||
if parsed.get('status') == 1:
|
||||
log.delete()
|
||||
return HttpResponse(
|
||||
json.dumps(
|
||||
{
|
||||
'status': 1,
|
||||
'message': parsed.get('message', 'IP unbanned successfully'),
|
||||
}
|
||||
),
|
||||
content_type='application/json',
|
||||
)
|
||||
if 'not found' in err_msg:
|
||||
log.delete()
|
||||
return HttpResponse(
|
||||
json.dumps(
|
||||
{
|
||||
'status': 1,
|
||||
'message': 'Log cleared (ban was already removed or not found in firewall)',
|
||||
}
|
||||
),
|
||||
content_type='application/json',
|
||||
)
|
||||
final_dic = {'status': 0, 'error_message': err_raw or 'Failed to remove ban'}
|
||||
return HttpResponse(json.dumps(final_dic), content_type='application/json')
|
||||
except BaseException as msg:
|
||||
final_dic = {'status': 0, 'error_message': str(msg)}
|
||||
return HttpResponse(json.dumps(final_dic), content_type='application/json')
|
||||
|
||||
def _load_banned_ips_store(self):
|
||||
"""
|
||||
Load banned IPs from the primary store, falling back to legacy path.
|
||||
@@ -2094,6 +2262,9 @@ class FirewallManager:
|
||||
'active': True
|
||||
})
|
||||
|
||||
# Auto Ban Security Alerts: show log-only / plugin-visible bans on same list as firewall UI
|
||||
self._merge_autoban_security_alerts_logs(active_banned_ips, current_time)
|
||||
|
||||
# Optional server-side search: filter by IP or reason (case-insensitive substring)
|
||||
# Normalize: strip query and compare against stripped IP/reason so "1.2.3.4" matches stored " 1.2.3.4 "
|
||||
search_q = (data.get('search') or data.get('q') or '').strip() if data else ''
|
||||
@@ -2296,6 +2467,9 @@ class FirewallManager:
|
||||
return ACLManager.loadError()
|
||||
|
||||
banned_ip_id = data.get('id')
|
||||
if isinstance(banned_ip_id, str) and banned_ip_id.startswith('ablog-'):
|
||||
return self._remove_autoban_log_by_suffix_id(userID, banned_ip_id)
|
||||
|
||||
requested_ip = (data.get('ip') or '').strip()
|
||||
ip_to_unban = None
|
||||
|
||||
@@ -2388,6 +2562,9 @@ class FirewallManager:
|
||||
return ACLManager.loadError()
|
||||
|
||||
banned_ip_id = data.get('id')
|
||||
if isinstance(banned_ip_id, str) and banned_ip_id.startswith('ablog-'):
|
||||
return self._remove_autoban_log_by_suffix_id(userID, banned_ip_id)
|
||||
|
||||
requested_ip = (data.get('ip') or '').strip()
|
||||
|
||||
try:
|
||||
|
||||
134
plogical/lpma_policy_read.inc.php
Normal file
134
plogical/lpma_policy_read.inc.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* Load Limited phpMyAdmin UI policy (strict mode + blocked preference tabs).
|
||||
* Primary: pluginState (writable by cyberpanel). Fallbacks for older installs.
|
||||
*/
|
||||
function lpma_read_limited_policy(): array
|
||||
{
|
||||
$defaultBlocked = [
|
||||
'manage' => true,
|
||||
'two_factor' => true,
|
||||
'features' => true,
|
||||
'sql' => true,
|
||||
'navigation' => true,
|
||||
'main_panel' => true,
|
||||
'export' => true,
|
||||
'import' => true,
|
||||
];
|
||||
$policy = [
|
||||
'strict_mode' => true,
|
||||
'blocked_tabs' => $defaultBlocked,
|
||||
];
|
||||
$paths = [
|
||||
'/usr/local/CyberCP/pluginState/limited_phpmyadmin_policy.json',
|
||||
'/var/lib/cyberpanel-panelstate/limited_phpmyadmin_policy.json',
|
||||
'/etc/cyberpanel/limited_phpmyadmin_policy.json',
|
||||
];
|
||||
foreach ($paths as $policyPath) {
|
||||
if (! @is_readable($policyPath)) {
|
||||
continue;
|
||||
}
|
||||
$raw = @file_get_contents($policyPath);
|
||||
if ($raw === false) {
|
||||
continue;
|
||||
}
|
||||
$decoded = @json_decode($raw, true);
|
||||
if (! is_array($decoded)) {
|
||||
continue;
|
||||
}
|
||||
$policy['strict_mode'] = isset($decoded['strict_mode']) ? (bool) $decoded['strict_mode'] : true;
|
||||
if (isset($decoded['blocked_tabs']) && is_array($decoded['blocked_tabs'])) {
|
||||
foreach ($defaultBlocked as $k => $_v) {
|
||||
$policy['blocked_tabs'][$k] = isset($decoded['blocked_tabs'][$k])
|
||||
? (bool) $decoded['blocked_tabs'][$k]
|
||||
: true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $policy;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if a cpma_* request to this application route must be turned away (Settings prefs + main menu targets).
|
||||
* Does not block table browse at route "/sql" (that is Browse, not the SQL runner).
|
||||
*/
|
||||
function lpma_cpma_route_blocked(string $requestedRoute, array $policy): bool
|
||||
{
|
||||
if ($requestedRoute === '') {
|
||||
return false;
|
||||
}
|
||||
$bt = $policy['blocked_tabs'] ?? [];
|
||||
$blocked = static function (string $k) use ($bt): bool {
|
||||
return (($bt[$k] ?? true) === true);
|
||||
};
|
||||
|
||||
if (strpos($requestedRoute, '/preferences') === 0) {
|
||||
$routeToTab = [
|
||||
'/preferences/manage' => 'manage',
|
||||
'/preferences/two-factor' => 'two_factor',
|
||||
'/preferences/features' => 'features',
|
||||
'/preferences/sql' => 'sql',
|
||||
'/preferences/navigation' => 'navigation',
|
||||
'/preferences/main-panel' => 'main_panel',
|
||||
'/preferences/export' => 'export',
|
||||
'/preferences/import' => 'import',
|
||||
];
|
||||
if (isset($routeToTab[$requestedRoute])) {
|
||||
return $blocked($routeToTab[$requestedRoute]);
|
||||
}
|
||||
|
||||
return (($policy['strict_mode'] ?? true) === true);
|
||||
}
|
||||
|
||||
if ($blocked('sql')) {
|
||||
if (preg_match('#^/(server|database|table)/sql$#', $requestedRoute) === 1) {
|
||||
return true;
|
||||
}
|
||||
if ($requestedRoute === '/database/multi-table-query' || $requestedRoute === '/database/qbe') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($blocked('export') && preg_match('#^/(server|database|table)/export$#', $requestedRoute) === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($blocked('import') && preg_match('#^/(server|database|table)/import$#', $requestedRoute) === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($blocked('main_panel')) {
|
||||
if (
|
||||
$requestedRoute === '/server/databases'
|
||||
|| $requestedRoute === '/server/variables'
|
||||
|| $requestedRoute === '/server/collations'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (strpos($requestedRoute, '/server/status') === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($blocked('features')) {
|
||||
if (
|
||||
$requestedRoute === '/server/engines'
|
||||
|| $requestedRoute === '/server/plugins'
|
||||
|| $requestedRoute === '/server/binlog'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
$requestedRoute === '/database/designer'
|
||||
|| $requestedRoute === '/database/central-columns'
|
||||
|| $requestedRoute === '/database/tracking'
|
||||
|| $requestedRoute === '/table/tracking'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -3,10 +3,28 @@
|
||||
|
||||
define("PMA_SIGNON_INDEX", 1);
|
||||
|
||||
require_once __DIR__ . '/lpma_policy_read.inc.php';
|
||||
|
||||
try {
|
||||
define('PMA_SIGNON_SESSIONNAME', 'SignonSession');
|
||||
define('PMA_DISABLE_SSL_PEER_VALIDATION', TRUE);
|
||||
|
||||
function lpma_set_strict_cookie($enabled) {
|
||||
$opts = array(
|
||||
'expires' => $enabled ? (time() + 86400) : (time() - 86400),
|
||||
'path' => '/phpmyadmin/',
|
||||
'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
);
|
||||
setcookie('PMA_LPMA_STRICT', $enabled ? '1' : '', $opts);
|
||||
}
|
||||
|
||||
function lpma_global_strict_mode_enabled() {
|
||||
$p = lpma_read_limited_policy();
|
||||
return ! empty($p['strict_mode']);
|
||||
}
|
||||
|
||||
// Handle both GET and POST parameters for token and username
|
||||
$token = isset($_POST['token']) ? $_POST['token'] : (isset($_GET['token']) ? $_GET['token'] : null);
|
||||
$username = isset($_POST['username']) ? $_POST['username'] : (isset($_GET['username']) ? $_GET['username'] : null);
|
||||
@@ -32,6 +50,7 @@ try {
|
||||
echo '<script>document.getElementById("redirectForm").submit();</script>';
|
||||
|
||||
} else if (isset($_POST['logout']) || isset($_GET['logout'])) {
|
||||
lpma_set_strict_cookie(false);
|
||||
session_name(PMA_SIGNON_SESSIONNAME);
|
||||
@session_start();
|
||||
$_SESSION = array();
|
||||
@@ -47,9 +66,14 @@ try {
|
||||
|
||||
$username = htmlspecialchars($_POST['username'], ENT_QUOTES, 'UTF-8');
|
||||
$password = $_POST['password'];
|
||||
$strictMode = (isset($_POST['lpma_strict']) && $_POST['lpma_strict'] === '1');
|
||||
$isLimitedUser = (strpos($username, 'cpma_') === 0);
|
||||
$host = isset($_POST['host']) ? trim($_POST['host']) : '127.0.0.1';
|
||||
if ($host === 'localhost') { $host = '127.0.0.1'; }
|
||||
|
||||
$effectiveStrictMode = ($strictMode || lpma_global_strict_mode_enabled()) && $isLimitedUser;
|
||||
lpma_set_strict_cookie($effectiveStrictMode);
|
||||
|
||||
$_SESSION['PMA_single_signon_user'] = $username;
|
||||
$_SESSION['PMA_single_signon_password'] = $password;
|
||||
$_SESSION['PMA_single_signon_host'] = $host;
|
||||
|
||||
@@ -4577,17 +4577,22 @@ class Migration(migrations.Migration):
|
||||
# Change to parent directory
|
||||
os.chdir('/usr/local')
|
||||
|
||||
# Remove old CyberCP directory
|
||||
# Remove old CyberCP directory (quarantine if rmtree fails — e.g. busy files)
|
||||
if os.path.exists('CyberCP'):
|
||||
Upgrade.stdOut("Removing old CyberCP directory...")
|
||||
try:
|
||||
shutil.rmtree('CyberCP')
|
||||
Upgrade.stdOut("Old CyberCP directory removed successfully.")
|
||||
except Exception as e:
|
||||
Upgrade.stdOut(f"Error removing CyberCP directory: {str(e)}")
|
||||
# Try to restore backup if removal fails
|
||||
Upgrade.restoreCriticalFiles(backup_dir, backed_up_files)
|
||||
return 0, 'Failed to remove old CyberCP directory'
|
||||
except OSError as e:
|
||||
Upgrade.stdOut("rmtree failed (%s); quarantining old CyberCP..." % str(e))
|
||||
quarantine = '/usr/local/CyberCP.legacy.%s' % int(time.time())
|
||||
try:
|
||||
shutil.move('CyberCP', quarantine)
|
||||
Upgrade.stdOut("Moved old tree to %s" % quarantine)
|
||||
except OSError as e2:
|
||||
Upgrade.stdOut(f"Error removing or moving CyberCP directory: {str(e2)}")
|
||||
Upgrade.restoreCriticalFiles(backup_dir, backed_up_files)
|
||||
return 0, 'Failed to remove or quarantine old CyberCP directory'
|
||||
|
||||
# Clone the new repository (use CYBERPANEL_GIT_USER for fork, e.g. master3395)
|
||||
git_user = os.environ.get('CYBERPANEL_GIT_USER', 'master3395')
|
||||
@@ -4612,10 +4617,15 @@ class Migration(migrations.Migration):
|
||||
if os.path.exists('CyberCP'):
|
||||
try:
|
||||
shutil.rmtree('CyberCP')
|
||||
except Exception as e:
|
||||
Upgrade.stdOut("Error removing CyberCP: %s" % str(e))
|
||||
Upgrade.restoreCriticalFiles(backup_dir, backed_up_files)
|
||||
return 0, 'Failed to remove CyberCP for upstream clone'
|
||||
except OSError as e:
|
||||
Upgrade.stdOut("rmtree failed (%s); quarantining..." % str(e))
|
||||
quarantine = '/usr/local/CyberCP.legacy.%s' % int(time.time())
|
||||
try:
|
||||
shutil.move('CyberCP', quarantine)
|
||||
except OSError as e2:
|
||||
Upgrade.stdOut("Error removing CyberCP: %s" % str(e2))
|
||||
Upgrade.restoreCriticalFiles(backup_dir, backed_up_files)
|
||||
return 0, 'Failed to remove CyberCP for upstream clone'
|
||||
command = 'git clone https://github.com/%s/cyberpanel CyberCP' % upstream_user
|
||||
if not Upgrade.executioner(command, command, 1):
|
||||
Upgrade.restoreCriticalFiles(backup_dir, backed_up_files)
|
||||
@@ -6581,7 +6591,31 @@ slowlog = /var/log/php{version}-fpm-slow.log
|
||||
# execPath = execPath + " removeCSF"
|
||||
# Upgrade.executioner(execPath, 'fix csf if there', 0)
|
||||
|
||||
Upgrade.downloadAndUpgrade(versionNumbring, branch)
|
||||
clone_attempts = int(os.environ.get('CYBERPANEL_UPGRADE_CLONE_ATTEMPTS', '2'))
|
||||
if clone_attempts < 1:
|
||||
clone_attempts = 1
|
||||
download_ok = False
|
||||
download_err = None
|
||||
for attempt in range(1, clone_attempts + 1):
|
||||
ok, download_err = Upgrade.downloadAndUpgrade(versionNumbring, branch)
|
||||
if ok:
|
||||
download_ok = True
|
||||
break
|
||||
Upgrade.stdOut(
|
||||
'downloadAndUpgrade failed (attempt %d/%d): %s'
|
||||
% (attempt, clone_attempts, download_err or 'unknown'), 0)
|
||||
if attempt < clone_attempts:
|
||||
Upgrade.stdOut('Retrying full CyberPanel tree replacement in 5 seconds...', 0)
|
||||
time.sleep(5)
|
||||
if not download_ok:
|
||||
Upgrade.stdOut(
|
||||
'CRITICAL: CyberPanel code update failed after %d attempt(s): %s. '
|
||||
'The panel was not replaced with the new branch. Check logs and disk permissions on /usr/local.'
|
||||
% (clone_attempts, download_err or 'unknown'), 0)
|
||||
if Upgrade.SoftUpgrade == 0:
|
||||
Upgrade.executioner('systemctl start lscpd', 'Start LSCPD after failed code update', 0)
|
||||
sys.exit(1)
|
||||
|
||||
versionNumbring = Upgrade.downloadLink()
|
||||
Upgrade.download_install_phpmyadmin()
|
||||
Upgrade.downoad_and_install_raindloop()
|
||||
|
||||
@@ -3,10 +3,39 @@
|
||||
|
||||
define("PMA_SIGNON_INDEX", 1);
|
||||
|
||||
// Policy helper ships in plogical/ (same layout as phpmyadmin index.php)
|
||||
$_lpma_policy = dirname(dirname(__DIR__)) . '/plogical/lpma_policy_read.inc.php';
|
||||
if (is_readable($_lpma_policy)) {
|
||||
require_once $_lpma_policy;
|
||||
} elseif (is_readable(__DIR__ . '/lpma_policy_read.inc.php')) {
|
||||
require_once __DIR__ . '/lpma_policy_read.inc.php';
|
||||
} else {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'phpMyAdmin sign-on is misconfigured: lpma_policy_read.inc.php is missing.';
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
define('PMA_SIGNON_SESSIONNAME', 'SignonSession');
|
||||
define('PMA_DISABLE_SSL_PEER_VALIDATION', TRUE);
|
||||
|
||||
function lpma_set_strict_cookie($enabled) {
|
||||
$opts = array(
|
||||
'expires' => $enabled ? (time() + 86400) : (time() - 86400),
|
||||
'path' => '/phpmyadmin/',
|
||||
'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
);
|
||||
setcookie('PMA_LPMA_STRICT', $enabled ? '1' : '', $opts);
|
||||
}
|
||||
|
||||
function lpma_global_strict_mode_enabled() {
|
||||
$p = lpma_read_limited_policy();
|
||||
return ! empty($p['strict_mode']);
|
||||
}
|
||||
|
||||
// Handle both GET and POST parameters for token and username
|
||||
$token = isset($_POST['token']) ? $_POST['token'] : (isset($_GET['token']) ? $_GET['token'] : null);
|
||||
$username = isset($_POST['username']) ? $_POST['username'] : (isset($_GET['username']) ? $_GET['username'] : null);
|
||||
@@ -32,6 +61,7 @@ try {
|
||||
echo '<script>document.getElementById("redirectForm").submit();</script>';
|
||||
|
||||
} else if (isset($_POST['logout']) || isset($_GET['logout'])) {
|
||||
lpma_set_strict_cookie(false);
|
||||
session_name(PMA_SIGNON_SESSIONNAME);
|
||||
@session_start();
|
||||
$_SESSION = array();
|
||||
@@ -47,9 +77,14 @@ try {
|
||||
|
||||
$username = htmlspecialchars($_POST['username'], ENT_QUOTES, 'UTF-8');
|
||||
$password = $_POST['password'];
|
||||
$strictMode = (isset($_POST['lpma_strict']) && $_POST['lpma_strict'] === '1');
|
||||
$isLimitedUser = (strpos($username, 'cpma_') === 0);
|
||||
$host = isset($_POST['host']) ? trim($_POST['host']) : '127.0.0.1';
|
||||
if ($host === 'localhost') { $host = '127.0.0.1'; }
|
||||
|
||||
$effectiveStrictMode = ($strictMode || lpma_global_strict_mode_enabled()) && $isLimitedUser;
|
||||
lpma_set_strict_cookie($effectiveStrictMode);
|
||||
|
||||
$_SESSION['PMA_single_signon_user'] = $username;
|
||||
$_SESSION['PMA_single_signon_password'] = $password;
|
||||
$_SESSION['PMA_single_signon_host'] = $host;
|
||||
|
||||
@@ -30,6 +30,8 @@ echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Running: $CP_PYTHON upgrade.py $Branch_N
|
||||
|
||||
# Export Git user so upgrade.py clones from the same repo (master3395 or --repo override)
|
||||
export CYBERPANEL_GIT_USER="${Git_User:-master3395}"
|
||||
# Retry full /usr/local/CyberCP re-clone this many times if download/checkout fails (default 2)
|
||||
export CYBERPANEL_UPGRADE_CLONE_ATTEMPTS="${CYBERPANEL_UPGRADE_CLONE_ATTEMPTS:-2}"
|
||||
|
||||
# Run from directory that contains upgrade.py (downloaded by Pre_Upgrade_Required_Components)
|
||||
for d in /root/cyberpanel_upgrade_tmp /usr/local/CyberCP; do
|
||||
|
||||
@@ -1,30 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
# CyberPanel upgrade – sync CyberCP to latest commit. Sourced by cyberpanel_upgrade.sh.
|
||||
# If local edits or untracked files block checkout, stash/quarantine and reset --hard to match origin.
|
||||
|
||||
Sync_CyberCP_To_Latest() {
|
||||
local SYNC_STATE_FILE="/etc/cyberpanel/last_git_sync_failed"
|
||||
mkdir -p /etc/cyberpanel
|
||||
rm -f "$SYNC_STATE_FILE" 2>/dev/null || true
|
||||
|
||||
if [[ ! -d /usr/local/CyberCP/.git ]]; then
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] No .git in /usr/local/CyberCP, skipping sync" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Syncing /usr/local/CyberCP to latest commit for branch: $Branch_Name" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
# Backup production settings so sync does not overwrite DB credentials / local config
|
||||
|
||||
if [[ -f /usr/local/CyberCP/CyberCP/settings.py ]]; then
|
||||
cp /usr/local/CyberCP/CyberCP/settings.py /tmp/cyberpanel_settings_backup.py
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Backed up settings.py for restore after sync" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
fi
|
||||
(
|
||||
cd /usr/local/CyberCP
|
||||
git fetch origin 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
if git show-ref -q "refs/remotes/origin/$Branch_Name"; then
|
||||
git checkout -B "$Branch_Name" "origin/$Branch_Name" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
else
|
||||
git checkout "$Branch_Name" 2>/dev/null || true
|
||||
git pull --ff-only origin "$Branch_Name" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log || true
|
||||
|
||||
if ! cd /usr/local/CyberCP; then
|
||||
echo "1" > "$SYNC_STATE_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local remote_ref="origin/$Branch_Name"
|
||||
local fetch_rc sync_ok=0
|
||||
|
||||
git fetch origin "$Branch_Name" 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
fetch_rc="${PIPESTATUS[0]}"
|
||||
if [[ "$fetch_rc" -ne 0 ]]; then
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] ERROR: git fetch origin $Branch_Name failed (exit $fetch_rc)" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
echo "1" > "$SYNC_STATE_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! git show-ref -q "refs/remotes/$remote_ref"; then
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] ERROR: remote ref $remote_ref not found after fetch" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
echo "1" > "$SYNC_STATE_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_cp_force_sync_to_remote_tip() {
|
||||
local ref="$1" quarantine dest rel
|
||||
git checkout -B "$Branch_Name" "$ref" >> /var/log/cyberpanel_upgrade_debug.log 2>&1
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
)
|
||||
local sync_code=$?
|
||||
# Merge production DATABASES into branch settings.py so DB creds survive without stripping
|
||||
# new INSTALLED_APPS (webmail, emailDelivery, etc.). Blind full restore broke integrated webmail.
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Checkout blocked; stashing tracked+untracked changes and resetting --hard to $ref" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
git stash push -u -m "cyberpanel-upgrade-auto-$(date +%s)" >> /var/log/cyberpanel_upgrade_debug.log 2>&1 || true
|
||||
git reset --hard "$ref" >> /var/log/cyberpanel_upgrade_debug.log 2>&1
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
quarantine="/root/cyberpanel_git_untracked_quarantine_$(date +%s)"
|
||||
mkdir -p "$quarantine"
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] reset --hard still blocked; moving untracked paths to $quarantine" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
# shellcheck disable=SC2162
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
\?\?*)
|
||||
rel="${line#??}"
|
||||
rel="${rel#"${rel%%[![:space:]]*}"}"
|
||||
if [[ -n "$rel" ]] && [[ -e "$rel" ]]; then
|
||||
dest="$quarantine/$rel"
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
mv "$rel" "$dest" 2>/dev/null || mv "$rel" "$quarantine/" 2>/dev/null || true
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done < <(git status --porcelain)
|
||||
git reset --hard "$ref" >> /var/log/cyberpanel_upgrade_debug.log 2>&1
|
||||
return $?
|
||||
}
|
||||
|
||||
if _cp_force_sync_to_remote_tip "$remote_ref"; then
|
||||
local local_h remote_h
|
||||
local_h=$(git rev-parse HEAD 2>/dev/null)
|
||||
remote_h=$(git rev-parse "$remote_ref" 2>/dev/null)
|
||||
if [[ -n "$local_h" ]] && [[ -n "$remote_h" ]] && [[ "$local_h" == "$remote_h" ]]; then
|
||||
sync_ok=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$sync_ok" -ne 1 ]]; then
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] ERROR: /usr/local/CyberCP HEAD does not match $remote_ref after forced sync (local=$(git rev-parse HEAD 2>/dev/null), remote=$(git rev-parse "$remote_ref" 2>/dev/null))" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
echo "1" > "$SYNC_STATE_FILE"
|
||||
if [[ -f /tmp/cyberpanel_settings_backup.py ]]; then
|
||||
cp /tmp/cyberpanel_settings_backup.py /usr/local/CyberCP/CyberCP/settings.py
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -f /tmp/cyberpanel_settings_backup.py ]] && [[ -f /usr/local/CyberCP/upgrade_modules/merge_production_settings.py ]]; then
|
||||
python3 /usr/local/CyberCP/upgrade_modules/merge_production_settings.py /tmp/cyberpanel_settings_backup.py /usr/local/CyberCP/CyberCP/settings.py 2>&1 | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
if [[ "${PIPESTATUS[0]}" -eq 0 ]]; then
|
||||
@@ -36,22 +103,18 @@ Sync_CyberCP_To_Latest() {
|
||||
cp /tmp/cyberpanel_settings_backup.py /usr/local/CyberCP/CyberCP/settings.py
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Restored settings.py after sync (merge script missing)" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
fi
|
||||
# LiteSpeed serves /static/ from public/static/; ensure it has latest baseTemplate static files (e.g. dashboard JS)
|
||||
|
||||
if [[ -d /usr/local/CyberCP/public/static ]] && [[ -d /usr/local/CyberCP/baseTemplate/static/baseTemplate ]]; then
|
||||
rsync -a /usr/local/CyberCP/baseTemplate/static/baseTemplate/ /usr/local/CyberCP/public/static/baseTemplate/ 2>/dev/null || \
|
||||
cp -r /usr/local/CyberCP/baseTemplate/static/baseTemplate/* /usr/local/CyberCP/public/static/baseTemplate/ 2>/dev/null || true
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Synced baseTemplate static to public/static" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
fi
|
||||
# Firewall UI (firewall.js) – so Firewall Rules and Banned IPs load correct layout and Modify buttons
|
||||
if [[ -d /usr/local/CyberCP/public/static ]] && [[ -f /usr/local/CyberCP/firewall/static/firewall/firewall.js ]]; then
|
||||
mkdir -p /usr/local/CyberCP/public/static/firewall
|
||||
cp -f /usr/local/CyberCP/firewall/static/firewall/firewall.js /usr/local/CyberCP/public/static/firewall/ 2>/dev/null && \
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Synced firewall static to public/static" | tee -a /var/log/cyberpanel_upgrade_debug.log || true
|
||||
fi
|
||||
if [[ $sync_code -eq 0 ]]; then
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Sync completed. Current HEAD: $(git -C /usr/local/CyberCP rev-parse HEAD 2>/dev/null || echo 'unknown')" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
else
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Sync returned code $sync_code (non-fatal)" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
fi
|
||||
|
||||
echo -e "[$(date +"%Y-%m-%d %H:%M:%S")] Sync completed. Current HEAD: $(git -C /usr/local/CyberCP rev-parse HEAD 2>/dev/null || echo 'unknown')" | tee -a /var/log/cyberpanel_upgrade_debug.log
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -22,7 +22,12 @@ _b " ▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒███ ▒▒▒▒▒▒
|
||||
_b " ███ ▒███"
|
||||
_b " ▒▒██████"
|
||||
_b " ▒▒▒▒▒▒"
|
||||
_b " *** UPGRADE COMPLETED SUCCESSFULLY! ***"
|
||||
if [[ "${CYBERPANEL_GIT_SYNC_OK:-1}" -eq 1 ]]; then
|
||||
_b " *** UPGRADE COMPLETED SUCCESSFULLY! ***"
|
||||
else
|
||||
_b " *** UPGRADE FINISHED BUT GIT SYNC FAILED - /usr/local/CyberCP MAY BE OUTDATED ***"
|
||||
_b " See /var/log/cyberpanel_upgrade_debug.log (exit code will be non-zero)"
|
||||
fi
|
||||
_b ""
|
||||
_bl
|
||||
|
||||
|
||||
@@ -98,7 +98,9 @@ class IMAPClient:
|
||||
def _folder_type(self, folder_name):
|
||||
"""Identify special folder type for UI (icons, sidebar grouping).
|
||||
|
||||
CyberPanel/Dovecot uses INBOX.* names; accounts may also use Spam, Trash, etc.
|
||||
CyberPanel/Dovecot uses INBOX.* names, but some accounts also have
|
||||
INBOX.spam, Trash, Archive, etc. Classify those so they are not treated
|
||||
as generic user folders.
|
||||
"""
|
||||
fn = (folder_name or '').strip()
|
||||
if not fn:
|
||||
|
||||
@@ -5,7 +5,8 @@ from loginSystem.views import loadLoginPage
|
||||
from .webmailManager import WebmailManager
|
||||
|
||||
|
||||
# --- Page Views ---
|
||||
# ── Page Views ────────────────────────────────────────────────
|
||||
|
||||
def loadWebmail(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -23,7 +24,8 @@ def loadLogin(request):
|
||||
return wm.loadLogin()
|
||||
|
||||
|
||||
# --- Auth APIs ---
|
||||
# ── Auth APIs ─────────────────────────────────────────────────
|
||||
|
||||
def apiLogin(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -70,7 +72,8 @@ def apiSwitchAccount(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Folder APIs ---
|
||||
# ── Folder APIs ───────────────────────────────────────────────
|
||||
|
||||
def apiListFolders(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -111,7 +114,8 @@ def apiDeleteFolder(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Message APIs ---
|
||||
# ── Message APIs ──────────────────────────────────────────────
|
||||
|
||||
def apiListMessages(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -152,7 +156,8 @@ def apiGetAttachment(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Action APIs ---
|
||||
# ── Action APIs ───────────────────────────────────────────────
|
||||
|
||||
def apiSendMessage(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -223,7 +228,8 @@ def apiMarkFlagged(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Contact APIs ---
|
||||
# ── Contact APIs ──────────────────────────────────────────────
|
||||
|
||||
def apiListContacts(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -323,7 +329,8 @@ def apiImportRulesFromSnappymail(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Sieve Rule APIs ---
|
||||
# ── Sieve Rule APIs ──────────────────────────────────────────
|
||||
|
||||
def apiListRules(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -374,7 +381,8 @@ def apiActivateRules(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Settings APIs ---
|
||||
# ── Settings APIs ─────────────────────────────────────────────
|
||||
|
||||
def apiGetSettings(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -395,7 +403,8 @@ def apiSaveSettings(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Image Proxy ---
|
||||
# ── Image Proxy ───────────────────────────────────────────────
|
||||
|
||||
def apiProxyImage(request):
|
||||
try:
|
||||
wm = WebmailManager(request)
|
||||
@@ -404,7 +413,8 @@ def apiProxyImage(request):
|
||||
return _error_response(e)
|
||||
|
||||
|
||||
# --- Helpers ---
|
||||
# ── Helpers ───────────────────────────────────────────────────
|
||||
|
||||
def _error_response(e):
|
||||
data = {'status': 0, 'error_message': str(e)}
|
||||
return HttpResponse(json.dumps(data), content_type='application/json')
|
||||
|
||||
Reference in New Issue
Block a user