mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-09-07 10:07:39 +02:00
Update to v8.7.0
This commit is contained in:
+13
-3
@@ -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__':
|
||||
|
||||
@@ -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
@@ -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():
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user