Update to v8.7.0

This commit is contained in:
aapanel.com
2026-04-16 18:17:07 +08:00
parent c3f2d7566c
commit ee92fa2dbd
2472 changed files with 33631 additions and 20919 deletions
+13 -3
View File
@@ -18,11 +18,21 @@ class main:
from panel_site_v2 import panelSite
site_obj = panelSite()
res = site_obj.get_Scan(None)
domains_str ='\n' + ",\n".join(res.get('target_domains', [])) + '\n'
if int(res['loophole_num']):
msg_list.append('Scan the website {} and find {} vulnerabilities'.format(res['site_num'], res['loophole_num']))
msg = 'Scan {} websites and found {} vulnerabilities.\n Risk domains: [{}]'.format(
res['site_num'],
res['loophole_num'],
domains_str
)
msg_list.append(msg)
else:
msg_list.append('Scan the website [{}], status is [Security]'.format(res['site_num']))
return {"msg_list": msg_list}
msg = 'Scan {} websites, status is [Security].\n Scanned domains: [{}]'.format(
res['site_num'],
domains_str
)
msg_list.append(msg)
return {"msg_list": msg_list}
if __name__ == '__main__':
+38
View File
@@ -0,0 +1,38 @@
#!/www/server/panel/pyenv/bin/python
# coding: utf-8
import sys
if "/www/server/panel" not in sys.path:
sys.path.insert(0, "/www/server/panel")
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
if "/www/server/panel/class_v2" not in sys.path:
sys.path.insert(0, "/www/server/panel/class_v2")
import public
from btdockerModelV2 import composeModel
def _parse_task_id(argv):
for i, arg in enumerate(argv):
if arg == "--task-id" and i + 1 < len(argv):
return argv[i + 1]
if arg.startswith("--task-id="):
return arg.split("=", 1)[1]
return ""
def main():
task_id = _parse_task_id(sys.argv)
if not task_id:
return
composeModel.main().run_create_task(task_id)
if __name__ == '__main__':
try:
main()
except Exception:
public.print_log(public.get_error_info())
+193 -11
View File
@@ -1,6 +1,7 @@
import json
import os
import sys
import time
panel_path = '/www/server/panel'
os.chdir(panel_path)
@@ -12,6 +13,54 @@ if "/www/server/panel" not in sys.path:
import public
class Counter:
FAILURE_DIR = '/www/server/panel/data/project_daemon_failures'
MAX_COUNT = 10
@classmethod
def _get_fail_flag(cls, id_project_name: str):
"""获取项目失败记录文件路径"""
return os.path.join(cls.FAILURE_DIR, '{}.pl'.format(id_project_name))
@classmethod
def is_skip(cls, id_project_name) -> bool:
"""判断项目是否应该被守护进程跳过"""
return cls.get_fail(id_project_name) >= cls.MAX_COUNT
@classmethod
def get_fail(cls, id_project_name) -> int:
"""获取项目失败次数"""
fail_file = cls._get_fail_flag(id_project_name)
if not os.path.exists(fail_file):
return 0
try:
return int(public.readFile(fail_file) or 0)
except:
return 0
@classmethod
def add_fail(cls, id_project_name) -> None:
"""增加项目失败计数"""
fail_file = Counter._get_fail_flag(id_project_name)
try:
count = cls.get_fail(id_project_name) + 1
if count > cls.MAX_COUNT:
return
public.writeFile(fail_file, str(count))
except Exception as e:
public.print_log("add_fail_count error: {}".format(e))
@classmethod
def clear_fail(cls, id_project_name) -> None:
"""清除项目失败计数"""
fail_file = cls._get_fail_flag(id_project_name)
try:
if os.path.exists(fail_file):
os.remove(fail_file)
except Exception as e:
public.print_log("clear_fail_count error: {}".format(e))
def PythonDaemons():
"""
@name Python 项目守护进程
@@ -21,11 +70,15 @@ def PythonDaemons():
if public.M('sites').where('project_type=?', ('Python',)).count() >= 1:
project_info = public.M('sites').where('project_type=?', ('Python',)).select()
for i in project_info:
id_project_name = "{}_{}".format(i['id'], i['name'])
# 检查失败次数, 则静默跳过
if Counter.is_skip(id_project_name):
continue
try:
from projectModelV2 import pythonModel
# sites 表中的 project_config
i['project_config'] = json.loads(i['project_config'])
# auto_run, 自启,守护
# auto_run, 自启,守护 的关键字段
if 'auto_run' in i['project_config'] and i['project_config']['auto_run'] in ["1", 1, True, "true"]:
python_obj = pythonModel.main()
# 项目的运行状态是否为运行中
@@ -37,9 +90,17 @@ def PythonDaemons():
get = public.dict_obj()
get.name = i['name']
# StartProject
python_obj.StartProject(get)
res = python_obj.StartProject(get)
if res.get("status", -1) != 0:
# 失败增加计数
Counter.add_fail(id_project_name)
continue
public.WriteLog('Project Daemon', 'Python project [{}] auto start!'.format(i['name']))
# 正常启动成功,清除失败计数
Counter.clear_fail(id_project_name)
except Exception:
# 异常兜底,增加失败计数
Counter.add_fail(id_project_name)
import traceback
print("Python Daemon Error: {}".format(traceback.format_exc()))
continue
@@ -50,31 +111,152 @@ def NodeDaemons():
project_info = public.M('sites').where('project_type=?', ('Node',)).select()
from projectModelV2 import nodejsModel
from mod.project.nodejs import comMod
for i in project_info:
id_project_name = "{}_{}".format(i['id'], i['name'])
if Counter.is_skip(id_project_name):
continue
try:
i['project_config'] = json.loads(i['project_config'])
if 'is_power_on' in i['project_config'] and i['project_config']['is_power_on'] in ["1", 1, True, "true"]: # node 默认为is_power_on
# 查看网站自启开关,node 默认为is_power_on
if 'is_power_on' in i['project_config'] and i['project_config']['is_power_on'] in ["1", 1, True, "true"]:
p_type = 'pm2' if i['project_config'].get('pm2_name') else 'nodejs'
node_obj = nodejsModel.main()
# 判断状态
status = node_obj.get_project_info(public.to_dict_obj({"project_name": i['name']}))
if status['status'] == 0 and status.get('message').get('run') in [True,'true']:
if status['status'] == 0 and status.get('message').get('run') in [True, 'true']:
continue
# 区分PM2与nodejs项目
if p_type == 'pm2':
if not node_obj.get_project_run(project_name=i['project_config'].get('pm2_name'), project_type = 'pm2'):
comMod.main().set_project_status(public.to_dict_obj({"project_name":i['name'],"project_type":p_type,"status":"start","pm2_name":i['project_config'].get('pm2_name')}))
if not node_obj.get_project_run(project_name=i['project_config'].get('pm2_name'),
project_type='pm2'):
res = comMod.main().set_project_status(public.to_dict_obj(
{"project_name": i['name'], "project_type": p_type, "status": "start",
"pm2_name": i['project_config'].get('pm2_name')}))
if res.get("status", -1) != 0:
public.WriteLog('Project Daemon','Node.js project [{}] Startup failed!'.format(i['name']))
Counter.add_fail(id_project_name)
continue
else:
if not node_obj.get_project_run(project_name=i['name'], project_type = 'node'):
comMod.main().set_project_status(public.to_dict_obj({"project_name":i['name'],"project_type":p_type,"status":"start"}))
public.WriteLog('Project Daemon', 'Node.js project [{}] auto start!'.format(i['name']))
if not node_obj.get_project_run(project_name=i['name'], project_type='node'):
res = comMod.main().set_project_status(public.to_dict_obj(
{"project_name": i['name'], "project_type": p_type, "status": "start"}))
if res.get("status", -1) != 0:
public.WriteLog('Project Daemon','Node.js project [{}] Startup failed!'.format(i['name']))
Counter.add_fail(id_project_name)
continue
# 进行二次校验,避免成功启动后马上挂掉
time.sleep(3)
status = node_obj.get_project_info(public.to_dict_obj({"project_name": i['name']}))
if status['status'] == 0 and status.get('message').get('run') in [True, 'true']:
public.WriteLog('Project Daemon', 'Node.js project [{}] auto start!'.format(i['name']))
Counter.clear_fail(id_project_name)
continue
public.WriteLog('Project Daemon', 'Node.js project [{}] Startup failed!'.format(i['name']))
Counter.add_fail(id_project_name)
except Exception:
Counter.add_fail(id_project_name)
import traceback
print("Node.js Daemon Error: {}".format(traceback.format_exc()))
continue
def GoDaemons():
pass
"""
@name Go 项目守护进程
@author hzh@bt.cn
@time 2026-04-15
@modified 2026-04-15
检测Go项目运行状态,如果项目异常停止(非用户手动停止),尝试重启
启动失败会记录日志并累计失败次数,超过10次后跳过该项目
"""
go_pid_path = '/var/tmp/gopids'
if public.M('sites').where('project_type=?', ('Go')).count() >= 1:
project_info = public.M('sites').where('project_type=?', ('Go')).select()
from projectModelV2 import goModel
for i in project_info:
id_project_name = "{}_{}".format(i['id'], i['name'])
# 检查失败次数,超过10次则静默跳过
if Counter.is_skip(id_project_name):
continue
try:
i['project_config'] = json.loads(i['project_config'])
# 检查是否开启自启动
if 'is_power_on' in i['project_config'] and i['project_config']['is_power_on'] in ["1", 1, True, "true"]:
go_obj = goModel.main()
pid_file = "{}/{}.pid".format(go_pid_path, i['name'])
# 检查项目运行状态
is_running = go_obj.get_project_run_state(project_name=i['name'])
if is_running:
# 项目正常运行,清除失败计数,并标记为非用户停止状态
Counter.clear_fail(id_project_name)
go_obj.start_by_user(i['id']) # 标记项目正在运行(非用户停止状态)
continue
# 项目未运行,检查是否为异常停止(PID文件存在但进程不存在)
pid_file_exists = os.path.exists(pid_file)
process_was_killed = False
if pid_file_exists:
try:
pid_content = public.readFile(pid_file)
if pid_content:
old_pid = int(pid_content)
# PID文件存在但进程不存在 = 异常停止
if not os.path.exists('/proc/{}'.format(old_pid)):
process_was_killed = True
except:
process_was_killed = True # 读取失败也视为异常
if process_was_killed:
# 项目被异常停止(kill或崩溃),清理残留PID文件后尝试重启
try:
os.remove(pid_file)
except:
pass
# 重置用户停止状态,因为项目是异常停止的
go_obj.start_by_user(i['id'])
get = public.dict_obj()
get.project_name = i['name']
res = go_obj.start_project(get)
if res.get("status", -1) != 0:
error_msg = res.get('msg', 'Unknown error')
public.WriteLog('Project Daemon', 'Go project [{}] auto restart failed after abnormal stop! Error: {}'.format(i['name'], error_msg))
Counter.add_fail(id_project_name)
continue
# 启动成功,等待3秒进行二次校验
time.sleep(3)
if go_obj.get_project_run_state(project_name=i['name']):
public.WriteLog('Project Daemon', 'Go project [{}] auto restart success after abnormal stop!'.format(i['name']))
Counter.clear_fail(id_project_name)
else:
public.WriteLog('Project Daemon', 'Go project [{}] auto restart failed! Project crashed after startup'.format(i['name']))
Counter.add_fail(id_project_name)
continue
# 项目未运行且PID文件不存在,检查是否为用户通过面板手动停止
if go_obj.is_stop_by_user(i['id']):
# 用户通过面板停止按钮停止的项目,不自动启动
continue
except Exception:
Counter.add_fail(id_project_name)
import traceback
error_info = traceback.format_exc()
public.WriteLog('Project Daemon', 'Go project [{}] daemon error: {}'.format(i['name'], error_info[:500]))
continue
def main():
+2
View File
@@ -20,11 +20,13 @@ import public, json
try:
from projectModelV2.pythonModel import main as pythonMod
from projectModelV2.goModel import main as goMod
from projectModelV2.nodejsModel import main as nodejsMod
mods = {
"python": pythonMod(),
"node": nodejsMod(),
"go": goMod(),
}
except Exception as e:
print(str(e))
+20 -60
View File
@@ -10,22 +10,19 @@
# | 支持升级环境,升级面板,修复面板;环境相关由shell部分处理,面板升级沿用python脚本处理
# ------------------------------
# coding: utf-8
import argparse
import hashlib
import json
import os
import re
import shutil
import subprocess
import sys
import time
import json
import re
import hashlib
import shutil
import socket
import argparse
import subprocess
from dataclasses import dataclass
from typing import Tuple, Optional, Dict, List
import psutil
from urllib.parse import urlparse, urlunparse
from dataclasses import dataclass, field
from typing import Tuple, Optional, Dict, List
# 全局变量
UPGRADE_MODEL = 'python'
@@ -194,56 +191,15 @@ class Version:
if not self:
print_x("No version information available; cannot run pre-processing script")
return False
# down_url = "https://node.aapanel.com/install/update/update_prep_script.sh"
# try:
# sh_content = http_get(down_url)
# sh_content = sh_content.replace("\r\n", "\n")
# if len(sh_content) < 10:
# print_x("ERROR: Failed to download pre-processing script")
# return False
# except:
# print_x("ERROR: Failed to download pre-processing script")
# return False
#
# prep_sh_path = "{}/script/update_prep_script.sh".format(PANEL_PATH)
# write_file(prep_sh_path, sh_content)
# shell = "bash {} {} {} prepare".format(prep_sh_path, self, self.is_lts)
#
# update_ready = False
#
# def print_and_check(log: str):
# nonlocal update_ready
# print_x(log, end="")
# if log.find("BT-Panel Update Ready") != -1:
# update_ready = True
#
# run_command_with_call_log(shell, print_and_check)
# if update_ready:
# print_x("Pre-processing script executed successfully")
# return True
# print_x("ERROR: Pre-processing script execution failed")
# return False
print_x("Pre-processing script executed successfully")
return True
def run_after_script(self):
# prep_sh_path = "{}/script/update_prep_script.sh".format(PANEL_PATH)
# shell = "bash {} {} {} after".format(prep_sh_path, self, self.is_lts)
# update_ready = False
#
# def print_and_check(log: str):
# nonlocal update_ready
# print_x(log, end="")
# if log.find("BT-Panel Update Ready") != -1:
# update_ready = True
#
# run_command_with_call_log(shell, print_and_check)
# if update_ready:
# print_x("Startup check script executed successfully")
# else:
# print_x("Warning: Startup check script execution failed")
if args_data.action == 'repair_panel' and os.path.exists('/www/server/panel/data/is_beta.pl'):
try:
os.system('rm -f /www/server/panel/data/is_beta.pl')
except Exception as e:
print_x('WARNING: Failed to remove beta.pl: {}'.format(str(e)))
print_x("Startup check script executed successfully")
@@ -610,6 +566,7 @@ def check_hash(f_list, src_dir, dst_dir):
if d_file.endswith('.whl'): continue
if d_file == '/www/server/panel/BTPanel/languages/settings.json': continue
if d_file.endswith('bt-ipfilter'): continue
# 校验文件hash
if file_hash(s_file) != file_hash(d_file):
@@ -819,7 +776,7 @@ def install_with_pkg_file(package_file: str, start_func=None):
bak_dir = PANEL_PATH.replace('panel', 'panel_bak_{}'.format(int(time.time())))
if copy_dir(f_list, PANEL_PATH, bak_dir, 'Backing up:'):
print_x("Backup completed...")
if not update_panel_files(f_list, src_dir, start_func=start_func): # 更新结果
if not update_panel_files(f_list, src_dir, start_func=start_func): # 更新结果
# 恢复备份
copy_dir(f_list, bak_dir, PANEL_PATH, "Restoring backup files:")
print_x('Operation failed, backup has been restored successfully...')
@@ -856,7 +813,7 @@ def run() -> int:
if not v.checksum:
print_x('ERROR: No remote version checksum information available')
return 1
# 前置 run_pre_script
if not v.run_pre_script():
print_x('ERROR: Pre-processing failed, cannot continue upgrade; please check network connectivity')
return 1
@@ -870,7 +827,10 @@ def run() -> int:
action_text = "repair" if args_data.action == 'repair_panel' else "upgrade"
print_x('Package downloaded successfully: {}, starting {}...'.format(tmp_file, action_text))
install_with_pkg_file(tmp_file, start_func=v.run_after_script)
install_with_pkg_file(
tmp_file,
start_func=v.run_after_script # 后置 run_after_script
)
return 0