Update to 7.10.0

[+] Add Mail Server to the left menu.
[+] Add Mail Server - WebMail (requires Mail Server upgrade 5.1)
[+] Add WP Toolkit Plugin, Theme installation.
[+] Add Mail Server Login info.
[+] Add Mail Server WebMail One-click Login (requires Mail Server upgrade 5.2).
[+] Redesigned Security - Firewall.

[*] Optimized Mass Mail sending speed.
[*] Optimized the CPU usage problem of aaPanel startup service.

[-] Fix apache application, renew SSL issue.
[-] Fix Let's Encrypt account registeration failed.
[-] Fix openlitespeed using capital letters on domain name caused the website cannot be accessed issue.
[-] Fix problem of failure to reset root password in some versions of MariaDB.
[-] Fix an error in modifying Permission in some cases of MySQL.
[-] Fix the Website category display problem.
This commit is contained in:
Jack
2024-08-09 09:57:59 +08:00
parent 82036d47c0
commit 1459724ba3
617 changed files with 8213 additions and 1398 deletions
+178 -37
View File
@@ -70,6 +70,7 @@ class acme_v2:
_conf_file = 'config/letsencrypt.json'
_conf_file_v2 = 'config/letsencrypt_v2.json'
_request_type = 'curl'
_stop_rp_file = '{}/data/stop_rp_when_renew_ssl.pl'.format(public.get_panel_path())
def __init__(self):
if not os.path.exists(self._conf_file_v2) and os.path.exists(self._conf_file):
@@ -1752,62 +1753,187 @@ fullchain.pem Paste into certificate input box
if not os.path.exists(args.auth_to):
return public.return_msg_gettext(False, 'Invalid site directory, please check if the specified site exists!')
try:
# 检查认证环境
check_result = self.check_auth_env(args)
if check_result:
return check_result
# 检查认证环境
check_result = self.check_auth_env(args)
if check_result:
return check_result
return self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to)
except:
pass
finally:
self.turnon_redirect_proxy_httptohttps(args)
return self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to)
# 检查认证环境
def check_auth_env(self,args):
for domain in json.loads(args.domains):
if public.checkIp(domain): continue
if domain.find('*.') != -1 and args.auth_type in ['http','tls']:
return public.return_msg_gettext(False, 'Universal domain names cannot apply for certificates using file verification!')
data = public.M('sites').where('id=?', (args.id,)).find()
if not data:
return public.return_msg_gettext(False, "Website lost, unable to continue applying for certificate")
else:
args.siteName = data['name']
site_type = data["project_type"]
use_nginx_conf_to_auth = False
if args.auth_type in ['http', 'tls'] and public.get_webserver() == "nginx": # nginx 在lua验证和可重启的
if self.can_use_lua_for_site(args.siteName, site_type):
use_nginx_conf_to_auth = True
else:
if self.can_use_if_for_file_check(args.siteName, site_type):
use_nginx_conf_to_auth = True
def turnon_redirect_proxy_httptohttps(self, args):
import panelSite
s = panelSite.panelSite()
if args.auth_type in ['http', 'tls'] and use_nginx_conf_to_auth is False:
if not 'siteName' in args:
args.siteName = public.M('sites').where('id=?', (args.id,)).getField('name')
args.sitename = args.siteName
self.turnon_redirect(args, s)
self.turnon_proxy(args, s)
self.turnon_httptohttps(args, s)
public.serviceReload()
def turnon_httptohttps(self, args, s):
conf_file = '{}/data/stop_httptohttps.pl'.format(public.get_panel_path())
if os.path.exists(conf_file):
write_log('|-Turning on http to https')
s.HttpToHttps(args)
try:
os.remove(conf_file)
except:
pass
def turnon_proxy(self, args, s):
conf_file = '{}/data/stop_p_tmp.pl'.format(public.get_panel_path())
if not os.path.exists(conf_file):
return
write_log('|-Turning on proxy')
conf = json.loads(public.readFile(conf_file))
data = s.GetProxyList(args)
for x in data:
if x['sitename'] not in conf:
continue
if x['proxyname'] not in conf[x['sitename']]:
continue
args.type = 1
args.advanced = x['advanced']
args.cache = x['cache']
args.cachetime = x['cachetime']
args.proxydir = x['proxydir']
args.proxyname = x['proxyname']
args.proxysite = x['proxysite']
args.sitename = x['sitename']
args.subfilter = json.dumps(x['subfilter'])
args.todomain = x['todomain']
s.ModifyProxy(args)
try:
os.remove(conf_file)
except:
pass
def turnon_redirect(self, args, s):
conf_file = '{}/data/stop_r_tmp.pl'.format(public.get_panel_path())
if not os.path.exists(conf_file):
return
write_log('|-Turning on redirection')
conf = json.loads(public.readFile(conf_file))
data = s.GetRedirectList(args)
for x in data:
if x['sitename'] not in conf:
continue
if x['redirectname'] not in conf[x['sitename']]:
continue
args.type = 1
args.sitename = x['sitename']
args.holdpath = x['holdpath']
args.redirectname = x['redirectname']
args.redirecttype = x['redirecttype']
args.domainorpath = x['domainorpath']
args.redirectpath = x['redirectpath']
args.redirectdomain = json.dumps(x['redirectdomain'])
args.tourl = x['tourl']
s.ModifyRedirect(args)
try:
os.remove(conf_file)
except:
pass
# 检查认证环境
def check_auth_env(self, args, check=None):
if not check:
return
for domain in json.loads(args.domains):
if public.checkIp(domain): continue
if domain.find('*.') != -1 and args.auth_type in ['http', 'tls']:
raise public.return_msg_gettext(False,
'Pan domain names cannot apply for a certificate using [File Verification]!')
import panelSite
s = panelSite.panelSite()
if args.auth_type in ['http', 'tls']:
try:
rp_conf = public.readFile(self._stop_rp_file)
try:
if rp_conf:
rp_conf = json.loads(rp_conf)
except:
write_log('|-Failed to parse configuration file')
if not 'siteName' in args:
args.siteName = public.M('sites').where('id=?', (args.id,)).getField('name')
args.sitename = args.siteName
data = s.GetRedirectList(args)
# 检查重定向是否开启
if type(data) == list:
redirect_tmp = {args.sitename: []}
for x in data:
if x['type']: return public.return_msg_gettext(False,
if rp_conf and x['sitename'] in rp_conf:
if str(x['type']) == '0':
continue
args.type = 0
args.sitename = x['sitename']
args.holdpath = x['holdpath']
args.redirectname = x['redirectname']
args.redirecttype = x['redirecttype']
args.domainorpath = x['domainorpath']
args.redirectpath = x['redirectpath']
args.redirectdomain = json.dumps(x['redirectdomain'])
args.tourl = x['tourl']
args.notreload = True
write_log("|- Turning off redirection {}".format(args.redirectname))
s.ModifyRedirect(args)
redirect_tmp[args.sitename].append(x['redirectname'])
else:
if x['type']: return public.return_msg_gettext(False,
'Your site has 301 Redirect onPlease turn it off first!')
if redirect_tmp[args.sitename]:
public.writeFile('{}/data/stop_r_tmp.pl'.format(public.get_panel_path()),
json.dumps(redirect_tmp))
data = s.GetProxyList(args)
# # 检查反向代理是否开启
# if type(data) == list:
# for x in data:
# if x['type']: return public.return_msg_gettext(False,
# 'Sites with reverse proxy turned on cannot apply for SSL!')
# 检查反向代理是否开启
if type(data) == list:
proxy_tmp = {args.sitename: []}
for x in data:
if rp_conf and x['sitename'] in rp_conf:
if str(x['type']) == '0':
continue
args.type = 0
args.advanced = x['advanced']
args.cache = x['cache']
args.cachetime = x['cachetime']
args.proxydir = x['proxydir']
args.proxyname = x['proxyname']
args.proxysite = x['proxysite']
args.sitename = x['sitename']
args.subfilter = json.dumps(x['subfilter'])
args.todomain = x['todomain']
args.notreload = True
s.ModifyProxy(args)
write_log("|- Turning off proxy {}".format(args.proxyname))
proxy_tmp[args.sitename].append(x['proxyname'])
else:
if x['type']: return public.return_msg_gettext(False,
'Sites with reverse proxy turned on cannot apply for SSL!')
if proxy_tmp[args.sitename]:
public.writeFile('{}/data/stop_p_tmp.pl'.format(public.get_panel_path()), json.dumps(proxy_tmp))
# 检查旧重定向是否开启
data = s.Get301Status(args)
if data['status']:
return public.return_msg_gettext(False,
'The website has been redirected, please close it before applying!')
#判断是否强制HTTPS
# 判断是否强制HTTPS
if s.IsToHttps(args.siteName):
return public.return_msg_gettext(False,
if os.path.exists(self._stop_rp_file):
if rp_conf and args.siteName in rp_conf:
write_log("|- Turning off http to https")
s.CloseToHttps(args)
public.writeFile('{}/data/stop_httptohttps.pl'.format(public.get_panel_path()), '')
else:
return public.return_msg_gettext(False,
'After configuring Force HTTPS, you cannot use [File Verification] to apply for a certificate!')
public.serviceReload()
except:
return False
else:
@@ -1884,7 +2010,7 @@ fullchain.pem Paste into certificate input box
skey_file = '{}/{}/privkey.pem'.format(cert_paths,c_name)
skey = public.readFile(skey_file)
if not skey: continue
if skey == pkey:
if skey == pkey or 1==1:
args.siteName = c_name
site_info = public.M('sites').where('name=?', c_name).find()
if not site_info or isinstance(site_info, str):
@@ -2176,7 +2302,17 @@ fullchain.pem Paste into certificate input box
n = 0
self.get_apis()
cert = None
args = public.to_dict_obj({})
for index in order_index:
args.domains = json.dumps(self._config['orders'][index]['domains'])
args.auth_type = self._config['orders'][index]['auth_type']
args.auth_to = self._config['orders'][index]['auth_to']
sitename = args.auth_to.split('/')[-1]
if not sitename:
sitename = self._config['orders'][index]['auth_to'].split('/')[-2]
args.siteName = sitename
write_log('|-Renew the visa certificate and start checking the environment')
self.check_auth_env(args, check=True)
n += 1
domains = _test_domains(self._config['orders'][index]['domains'], self._config['orders'][index]['auth_to'],self._config['orders'][index]['auth_type'])
if len(domains) == 0:
@@ -2188,6 +2324,11 @@ fullchain.pem Paste into certificate input box
(n, str(self._config['orders'][index]['domains']))))
write_log(public.get_msg_gettext('|-Creating order..'))
cert = self.renew_cert_to(self._config['orders'][index]['domains'],self._config['orders'][index]['auth_type'],self._config['orders'][index]['auth_to'],index)
# aapanel 用
try:
self.turnon_redirect_proxy_httptohttps(args)
except:
pass
return cert
except Exception as ex:
+1 -1
View File
@@ -27,7 +27,7 @@ class panelSetup:
if ua.find('spider') != -1 or g.ua.find('bot') != -1:
return abort(403)
g.version = '7.0.6'
g.version = '7.10.0'
g.title = public.GetConfigValue('title')
g.uri = request.path
g.debug = os.path.exists('data/debug.pl')
+13 -3
View File
@@ -828,7 +828,9 @@ SetLink
admin_user, my_host[0]))
mysql_obj.execute(
"ALTER USER `%s`@`%s` IDENTIFIED BY '%s'" % (admin_user, my_host[0], password))
elif m_version.find('10.5.') != -1 or m_version.find('10.4.') != -1:
# elif m_version.find('10.5.') != -1 or m_version.find('10.4.') != -1:
elif any(mariadb_ver in m_version for mariadb_ver in
['10.5.', '10.4.', '10.6.', '10.7.', '10.11.', '11.3.']):
accept = self.map_to_list(
mysql_obj.query("select Host from mysql.user where User='{}'".format(admin_user)))
for my_host in accept:
@@ -1534,7 +1536,11 @@ SetLink
if not 'Run' in result and result:
result['Run'] = int(time.time()) - int(result['Uptime'])
tmp = panelMysql.panelMysql().query('show master status')
m_version = public.readFile(public.GetConfigValue('setup_path') + '/mysql/version.pl')
if m_version.find('8.4') != -1 or m_version.find('9.0') != -1:
tmp = panelMysql.panelMysql().query('SHOW BINARY LOG STATUS')
else:
tmp = panelMysql.panelMysql().query('show master status')
try:
result['File'] = tmp[0][0]
@@ -1558,7 +1564,11 @@ SetLink
text = public.readFile(index_file)
rows = panelMysql.panelMysql().query("show master status")
m_version = public.readFile(public.GetConfigValue('setup_path') + '/mysql/version.pl')
if m_version.find('8.4') != -1 or m_version.find('9.0') != -1:
rows = panelMysql.panelMysql().query("SHOW BINARY LOG STATUS")
else:
rows = panelMysql.panelMysql().query("show master status")
current_log = ""
if not isinstance(rows, list):
+1
View File
@@ -212,6 +212,7 @@ class Sql():
self._close()
self.__DB_CONN.commit()
self.rm_lock()
return id
except Exception as ex:
return "error: " + str(ex)
+64 -27
View File
@@ -8,26 +8,43 @@
# +-------------------------------------------------------------------
# +-------------------------------------------------------------------
# | 宝塔HTTP通信库
# | 宝塔HTTP通信库
# +-------------------------------------------------------------------
import os,sys,re
import ssl
import public
import json
import socket
import requests
import config
import requests.packages.urllib3.util.connection as urllib3_conn
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class http:
_ip_type = None
def __init__(self):
self._ip_type = config.config().get_request_iptype()
self._ip_type = self.get_request_iptype()
def get(self,url,timeout = 60,headers = {},verify = False,type = 'python'):
def get_request_iptype(self, get=None):
'''
@name 获取云端请求线路
@author hwliang<2022-02-09>
@return auto/ipv4/ipv6
'''
v4_file = '{}/data/v4.pl'.format(public.get_panel_path())
if not os.path.exists(v4_file): return 'auto'
iptype = public.readFile(v4_file).strip()
if not iptype: return 'auto'
if iptype == '-4': return 'ipv4'
return 'ipv6'
def get(self,url,timeout = (6,60),headers = {},verify = False,type = 'python'):
url = self.quote(url)
# url = public.get_home_node(url)
if type in ['python','src','php']:
old_family = urllib3_conn.allowed_gai_family
try:
@@ -90,8 +107,9 @@ class http:
result = self._get_py3(url,timeout,headers,verify)
return result
def post(self,url,data,timeout = 60,headers = {},verify = False,type = 'python'):
def post(self,url,data,timeout = (6,60),headers = {},verify = False,type = 'python'):
url = self.quote(url)
# url = public.get_home_node(url)
if type in ['python','src','php']:
old_family = urllib3_conn.allowed_gai_family
try:
@@ -102,7 +120,6 @@ class http:
result = requests.post(url,data,timeout=timeout,headers=headers,verify=verify)
except:
public.print_log(public.get_error_info())
try:
# IPV6
if self._ip_type != 'ipv6':
@@ -219,6 +236,8 @@ class http:
#POST请求,通过CURL
def _post_curl(self,url,data,timeout,headers,verify):
if isinstance(timeout,tuple):
timeout = timeout[1]
headers_str = self._str_headers(headers)
pdata = self._str_post(data,headers_str)
_ssl_verify = ''
@@ -229,6 +248,8 @@ class http:
#POST请求,通过PHP
def _post_php(self,url,data,timeout,headers,verify):
if isinstance(timeout,tuple):
timeout = timeout[1]
php_version = self._get_php_version()
if not php_version:
raise Exception('No PHP version available!')
@@ -337,6 +358,8 @@ exit($header."\r\n\r\n".json_encode($body));
#GET请求,通过CURL
def _get_curl(self,url,timeout,headers,verify):
if isinstance(timeout,tuple):
timeout = timeout[1]
headers_str = self._str_headers(headers)
_ssl_verify = ''
if not verify: _ssl_verify = ' -k'
@@ -346,9 +369,12 @@ exit($header."\r\n\r\n".json_encode($body));
#GET请求,通过PHP
def _get_php(self,url,timeout,headers,verify):
if isinstance(timeout,tuple):
timeout = timeout[1]
php_version = self._get_php_version()
if not php_version:
raise Exception('No PHP version available!')
ip_type = ''
if self._ip_type == 'ipv6':
ip_type = 'curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);'
@@ -439,21 +465,21 @@ exit($header."\r\n\r\n".json_encode($body));
def _curl_format(self,req):
match = re.search("(.|\n)+\r\n\r\n",req)
if not match: return req,{},0
tmp = match.group().split("\r\n")
status_code = 0
from public.regexplib import search_http_response_status_line
for i in range(len(tmp) - 1):
m = search_http_response_status_line.search(tmp[i])
if not m:
continue
status_code = int(m.group(1))
break
body = req.replace(match.group(),'')
tmp = match.group()
body = req.replace(tmp,'')
try:
for line in tmp.split('\r\n'):
if line.find('HTTP/') != -1:
if line.find('Continue') != -1: continue
status_code = int(re.search(r'HTTP/[\d\.]+\s(\d+)',line).groups()[0])
break
if status_code == 100:
status_code = 200
except:
if body:
status_code = 200
else:
status_code = 0
return body,tmp,status_code
#构造适用于PHP的headers
@@ -475,13 +501,13 @@ exit($header."\r\n\r\n".json_encode($body));
str_pdata = ''
if headers.find('application/jose') != -1 \
or headers.find('application/josn') != -1:
if type(pdata) == dict:
if type(pdata) == dict:
pdata = json.dumps(pdata)
if type(pdata) == bytes:
pdata = pdata.decode('utf-8')
str_pdata += " -d '{}'".format(pdata)
return str_pdata
for key in pdata.keys():
str_pdata += " -F '{}={}'".format(key ,pdata[key])
return str_pdata
@@ -491,13 +517,14 @@ exit($header."\r\n\r\n".json_encode($body));
if 'Content-Type' in headers:
if headers['Content-Type'].find('application/jose') != -1 \
or headers['Content-Type'].find('application/josn') != -1:
if type(pdata) == dict:
if type(pdata) == dict:
pdata = json.dumps(pdata)
if type(pdata) == str:
pdata = pdata.encode('utf-8')
return pdata
return public.url_encode(pdata)
#响应头对象
class http_headers:
def __contains__(self, key):
@@ -527,6 +554,12 @@ class response:
self.format_headers(headers)
def format_headers(self,raw_headers):
if isinstance(raw_headers,str):
raw_headers = raw_headers.strip().split('\r\n')
if isinstance(raw_headers,dict):
for k in raw_headers.keys():
self.headers[k] = raw_headers[k]
return
raw = []
for h in raw_headers:
if not h: continue
@@ -563,7 +596,7 @@ __version__ = 1.0
#请请求方法
def get_stype(s_type):
if not s_type:
if not s_type:
s_type_file = '/www/server/panel/data/http_type.pl'
if os.path.exists(s_type_file):
tmp_type = public.readFile(s_type_file)
@@ -585,7 +618,7 @@ def get_headers(headers):
headers['User-Agent'] = DEFAULT_HEADERS['User-Agent']
return headers
def post(url,data = {},timeout = 60,headers = {},verify = False,s_type = None):
def post(url,data = {},timeout = (15,120),headers = {},verify = False,s_type = None):
'''
POST请求
@param [url] string URL地址
@@ -595,13 +628,15 @@ def post(url,data = {},timeout = 60,headers = {},verify = False,s_type = None):
@param [verify] bool 是否验证ssl证书 默认False
@param [s_type] string 请求方法 默认python 可选:curl或php
'''
if isinstance(timeout,list):
timeout = tuple(timeout)
p = http()
try:
return p.post(url,data,timeout,get_headers(headers),verify,get_stype(s_type))
except:
raise Exception(public.get_error_info())
def get(url,timeout = 60,headers = {},verify = False,s_type = None):
def get(url,timeout = (15,120),headers = {},verify = False,s_type = None):
'''
GET请求
@param [url] string URL地址
@@ -610,6 +645,8 @@ def get(url,timeout = 60,headers = {},verify = False,s_type = None):
@param [verify] bool 是否验证ssl证书 默认False
@param [s_type] string 请求方法 默认python 可选:curl或php
'''
if isinstance(timeout,list):
timeout = tuple(timeout)
p = http()
try:
return p.get(url,timeout,get_headers(headers),verify,get_stype(s_type))
+2 -2
View File
@@ -42,7 +42,7 @@ def control_init():
null_html()
remove_other()
deb_bashrc()
upgrade_gevent()
# upgrade_gevent()
upgrade_polkit()
#hide_docker()
rep_pyenv_link()
@@ -670,7 +670,7 @@ def files_set_mode():
recycle_list = public.get_recycle_bin_list()
for recycle_path in recycle_list:
m_paths.append([recycle_path,'','root',600,True])
m_paths.append([recycle_path,'','root',600,False])
for m in m_paths:
if not os.path.exists(m[0]): continue
+2 -1
View File
@@ -37,7 +37,8 @@ class panelAuth:
data['server_id'] = serverid
public.writeFile(userPath,json.dumps(data))
return data
except: return public.return_msg_gettext(False,'Please login with account first')
except:
return public.return_msg_gettext(False,'Please login with account first')
def create_plugin_other_order(self,get):
+3 -1
View File
@@ -425,6 +425,7 @@ class panelPlugin:
softList = public.load_soft_list(True if force == 1 else False)
if get and 'init' in get:
if softList:
if 'success' not in softList:
@@ -809,7 +810,7 @@ class panelPlugin:
import one_key_wp
one_key_wp.fast_cgi().set_nginx_conf()
one_key_wp.fast_cgi().set_nginx_init()
public.ExecShell("/etc/init.d/nginx start")
public.ExecShell("/etc/init.d/nginx reload")
return softList
#取首页软件列表
@@ -1093,6 +1094,7 @@ class panelPlugin:
softInfo["php_ini"] = "/usr/local/lsws/lsphp{}/etc/php.ini".format(v1)
else:
softInfo["php_ini"] = "/www/server/php/{}/etc/php.ini".format(v1)
return self.check_status(softInfo)
return False
+43
View File
@@ -0,0 +1,43 @@
from contextlib import contextmanager
import threading
# 存储所有已排序的线程锁
_local = threading.local()
@contextmanager
def acquire(*locks, timeout=-1):
'''
@name 避免死锁
@author Zhj
@param locks<[]threading.Lock> 线程锁
@param timeout<integer> 最长阻塞时间/
@return None
'''
# 升序排序
locks = sorted(locks, key=lambda x: id(x))
# 确保按顺序加锁
acquired = getattr(_local, 'acquired', [])
if timeout <= 0 and acquired and max(id(lock) for lock in acquired) >= id(locks[0]):
raise RuntimeError('Lock Order Violation')
# 加锁
acquired.extend(locks)
_local.acquired = acquired
try:
# 按顺序加锁
for lock in locks:
lock.acquire(timeout=timeout)
# 转出程序控制权
yield
finally:
# 倒序释放锁
for lock in reversed(locks):
try:
lock.release()
except: pass
del (acquired[-len(locks):],)
+130 -38
View File
@@ -5,17 +5,12 @@ import json, os, sys, time, re, socket, importlib, binascii, base64, io, string,
import gettext
import typing
import werkzeug.datastructures
from .exceptions import PanelError
from .validate import Param, trim_filter
from .regexplib import match_ipv4, match_ipv6, match_class_private_property, match_safe_path, match_based_host, \
find_url_root
find_url_root, search_sql_special_chars
from .tools import is_number
import collections
# Common structures
aap_t_simple_result = collections.namedtuple('aap_t_simple_result', ['success', 'msg'])
aap_t_mysql_dump_info = collections.namedtuple('aap_t_mysql_dump_info', ['db_name', 'file', 'dump_time'])
from .structures import aap_t_simple_result, aap_t_mysql_dump_info
es = gettext.translation('en', localedir='/www/server/panel/BTPanel/static/language', languages=['en'])
@@ -48,6 +43,24 @@ def M(table):
return sql.table(table)
# Easy Sqlite Toolkit for query
def S(table_name: typing.Optional[str] = None, db_name: str = 'default'):
from .sqlite_easy import Db
query = Db(db_name).query()
if table_name is not None and str(table_name).strip() != '':
query.table(str(table_name).strip())
return query
# Easy Sqlite Toolkit for connection
def SqliteConn(db_name: str = 'default'):
from .sqlite_easy import Db
return Db(db_name)
# 连接MYSQL数据库
def MysqlConn(db_name: typing.Optional[str] = None, db_user: str = 'root', db_pwd: typing.Optional[str] = None, db_host: str = 'localhost'):
from panel_mysql_v2 import PanelMysqlWithContext
@@ -1471,30 +1484,30 @@ def checkWebConfig(repair_num=2):
"ulimit -n 8192 ; {setup_path}/nginx/sbin/nginx -t -c {setup_path}/nginx/conf/nginx.conf".format(
setup_path=setup_path))
writeFile('/tmp/nginx_new.conf', readFile('/www/server/nginx/conf/nginx.conf'))
print_log('checkWebConfig--result:{}'.format(result))
# print_log('checkWebConfig--result:{}'.format(result))
searchStr = 'successful'
nginx_version = ExecShell("{}/nginx/sbin/nginx -v".format(setup_path))
print_log('nginx')
# print_log('nginx')
version_info = nginx_version[1]
elif web_s == 'apache':
print_log('apache')
# print_log('apache')
# else:
result = ExecShell("ulimit -n 8192 ; {setup_path}/apache/bin/apachectl -t".format(setup_path=setup_path))
searchStr = 'Syntax OK'
apache_version = ExecShell("{}/apache/bin/httpd -v".format(setup_path))
version_info = apache_version[1]
else:
print_log('other')
# print_log('other')
result = ["1", "1"]
searchStr = "1"
version_info = "Unknow"
print_log('checkWebConfig--result1:{}'.format(result))
# print_log('checkWebConfig--result1:{}'.format(result))
if result[1].find(
'the "listen ... http2" directive is deprecated, use the "http2" directive instead') != -1 and web_s == "nginx" and is_change_nginx_http2():
if repair_num > 0:
repair_num -= 1
change_nginx_http2()
print_log('nginx----1')
# print_log('nginx----1')
return checkWebConfig(repair_num)
if result[1].find(searchStr) == -1:
@@ -1503,14 +1516,14 @@ def checkWebConfig(repair_num=2):
if repair_num > 0:
repair_num -= 1
change_nginx_old_http2()
print_log('nginx----2')
# print_log('nginx----2')
return checkWebConfig(repair_num)
if result[1].find('[emerg] invalid parameter "quic" in') != -1 and web_s == "nginx" and not is_nginx_http3():
if repair_num > 0:
repair_num -= 1
remove_nginx_quic()
print_log('nginx----3')
# print_log('nginx----3')
return checkWebConfig(repair_num)
WriteLog("TYPE_SOFT", 'CONF_CHECK_ERR', (result[1],))
@@ -1525,9 +1538,9 @@ def checkWebConfig(repair_num=2):
0, result[1].split("\n")[0].strip())
except Exception as e:
err_collect(result[1], 0, result[1].split("\n")[0].strip())
print_log('nginx----4')
# print_log('nginx----4')
return result[1]
print_log('nginx----5')
# print_log('nginx----5')
return True
@@ -2326,6 +2339,31 @@ def get_os_version():
version = "{} (Py{}.{}.{})".format(version, v_info.major, v_info.minor, v_info.micro)
return xsssec(version)
#获取总大小
def get_size_total(paths = []):
data = {}
try:
if type(paths) == str:
paths = [paths]
n_list = []
for path in paths:
if os.path.exists(path):
n_list.append(path)
else:
data[path] = 0
if len(n_list) > 0:
shell = 'du -s {}'.format(' '.join(n_list).strip())
res = ExecShell(shell)[0]
for n in res.split("\n"):
tmp = n.split("\t")
if len(tmp) < 2: continue
data[tmp[1]] = int(tmp[0]) * 1024
except:pass
return data
# 取文件或目录大小
def get_path_size(path, exclude=[]):
@@ -2813,6 +2851,24 @@ def is_local():
return os.path.exists(s_file)
# Dump面板数据库结构+数据
def dump_panel_databases():
backup_path = '{}/data/db_backups'.format(get_panel_path())
if not os.path.exists(backup_path):
os.makedirs(backup_path, 0o755)
backup_databases = (
'default',
)
def row_check_func(row: str) -> bool:
return row.find('INSERT INTO "logs" ') < 0
for db_name in backup_databases:
with SqliteConn(db_name) as db:
db.dump('{}/{}.sql'.format(backup_path, db_name), row_check_func)
# 自动备份面板数据
def auto_backup_panel():
try:
@@ -2824,6 +2880,10 @@ def auto_backup_panel():
backup_path = b_path + '/' + day_date
backup_file = backup_path + '.zip'
if os.path.exists(backup_path) or os.path.exists(backup_file): return True
# 导出面板数据库结构+数据
dump_panel_databases()
ignore_default = ''
ignore_system = ''
max_size = 100 * 1024 * 1024
@@ -4425,6 +4485,22 @@ def get_local_ip():
except:
return '127.0.0.1'
def get_local_ip_2():
"""获取内网IP"""
import socket
s = None
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
return ip
except:
pass
finally:
if s is not None:
s.close()
return '127.0.0.1'
def create_logs():
import db
@@ -4773,19 +4849,6 @@ def is_debug():
return os.path.exists(debug_file)
class PanelError(Exception):
'''
@name 宝塔通用异常对像
@author hwliang<2021-06-25>
'''
def __init__(self, value):
self.value = value
def __str__(self):
return ("An error occurred while the panel was running: {}".format(str(self.value)))
def sys_path_append(path):
'''
@name 追加引用路径
@@ -6107,7 +6170,9 @@ def return_area(result, key):
tmps.append(data[key])
res = get_ips_area(tmps)
if 'status' in res: return result
if 'status' in res:
return result
for data in result:
if data[key] in res:
@@ -8099,6 +8164,7 @@ def run_plugin_v2(plugin_name: str, def_name: str, args: dict_obj):
if isinstance(res['msg'], str):
if res['msg'].find('Traceback ') != -1:
raise PanelError(res['msg'])
if isinstance(res, dict):
if 'status' in res and 'msg' in res:
status = 0 if res['status'] else -1
@@ -8107,9 +8173,11 @@ def run_plugin_v2(plugin_name: str, def_name: str, args: dict_obj):
else:
# 改返回
res = return_message(0, 0, res)
if isinstance(res, list):
if isinstance(res, (list, str, int)):
res = return_message(0, 0, res)
return res
# 加载插件列表与授权列表
def load_soft_list(force: bool = True):
local_cache_file = '{}/data/plugin_bin.pl'.format(get_panel_path())
@@ -8124,12 +8192,33 @@ def load_soft_list(force: bool = True):
url_headers = {"authorization": "bt {}".format(pdata['token'])}
pdata['environment_info'] = json.dumps(fetch_env_info())
resp = requests.post(cloudUrl, params=pdata, headers=url_headers, verify=False, timeout=10)
update_ok = False
ex = None
# 请求成功后将授权密文信息写入本地文件
if resp.status_code == 200:
with open(local_cache_file, 'w') as fp:
fp.write(resp.text)
# 默认重试5次
for _ in range(5):
try:
resp = requests.post(cloudUrl, params=pdata, headers=url_headers, verify=False, timeout=10)
# 请求成功后将授权密文信息写入本地文件
if resp.status_code == 200:
with open(local_cache_file, 'w') as fp:
fp.write(resp.text)
update_ok = True
break
except Exception as ex:
pass
# 本地缓存存在则让其读取本地缓存
if not update_ok:
update_ok = os.path.exists(local_cache_file) and os.path.getsize(local_cache_file) >= 10
# 本地缓存都不存在,如果捕获到异常则抛出异常,否则抛出获取软件列表与授权信息失败的提示
if not update_ok:
if ex is not None:
raise ex
raise PanelError(get_msg_gettext('Load softlist and authorizations failed, please wait for few moment and try again.'))
import PluginLoader
@@ -8275,3 +8364,6 @@ def make_panel_tmp_path_with_context():
shutil.rmtree(tmp_path)
# 处理SQL语句中的特殊字符
def escape_sql_str(s: str) -> str:
return search_sql_special_chars.sub(r'\\\g<0>', s)
+14
View File
@@ -9,3 +9,17 @@ class HintException(Exception):
# 无授权异常
class NoAuthorizationException(HintException):
pass
# 面板错误异常
class PanelError(Exception):
'''
@name 面板通用异常对像
@author hwliang<2021-06-25>
'''
def __init__(self, value):
self.value = value
def __str__(self):
return ("An error occurred while the panel was running: {}".format(str(self.value)))
+27
View File
@@ -0,0 +1,27 @@
from .acquire import acquire
import threading
import gc
_GC_DISABLE_COUNT = 0
_GC_DISABLE_COUNT_LOCK = threading.Lock()
# 停用GC
def gc_disable():
with acquire(_GC_DISABLE_COUNT_LOCK, timeout=1):
global _GC_DISABLE_COUNT
_GC_DISABLE_COUNT += 1
if _GC_DISABLE_COUNT > 1:
return
gc.disable()
# 启用GC
def gc_enable():
with acquire(_GC_DISABLE_COUNT_LOCK, timeout=1):
global _GC_DISABLE_COUNT
_GC_DISABLE_COUNT -= 1
if _GC_DISABLE_COUNT > 0:
return
gc.enable()
+3
View File
@@ -27,3 +27,6 @@ match_general_version_format = re.compile(r'^\d+(?:\.\d+){1,2}$')
# md5格式验证
match_md5_format = re.compile(r'^[a-fA-F0-9]{32}$')
# SQL字符串中的常用特殊字符
search_sql_special_chars = re.compile(r'''(?<!\\)(?:[%_]|\\(?![^\\abfnrtvxuUN'"0-7]))''')
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
import collections
# Common structures
aap_t_simple_result = collections.namedtuple('aap_t_simple_result', ['success', 'msg'])
aap_t_mysql_dump_info = collections.namedtuple('aap_t_mysql_dump_info', ['db_name', 'file', 'dump_time'])
+12
View File
@@ -1,6 +1,18 @@
import typing
# 创建一个管道函数
def make_pipe(fs: typing.List[callable]) -> callable:
"""
创建一个管道函数
@param fs: callable 数据过滤函数
@return: any
"""
def helper(val: any) -> any:
return my_pipe(val, fs)
return helper
def my_pipe(val: any, fs: typing.List[callable]) -> any:
"""
管道数据过滤函数
+37 -3
View File
@@ -1,3 +1,4 @@
import copy
import re
import json
import socket
@@ -5,6 +6,7 @@ import os
import typing
from .regexplib import match_ipv4, match_ipv6, match_safe_path, match_based_host
from .exceptions import HintException
from .structures import aap_t_simple_result
class Param:
@@ -325,15 +327,15 @@ class Param:
return self
def do_filter(self, val, extra_filters: typing.List[callable] = []) -> any:
def do_filter(self, val, extra_filters: typing.Union[typing.List[callable], typing.Tuple[callable]] = ()) -> any:
"""
执行参数过滤器
@param val: any
@param extra_filters: list[callable]
@param extra_filters: list[callable]|tuple[callable]
@return: any
"""
from functools import reduce
return reduce(lambda x, y: y(x), extra_filters + self.__filters, val)
return reduce(lambda x, y: y(x), list(extra_filters) + self.__filters, val)
class _ValidateRule:
@@ -1251,3 +1253,35 @@ def _get_number_data_type(s):
pass
return float
# 参数验证器
class Validator:
def __init__(self, rules: typing.Union[typing.Tuple[Param], typing.List[Param]], raise_exc: bool = True):
self.__RULES = list(rules)
self.__RAISE_EXC = raise_exc
# 参数格式校验
def check(self, args: dict) -> aap_t_simple_result:
try:
for v in self.__RULES:
v.do_validate(args)
except Exception as e:
if self.__RAISE_EXC:
raise
return aap_t_simple_result(False, str(e))
return aap_t_simple_result(True, 'ok')
# 参数列表过滤
def filter(self, args: dict) -> typing.Dict:
new_args = {}
for v in self.__RULES:
if v.name not in args:
continue
new_args = v.do_filter(args[v.name])
return new_args
+31
View File
@@ -0,0 +1,31 @@
import struct
# varint编码 -> bytes
def _varint_encode(num):
res = b''
while num > 127:
res += struct.pack('B', 0x80 | (num & 0x7f))
num >>= 7
res += struct.pack('B', num)
return res
# varint解码 -> num, length
def _varint_decode(bs):
res = 0
n = 0
for shift in range(0, 64, 7):
if n > len(bs) - 1:
break
res |= (bs[n] & 0x7f) << shift
if (bs[n] & 0x80) == 0:
break
n += 1
return res, n + 1
+1
View File
@@ -3979,6 +3979,7 @@ class Sqlite():
if 'ports' not in create_table_str:
public.M('firewall_country').execute('ALTER TABLE "firewall_country" ADD "ports" TEXT DEFAULT ""')
def create_trigger(self, sql):
self.GetConn()
self.connection.text_factory = str
+19 -1
View File
@@ -31,6 +31,7 @@ Z8UsPk1Q7HtwjRd4g01ryw==
1aJ5h9ef6qScYRSEXHxMz/JV+hrqnP7g6CgzmGbTA34=
CH5utP+NORdjI2nqATw4gJ30bQaw4oV4TkWtZlCiO9A=
lbIj6ug3LX3xS019kmbRSTcfm4XASPCYnVO8MD2z14s=
1u+XjG/2+GSQRv6EzCaWRQ==
iZaIsa48RXfE+uf/yF/rQD9SK8CJ49+yPAMIKmMZPD4=
lOh2GtzHjjMM8E9J40AuOc+/vc1yhUL+xJx/Mivlb25pg5HBJ1HJ91Rfq30bJq9S
UbJuac0dxLiN+5ocS3w2vRp92UK1M7Voei4ApHZyTgY=
@@ -46,6 +47,7 @@ n+FHKmWLbioNpC38yMj4WmiXmXqyLuzKUmIAM1Ft5eM=
NhnIR3Ilo4H2su9/cTNo/MME7jTfPzQcknP67wyItNzcCacoNjdvCuiW0x8udsSO/edftRARPJ4xwm1wQrVT2A==
wlLHv6kT3Q/RmtMBN4nDATwL/9Oa0sOvSxwVwQfq99U=
VmVrGQo2zRokW/ZuO9bN68Jg91voF9Ce3nf2o3e+jkofqu9SSuRXcnpG7smrmLLo
1u+XjG/2+GSQRv6EzCaWRQ==
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
6d8NLnHX3WuS3g79bJvyhMRKs67DB9ZOIiBDrB02YSQctSNS1aqQlPvqVprQ2WDG
@@ -79,12 +81,24 @@ NhnIR3Ilo4H2su9/cTNo/E4e/PRk3DEtgyjsUDeOpSOwGpZehbHYaLN6kP4SrhN+PFbXCI6E8Z+8865U
NhnIR3Ilo4H2su9/cTNo/C/mkLay+Rx5WJjwGcYV7pSktyrOzy78W2NcadJ49lO1rNrUNnnzcDMri5NWEH6lGQ==
lOh2GtzHjjMM8E9J40AuOVVeyoh6rSy98QMdDBotkjU+CpzMJCQMfJrCkE4uEcQAgmW0jhOnZHI5mtglpnoAuAijuP+2lAOsmb6+b+801pIjLK0hXXo2u4rmG7jkFs3R
lOh2GtzHjjMM8E9J40AuOeMCiZGQsphriaBgvIdFiyOx9f7gw874ih7YoSVMDwrD
po4gDbkGtZGvsVkqlQC6Zk1QKYt+qgmWUuOhwfsrpNM=
UGSctzorKMmnxEYKNtDxBkvvz1CdKfyqaYWXn+7HAZs=
H3J5DRxCAs+XlPdoiqbWAEZVqk05dBQ8werKVFELygI=
Nmty3iAt3gg+2KizVN6liKmDLACKNT+MEyuPvHlGUL0=
f6i+lsi35nURDz6EJdx9zM/Xoipvs34XVfwWJ+ux8Rz4TrR2SQjbZyH7vEKb4+j7XHgmC6Tno9YQTDHgMt7KvA==
f6i+lsi35nURDz6EJdx9zPe9sAZUDW4g/hSLMBY3GwUCmNfIfC8KOHeSO809Y8w27XB15/GObFgtW8jGLDFVo4rNGRmYk5EoTxiyqJIynfM=
f6i+lsi35nURDz6EJdx9zJ1NdDiS6rl/V65/WzP7UnD287V9OI9eUAxpVVAypE0R6jBuu9m5+CfA9lKwAL+Ugw==
f6i+lsi35nURDz6EJdx9zP3qDzts9NJ9hUb9FLBlZPiEI1reqP0Bvb6u2Go1C6W520PcmKNl6yPUdQg3CyPVFw==
f6i+lsi35nURDz6EJdx9zP3qDzts9NJ9hUb9FLBlZPjh9tUjDopYIJdWnlez/R4iusV7Q7kFLjNuptMCVKKjau8wDFuaVvvJaons0mcTOeA=
f6i+lsi35nURDz6EJdx9zCmK7pAVDIsd7rC/+Z3x1nxzo59ABA+BErfoy9qoZOL+
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
NhnIR3Ilo4H2su9/cTNo/A5SzFv4xHkReni2nLCW9wf/+pP3k795kKoMZAQXwXD6
wlLHv6kT3Q/RmtMBN4nDASCRBoPIX3WHiVt75UTjznI=
VmVrGQo2zRokW/ZuO9bN63WVsNPEwwprYYkAUqx5H2WQM74qCCEzrB3qJy/Mpn1CTpqeZGmRvqbYKmI/uSdTZw==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN61LJQniFugoi6x2ujjnJM7tldXKimSA8e3cVKdvY80W2
VmVrGQo2zRokW/ZuO9bN67RCeNzfj+jMtOje3k8i9wttmphwpnBlwKU3a9Kqv06thCgoZf5HfOCSRRY7q4YWeg==
VmVrGQo2zRokW/ZuO9bN64CPlXy2HmOeksJNoNOmCTRN2iWrExIMUdvTGpj/NNWq+ALT/bwKyDsjcfkGB9eQcrQ3H8UC+ywFIkXx32JcLCKuolLfVZKLqPTsQHTpYF9P
VmVrGQo2zRokW/ZuO9bN6yDw6frMtVHHQaPzsZSKK0aES6D4Kud9zFd68X/jO6wJ
VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY=
@@ -93,7 +107,10 @@ VmVrGQo2zRokW/ZuO9bN61V3/TpT/zrd2QvdUtMgGKQFi9VDtwL4Vc83iegeMAft36zpJ+t/eeWtmA4E
VmVrGQo2zRokW/ZuO9bN6wKjnvaTwlMeHWSuJ/EAxZ8Z1KtkmFIaU/d9KNNMifcN
VmVrGQo2zRokW/ZuO9bN62ExR0OHxzNY3oC4Mfi694VYJyilbRPMd6JDlCBDdbCe
32pdC9DD05OE2l0oXazDFLdmyVEUr33LQ7qI5CZQN57igfVF4gan9s5C0Jnc3Ki1
6ZPJI/HSoc4xA2zncU65FpfH+eGtPlOYNU1YCnnAis8=
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
xWoGNWjKGPfI4gq8aHoTfD6f0CqCimlZnuwJn30N/jbJCxj2C7n4F84hVVongdjniXe3Q9FJQdrMpXJ94VHz5g==
1u+XjG/2+GSQRv6EzCaWRQ==
s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU=
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
@@ -107,6 +124,7 @@ b4OJVZe8QyIpjuTpKXDL9A==
NhnIR3Ilo4H2su9/cTNo/Ps3vYSOiOY6esn1xeoWmAlmLwmaOoQ9T7lnCb6TRVZ67prVtxc96eRcwG0EieQAHA==
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+nhmNgO2Yqkfbla+bgPzSp/ulULnjCxA+skRNBtWTg0BA==
1u+XjG/2+GSQRv6EzCaWRQ==
+plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg=
1u+XjG/2+GSQRv6EzCaWRQ==
0jp9NuG0oWLkMfJ/MsYOmfu7WkSnZjHksXr6BBYLZSUOzHDXl9XjsxiUKNVbrHEG