Files
2026-08-13 18:24:50 +08:00

3455 lines
146 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: hwliang <hwl@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
# node.js模型
# ------------------------------
import os, sys, re, json, shutil, psutil, time, hashlib
from time import process_time_ns
from urllib.parse import urlparse
from projectModelV2.base import projectBase
import public
from public.validate import Param
try:
from BTPanel import cache
except:
pass
class main(projectBase):
_panel_path = public.get_panel_path()
_nodejs_plugin_path = public.get_plugin_path('nodejs')
_nodejs_path = '{}/nodejs'.format(public.get_setup_path())
_log_name = 'Project management'
_npm_exec_log = '{}/logs/npm-exec.log'.format(_panel_path)
_node_pid_path = '{}/vhost/pids'.format(_nodejs_path)
_node_logs_path = '{}/vhost/logs'.format(_nodejs_path)
_node_run_scripts = '{}/vhost/scripts'.format(_nodejs_path)
_pids = None
_vhost_path = '{}/vhost'.format(_panel_path)
_www_home = '/home/www'
_node_logs = '{}/vhost/logs'.format(_nodejs_path)
def __init__(self):
if not os.path.exists(self._node_run_scripts):
os.makedirs(self._node_run_scripts, 493)
if not os.path.exists(self._node_pid_path):
os.makedirs(self._node_pid_path, 493)
if not os.path.exists(self._node_logs_path):
os.makedirs(self._node_logs_path, 493)
if not os.path.exists(self._www_home):
os.makedirs(self._www_home, 493)
public.set_own(self._www_home, 'www')
def get_exec_logs(self, get):
'''
@name 获取执行日志
@author hwliang<2021-08-09>
@param get<dict_obj>
@return string
'''
if not os.path.exists(self._npm_exec_log): return public.returnMsg(False, 'NODE_NOT_EXISTS')
return public.return_message(0, 0, public.GetNumLines(self._npm_exec_log, 20))
def get_project_list(self, get):
'''
@name 获取项目列表(支持全局流量排序)
'''
try:
get.validate([
Param('search').String(),
Param('limit').Integer(),
Param('p').Integer(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
p = int(get.get('p', 1))
limit = int(get.get('limit', 20))
search_word = get.get('search', '').strip()
re_order = get.get('re_order', '') # 流量排序专用
order_str = get.get('order', 'id desc')
where_str = "project_type=?"
where_args = ["Node"]
if search_word:
where_str += " AND (name LIKE ? OR ps LIKE ?)"
search_pattern = "%{}%".format(search_word)
where_args.extend([search_pattern, search_pattern])
all_data = public.M('sites').where(where_str, tuple(where_args)).order(order_str).select()
if not all_data:
return public.return_message(0, 0, {'data': [], 'page': ''})
# 获取流量数据
re_data = None
if re_order:
import data_v2
res = data_v2.data().get_site_request(public.to_dict_obj({'site_type': 'Node'}))
if res.get('status') == 0:
re_data = res.get('message')
for i in range(len(all_data)):
all_data[i] = self.get_project_stat(all_data[i])
all_data[i]['re_total'] = 0
if re_data and all_data[i]['name'] in re_data:
all_data[i]['re_total'] = re_data[all_data[i]['name']]['total']['request']
# 批量获取备份信息
site_ids = [item['id'] for item in all_data]
if site_ids:
backup_info_map = {
j['pid']: j for j in
public.S('backup').where_in('pid', site_ids).where('type=?', ('0',))
.group('pid').field('pid,count(*) as cnt,max(addtime) as last_backup_time').select()
}
for item in all_data:
info = backup_info_map.get(item['id'], {})
item['backup_count'] = info.get('cnt', 0)
item['last_backup_time'] = info.get('last_backup_time', '')
if re_order:
is_reverse = True if re_order == 'desc' else False
all_data = sorted(all_data, key=lambda x: x.get('re_total', 0), reverse=is_reverse)
count = len(all_data)
start = (p - 1) * limit
end = start + limit
paged_data = all_data[start:end]
data = public.get_page(count, p, limit, get.get('callback', ''))
data['data'] = paged_data
return public.return_message(0, 0, data)
def get_ssl_end_date(self, project_name):
'''
@name 获取SSL信息
@author hwliang<2021-08-09>
@param project_name <string> 项目名称
@return dict
'''
import data
return data.data().get_site_ssl_info('node_{}'.format(project_name))
def is_install_nodejs(self, get):
'''
@name 是否安装nodejs版本管理器
@author hwliang<2021-08-09>
@param get<dict_obj> 请求数据
@return bool
'''
return_message = os.path.exists(self._nodejs_plugin_path)
return public.return_message(0, 0, return_message)
def _is_install_nodejs(self, get):
'''
@name 是否安装nodejs版本管理器
@author hwliang<2021-08-09>
@param get<dict_obj> 请求数据
@return bool
'''
return os.path.exists(self._nodejs_plugin_path)
def get_nodejs_version(self, get):
'''
@name 获取已安装的nodejs版本
@author hwliang<2021-08-09>
@param get<dict_obj> 请求数据
@return list
'''
nodejs_list = []
if not os.path.exists(self._nodejs_path): return public.return_message(0, 0, nodejs_list)
for v in os.listdir(self._nodejs_path):
if v[0] != 'v' or v.find('.') == -1: continue
node_path = os.path.join(self._nodejs_path, v)
node_bin = '{}/bin/node'.format(node_path)
if not os.path.exists(node_bin):
if os.path.exists(node_path + '/bin'):
public.ExecShell('rm -rf {}'.format(node_path))
continue
nodejs_list.append(v)
return public.return_message(0, 0, nodejs_list)
def get_run_list(self, get):
'''
@name 获取node项目启动列表
@author hwliang<2021-08-10>
@param get<dict_obj>{
project_cwd: string<项目目录>
}
''' # 校验参数
try:
get.validate([
Param('project_cwd').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_cwd = get.project_cwd.strip()
if not os.path.exists(project_cwd):
return_message = public.return_error(public.lang('The project directory does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
package_file = '{}/package.json'.format(project_cwd)
if not os.path.exists(package_file): return public.return_message(0, 0, {})
package_content = public.readFile(package_file)
if not package_content:
return public.return_message(0, 0, {})
try:
package_info = json.loads(package_content)
except json.JSONDecodeError:
return public.return_message(0, 0, {})
# package_info = json.loads(public.readFile(package_file))
if not 'scripts' in package_info: return public.return_message(0, 0, {})
if not package_info['scripts']: return public.return_message(0, 0, {})
return public.return_message(0, 0, package_info['scripts'])
def get_npm_bin(self, nodejs_version):
'''
@name 获取指定node版本的npm路径
@author hwliang<2021-08-10>
@param nodejs_version<string> nodejs版本
@return string
'''
npm_path = '{}/{}/bin/npm'.format(self._nodejs_path, nodejs_version)
if not os.path.exists(npm_path): return False
return npm_path
def get_yarn_bin(self, nodejs_version):
'''
@name 获取指定node版本的yarn路径
@author hwliang<2021-08-28>
@param nodejs_version<string> nodejs版本
@return string
'''
yarn_path = '{}/{}/bin/yarn'.format(self._nodejs_path, nodejs_version)
if not os.path.exists(yarn_path): return False
return yarn_path
def get_pnpm_bin(self, nodejs_version):
'''
@name 获取指定node版本的pnpm路径
@param nodejs_version<string> nodejs版本
@return string
'''
pnpm_path = '{}/{}/bin/pnpm'.format(self._nodejs_path, nodejs_version)
if not os.path.exists(pnpm_path): return False
return pnpm_path
def get_node_bin(self, nodejs_version):
'''
@name 获取指定node版本的node路径
@author hwliang<2021-08-10>
@param nodejs_version<string> nodejs版本
@return string
'''
node_path = '{}/{}/bin/node'.format(self._nodejs_path, nodejs_version)
if not os.path.exists(node_path): return False
return node_path
def get_last_env(self, nodejs_version, project_cwd=None):
'''
@name 获取前置环境变量
@author hwliang<2021-08-25>
@param nodejs_version<string> Node版本
@return string
'''
nodejs_bin_path = '{}/{}/bin'.format(self._nodejs_path, nodejs_version)
if project_cwd:
_bin = '{}/node_modules/.bin'.format(project_cwd)
if os.path.exists(_bin):
nodejs_bin_path = _bin + ':' + nodejs_bin_path
last_env = '''PATH={nodejs_bin_path}:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
'''.format(nodejs_bin_path=nodejs_bin_path)
return last_env
def install_packages(self, get):
'''
@name 安装指定项目的依赖包
@author hwliang<2021-08-10>
@param get<dict_obj>{
project_name: string<项目名称>
}
return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('The specified item does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not os.path.exists(project_find['path']):
return_message = public.return_error(public.lang('The project directory does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
package_file = '{}/package.json'.format(project_find['path'])
if not os.path.exists(package_file):
return_message = public.return_error(
public.lang('The package.json configuration file was not found in the project directory!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
nodejs_version = project_find['project_config']['nodejs_version']
package_lock_file = '{}/package-lock.json'.format(project_find['path'])
node_modules_path = '{}/node_modules'.format(project_find['path'])
# 已经安装过的依赖包的情况下,可能存在不同node版本导致的问题,可能需要重新构建依赖包
rebuild = False
if os.path.exists(package_lock_file) and os.path.exists(node_modules_path):
rebuild = True
npm_bin = self.get_npm_bin(nodejs_version)
yarn_bin = self.get_yarn_bin(nodejs_version)
if not npm_bin and not yarn_bin:
return_message = public.return_error(public.lang('The specified nodejs version does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
public.writeFile(self._npm_exec_log, "Installing dependencies...\n")
public.writeFile(self._npm_exec_log, "Downloading dependency package, please wait...\n")
if yarn_bin:
if os.path.exists(package_lock_file):
os.remove(package_lock_file)
public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} install >> {} 2>&1".format(project_find['path'],
yarn_bin,
self._npm_exec_log))
else:
public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} install >> {} 2>&1".format(project_find['path'],
npm_bin,
self._npm_exec_log))
public.writeFile(self._npm_exec_log, "|-Successify --- Command executed! ---", 'a+')
public.WriteLog(self._log_name,
'Node project: {}, the installation of the dependency package is complete!'.format(
project_find['name']))
if rebuild: # 重新构建已安装模块
self.rebuild_project(get.project_name)
return_message = public.return_data(True, 'The dependency package is installed successfully!')
del return_message['status']
return public.return_message(0, 0, return_message)
def update_packages(self, get):
'''
@name 更新指定项目的依赖包
@author hwliang<2021-08-10>
@param get<dict_obj>{
project_name: string<项目名称>
}
return dict
'''
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('The specified item does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not os.path.exists(project_find['path']):
return_message = public.return_error(public.lang('The project directory does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
package_file = '{}/package.json'.format(project_find['path'])
if not os.path.exists(package_file):
return_message = public.return_error(
public.lang('The package.json configuration file was not found in the project directory!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
package_lock_file = '{}/package-lock.json'.format(project_find['path'])
if not os.path.exists(package_lock_file):
return_message = public.return_error(public.lang('Please install the dependency package first!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
nodejs_version = project_find['project_config']['nodejs_version']
npm_bin = self.get_npm_bin(nodejs_version)
if not npm_bin:
return_message = public.return_error(public.lang('The specified nodejs version does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} update &> {}".format(project_find['path'], npm_bin,
self._npm_exec_log))
public.WriteLog(self._log_name, 'Project [{}] update all dependent packages'.format(get.project_name))
return_message = public.return_data(True, 'Dependent package updated successfully!')
del return_message['status']
return public.return_message(0, 0, return_message)
def reinstall_packages(self, get):
'''
@name 重新安装指定项目的依赖包
@author hwliang<2021-08-10>
@param get<dict_obj>{
project_name: string<项目名称>
}
return dict
'''
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('The specified item does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not os.path.exists(project_find['path']):
return_message = public.return_error(public.lang('The project directory does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
package_file = '{}/package.json'.format(project_find['path'])
if not os.path.exists(package_file):
return_message = public.return_error(
public.lang('The package.json configuration file was not found in the project directory!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
package_lock_file = '{}/package-lock.json'.format(project_find['path'])
if os.path.exists(package_lock_file): os.remove(package_lock_file)
package_path = '{}/node_modules'
if os.path.exists(package_path): shutil.rmtree(package_path)
nodejs_version = project_find['project_config']['nodejs_version']
npm_bin = self.get_npm_bin(nodejs_version)
if not npm_bin:
return_message = public.return_error(public.lang('The specified nodejs version does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
public.WriteLog(self._log_name, 'Node project: {}, all dependent packages have been reinstalled')
public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} install &> {}".format(project_find['path'], npm_bin,
self._npm_exec_log))
return_message = public.return_data(True, 'Dependent package reinstalled successfully!')
del return_message['status']
return public.return_message(0, 0, return_message)
def get_project_modules(self, get):
'''
@name 获取指定项目的依赖包列表
@author hwliang<2021-08-10>
@param get<dict_obj>{
project_name: string<项目名称>
project_cwd: string<项目目录> 可选
}
return list
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
Param('project_cwd').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
if not 'project_cwd' in get:
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('The specified item does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_cwd = project_find['path']
else:
project_cwd = get.project_cwd
mod_path = os.path.join(project_cwd, 'node_modules')
modules = []
if not os.path.exists(mod_path): return public.return_message(0, 0, modules)
for mod_name in os.listdir(mod_path):
try:
mod_pack_file = os.path.join(mod_path, mod_name, 'package.json')
if not os.path.exists(mod_pack_file): continue
mod_pack_info = json.loads(public.readFile(mod_pack_file))
pack_info = {
"name": mod_name,
"version": mod_pack_info['version'],
"description": mod_pack_info['description'],
"license": mod_pack_info['license'] if 'license' in mod_pack_info else 'NULL',
"homepage": mod_pack_info['homepage']
}
modules.append(pack_info)
except:
continue
return public.return_message(0, 0, modules)
def install_module(self, get):
'''
@name 安装指定模块
@author hwliang<2021-08-10>
@param get<dict_obj>{
project_name: string<项目名称>
mod_name: string<模块名称>
}
@return dict
'''
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('The specified item does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_cwd = project_find['path']
mod_name = get.mod_name
filename = '{}/node_modules/{}/package.json'.format(project_cwd, mod_name)
if os.path.exists(filename):
return_message = public.return_error(public.lang('The specified module has been installed!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
nodejs_version = project_find['project_config']['nodejs_version']
npm_bin = self.get_npm_bin(nodejs_version)
yarn_bin = self.get_yarn_bin(nodejs_version)
if not npm_bin and not yarn_bin:
return_message = public.return_error(public.lang('The specified nodejs version does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if yarn_bin:
public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} add {} &> {}".format(project_find['path'], yarn_bin,
mod_name, self._npm_exec_log))
else:
public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} install {} &> {}".format(project_find['path'], npm_bin,
mod_name, self._npm_exec_log))
if not os.path.exists(filename):
return_message = public.return_error(public.lang('Failed to install the specified module!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
public.WriteLog(self._log_name,
'Node project {}, {} module installation is complete!'.format(get.project_name, mod_name))
return_message = public.return_data(True, 'Successful installation!')
del return_message['status']
return public.return_message(0, 0, return_message)
def uninstall_module(self, get):
'''
@name 卸载指定模块
@author hwliang<2021-04-08>
@param get<dict_obj>{
project_name: string<项目名称>
mod_name: string<模块名称>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
Param('mod_name').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('The specified item does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_cwd = project_find['path']
mod_name = get.mod_name
filename = '{}/node_modules/{}/package.json'.format(project_cwd, mod_name)
if not os.path.exists(filename):
return_message = public.return_error(public.lang('The specified module is not installed!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
nodejs_version = project_find['project_config']['nodejs_version']
npm_bin = self.get_npm_bin(nodejs_version)
yarn_bin = self.get_yarn_bin(nodejs_version)
if not npm_bin and not yarn_bin:
return_message = public.return_error(public.lang('The specified nodejs version does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if yarn_bin:
result = public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} remove {}".format(project_find['path'], yarn_bin,
mod_name))
else:
result = public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} uninstall {}".format(project_find['path'], npm_bin,
mod_name))
if os.path.exists(filename):
result = "\n".join(result)
if result.find('looking for funding') != -1:
return_message = public.return_error(
public.lang("This module is dependent on other installed modules and cannot be uninstalled!"))
del return_message['status']
return public.return_message(-1, 0, return_message)
return_message = public.return_error(public.lang("Unable to uninstall this module!"))
del return_message['status']
return public.return_message(-1, 0, return_message)
public.WriteLog(self._log_name,
'Node project {}, {} module uninstallation completed!'.format(get.project_name, mod_name))
return_message = public.return_data(True, 'Module unloaded successfully!')
del return_message['status']
return public.return_message(0, 0, return_message)
def upgrade_module(self, get):
'''
@name 更新指定模块
@author hwliang<2021-08-10>
@param get<dict_obj>{
project_name: string<项目名称>
mod_name: string<模块名称>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
Param('mod_name').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('The specified item does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_cwd = project_find['path']
mod_name = get.mod_name
filename = '{}/node_modules/{}/package.json'.format(project_cwd, mod_name)
if not os.path.exists(filename):
return_message = public.return_error(public.lang('The specified module is not installed!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
nodejs_version = project_find['project_config']['nodejs_version']
npm_bin = self.get_npm_bin(nodejs_version)
if not npm_bin:
return_message = public.return_error(public.lang('The specified nodejs version does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} update {} &> {}".format(project_find['path'], npm_bin,
mod_name, self._npm_exec_log))
public.WriteLog(self._log_name,
'Node project {}, {} module update completed!'.format(get.project_name, mod_name))
return_message = public.return_data(True, 'Module updated successfully!')
del return_message['status']
return public.return_message(0, 0, return_message)
def _nodejs_error(self, msg):
return_message = public.return_error(msg)
if 'status' in return_message: del return_message['status']
return public.return_message(-1, 0, return_message)
def _parse_domain_list(self, domains):
if isinstance(domains, str):
domains = domains.strip()
if not domains: return []
try:
domains = json.loads(domains)
except:
domains = [i.strip() for i in domains.split(',') if i.strip()]
if not isinstance(domains, list): return []
return domains
def _format_and_validate_domains(self, bind_extranet, domains, port=0, require_domains=True):
try:
bind_extranet = int(bind_extranet)
except:
bind_extranet = 0
if bind_extranet != 1: return [], ''
try:
port = int(port or 0)
except:
port = 0
if port == 0:
return [], public.lang('Please set the running port for the project first.')
domains = self._parse_domain_list(domains)
if require_domains and not domains:
return [], public.lang('Please add at least one domain name in the [Domain Management] option')
format_domains = []
for domain in domains:
domain = str(domain).strip()
if not domain:
return [], public.lang('Domain name cannot be empty')
if domain.find(':') == -1:
domain += ':80'
domain_arr = domain.split(':')
if len(domain_arr) < 2 or not domain_arr[1]:
domain = '{}:80'.format(domain_arr[0])
domain_arr = domain.split(':')
if domain_arr[1] == '443':
return [], public.lang('Do not use system ports 443')
if public.M('domain').where('name=?', (domain_arr[0],)).count():
return [], 'Domain name already exists: {}'.format(domain)
format_domains.append(domain)
return format_domains, ''
def _normalize_package_manager(self, pkg_manager):
pkg_manager = str(pkg_manager or 'npm').strip().lower()
if pkg_manager not in ('npm', 'pnpm', 'yarn'): return 'npm'
return pkg_manager
def _get_package_manager_bin(self, nodejs_version, pkg_manager):
pkg_manager = self._normalize_package_manager(pkg_manager)
if pkg_manager == 'pnpm': return self.get_pnpm_bin(nodejs_version)
if pkg_manager == 'yarn': return self.get_yarn_bin(nodejs_version)
return self.get_npm_bin(nodejs_version)
def _get_package_manager_run_cmd(self, nodejs_version, pkg_manager, project_script):
pkg_manager = self._normalize_package_manager(pkg_manager)
pkg_bin = self._get_package_manager_bin(nodejs_version, pkg_manager)
if not pkg_bin: return False
return '{} run {}'.format(pkg_bin, project_script)
def _normalize_project_name(self, name, pid=0):
name = str(name or '').strip()
if name.startswith('@'): name = name[1:]
name = name.replace('/', '_').replace('\\', '_')
name = re.sub(r'[^0-9A-Za-z_]+', '_', name)
name = re.sub(r'_+', '_', name).strip('_')
if not name:
name = 'node_project_{}'.format(pid) if pid else 'node_project'
return name
def _make_unique_project_name(self, name, existing_names=None, pid=0):
if existing_names is None: existing_names = set()
base_name = self._normalize_project_name(name, pid)
project_name = base_name
index = 1
while project_name in existing_names or public.M('sites').where('name=?', (project_name,)).count():
project_name = '{}_{}'.format(base_name, index)
index += 1
return project_name
def _read_proc_cwd(self, pid):
try:
cwd_link = '/proc/{}/cwd'.format(pid)
if os.path.islink(cwd_link):
return os.path.realpath(os.readlink(cwd_link))
except:
pass
return ''
def _read_proc_exe(self, pid):
try:
exe_link = '/proc/{}/exe'.format(pid)
if os.path.islink(exe_link):
return os.path.realpath(os.readlink(exe_link))
except:
pass
return ''
def _read_proc_cmdline(self, pid):
try:
cmdline_file = '/proc/{}/cmdline'.format(pid)
if not os.path.exists(cmdline_file): return []
with open(cmdline_file, 'rb') as fp:
data = fp.read()
if not data: return []
return self._clean_cmdline([i.decode('utf-8', 'ignore') for i in data.split(b'\0') if i])
except:
return []
def _uid_to_username(self, uid):
try:
import pwd
return pwd.getpwuid(int(uid)).pw_name
except:
pass
return str(uid) if uid not in [None, ''] else 'www'
def _read_proc_status(self, pid):
status_info = {}
try:
status_file = '/proc/{}/status'.format(pid)
if not os.path.exists(status_file): return status_info
with open(status_file, 'r') as fp:
for line in fp:
if ':' not in line: continue
key, value = line.split(':', 1)
status_info[key.strip()] = value.strip()
except:
pass
return status_info
def _read_process_basic_from_proc(self, pid):
try:
if not os.path.exists('/proc/{}'.format(pid)): return {}
cmdline = self._read_proc_cmdline(pid)
if not cmdline: return {}
status_info = self._read_proc_status(pid)
uid = ''
if status_info.get('Uid'):
uid = status_info['Uid'].split()[0]
return {
'pid': int(pid),
'ppid': int(status_info.get('PPid', '0').split()[0]),
'name': status_info.get('Name', ''),
'cmdline': cmdline,
'cwd': self._read_proc_cwd(pid),
'exe': self._read_proc_exe(pid),
'user': self._uid_to_username(uid)
}
except:
return {}
def _read_process_basic(self, pid):
try:
p = psutil.Process(int(pid))
if p.status() == 'zombie': return {}
with p.oneshot():
cmdline = self._clean_cmdline(p.cmdline())
if not cmdline: return {}
try:
cwd = p.cwd()
except:
cwd = self._read_proc_cwd(pid)
try:
exe = p.exe()
except:
exe = self._read_proc_exe(pid)
return {
'pid': int(pid),
'ppid': p.ppid(),
'name': p.name(),
'cmdline': cmdline,
'cwd': os.path.realpath(cwd) if cwd else '',
'exe': os.path.realpath(exe) if exe else '',
'user': p.username()
}
except:
pass
return self._read_process_basic_from_proc(pid)
def _clean_cmdline(self, cmdline):
result = []
for arg in cmdline:
arg = str(arg or '').strip()
if arg:
result.append(arg)
return result
def _detect_package_manager(self, cmdline):
cmdline = self._normalize_cmdline_args(cmdline)
manager_map = {
'npm': ('npm', 'npm-cli', 'npm-cli.js', 'npm.cmd'),
'pnpm': ('pnpm', 'pnpm.cjs', 'pnpm.js', 'pnpm.cmd'),
'yarn': ('yarn', 'yarn.js', 'yarnpkg', 'yarnpkg.js', 'yarn.cmd')
}
for index, arg in enumerate(cmdline):
base_name = os.path.basename(str(arg)).lower()
for manager, names in manager_map.items():
if base_name in names:
return manager, index
return '', -1
def _normalize_cmdline_args(self, cmdline):
result = []
manager_names = ('npm', 'pnpm', 'yarn', 'node', 'nodejs', 'npm-cli.js', 'pnpm.cjs', 'yarn.js', 'yarnpkg')
for arg in cmdline:
arg = str(arg or '').strip()
if not arg: continue
parts = arg.split()
if len(parts) > 1 and os.path.basename(parts[0]).lower() in manager_names:
result += parts
else:
result.append(arg)
return result
def _command_args_after_manager(self, cmdline, manager_index):
cmdline = self._normalize_cmdline_args(cmdline)
args = [str(i) for i in cmdline[manager_index + 1:]]
if '--' in args:
args = args[:args.index('--')]
options_need_value = ('--prefix', '--userconfig', '--cache', '--registry', '--workspace', '-w', '--dir', '-C')
index = 0
while index < len(args):
arg = args[index]
if not arg.startswith('-'):
return args[index:]
if arg in options_need_value and index + 1 < len(args):
index += 2
continue
index += 1
return []
def _parse_package_command(self, cmdline, scripts=None):
manager, manager_index = self._detect_package_manager(cmdline)
result = {
'manager': manager,
'manager_index': manager_index,
'args': [],
'script': ''
}
if not manager: return result
args = self._command_args_after_manager(cmdline, manager_index)
result['args'] = args
if not isinstance(scripts, dict) or not scripts or not args:
return result
command = args[0]
if command in ('run', 'run-script') and len(args) > 1:
script_name = args[1]
result['script'] = script_name if script_name in scripts else ''
elif manager in ('pnpm', 'yarn') and command in scripts:
result['script'] = command
elif manager == 'npm' and command in ('start', 'test', 'restart', 'stop') and command in scripts:
result['script'] = command
return result
def _find_package_root(self, cwd, max_depth=8):
if not cwd: return ''
path = os.path.realpath(cwd)
for _ in range(max_depth + 1):
if os.path.exists('{}/package.json'.format(path)):
return path
parent = os.path.dirname(path)
if not parent or parent == path: break
path = parent
return ''
def _read_package_json(self, project_cwd):
package_file = '{}/package.json'.format(project_cwd)
if not os.path.exists(package_file): return {}
package_content = public.readFile(package_file)
if not package_content: return {}
try:
package_info = json.loads(package_content)
return package_info if isinstance(package_info, dict) else {}
except:
return {}
def _collect_child_pids(self, pid):
try:
p = psutil.Process(int(pid))
pids = [int(pid)]
for child in p.children(recursive=True):
try:
if child.status() != 'zombie':
pids.append(child.pid)
except:
continue
return sorted(list(set(pids)))
except:
if os.path.exists('/proc/{}'.format(pid)): return [int(pid)]
return []
def _get_listen_info_by_pids(self, pids):
listen_info = {}
listen_status = getattr(psutil, 'CONN_LISTEN', 'LISTEN')
for pid in pids:
try:
p = psutil.Process(int(pid))
try:
connects = p.connections(kind='inet')
except TypeError:
connects = p.connections()
for conn in connects:
if conn.status != listen_status and conn.status != 'LISTEN': continue
laddr = conn.laddr
if not laddr: continue
local_addr = getattr(laddr, 'ip', None)
local_port = getattr(laddr, 'port', None)
if local_addr is None:
try:
local_addr = laddr[0]
local_port = laddr[1]
except:
continue
if not local_port: continue
listen_info[int(local_port)] = {
'pid': int(pid),
'local_addr': local_addr,
'port': int(local_port)
}
except:
continue
return [listen_info[i] for i in sorted(listen_info.keys())]
def _get_primary_listen(self, listen_info):
if not listen_info: return {}
for item in listen_info:
if item.get('local_addr') in ('0.0.0.0', '::', '*'):
return item
return listen_info[0]
def _is_pm2_cmdline(self, cmdline):
cmd_text = ' '.join([str(i).lower() for i in cmdline])
return cmd_text.find('pm2') != -1
def _is_docker_process(self, pid):
try:
cgroup_file = '/proc/{}/cgroup'.format(pid)
if not os.path.exists(cgroup_file): return False
cgroup = public.readFile(cgroup_file)
if not cgroup: return False
cgroup = cgroup.lower()
for key in ('docker', 'kubepods', 'containerd', 'libpod'):
if key in cgroup: return True
except:
pass
return False
def _extract_nodejs_version_from_path(self, path):
path = str(path or '').replace('\\', '/')
match = re.search(r'/nodejs/(v\d+(?:\.\d+){1,3})(?:/|$)', path)
return match.group(1) if match else ''
def _append_nodejs_version_candidate(self, candidates, source, path, pid=0):
version = self._extract_nodejs_version_from_path(path)
if not version: return
item = {
'source': source,
'version': version,
'path': str(path or '')
}
try:
pid = int(pid or 0)
if pid: item['pid'] = pid
except:
pass
for candidate in candidates:
if candidate.get('source') == item.get('source') and candidate.get('path') == item.get(
'path') and candidate.get('pid', 0) == item.get('pid', 0):
return
candidates.append(item)
def _detect_nodejs_version_detail(self, process_info, pids=None, listen_info=None):
if pids is None: pids = []
if listen_info is None: listen_info = []
pid = 0
cmdline = []
if isinstance(process_info, dict):
pid = int(process_info.get('pid', 0) or 0)
cmdline = [str(i) for i in process_info.get('cmdline', [])]
else:
cmdline = [str(i) for i in process_info]
listen_pids = []
for item in listen_info:
try:
item_pid = int(item.get('pid', 0) or 0)
if item_pid: listen_pids.append(item_pid)
except:
continue
candidates = []
# If the command uses an absolute version-manager binary, it is the strongest signal.
for item in cmdline:
self._append_nodejs_version_candidate(candidates, 'cmdline', item, pid)
if isinstance(process_info, dict):
self._append_nodejs_version_candidate(candidates, 'process_exe', process_info.get('exe', ''), pid)
checked_pids = []
for item_pid in [pid] + listen_pids + [int(i) for i in pids if i]:
if not item_pid or item_pid in checked_pids: continue
checked_pids.append(item_pid)
exe = self._read_proc_exe(item_pid)
source = 'proc_exe'
if item_pid in listen_pids:
source = 'listen_proc_exe'
elif pid and item_pid != pid:
source = 'child_proc_exe'
self._append_nodejs_version_candidate(candidates, source, exe, item_pid)
try:
if pid:
env_path = psutil.Process(int(pid)).environ().get('PATH', '')
for item in env_path.split(':'):
self._append_nodejs_version_candidate(candidates, 'env_path', item, pid)
except:
pass
if candidates:
result = candidates[0].copy()
versions = []
for item in candidates:
if item.get('version') not in versions:
versions.append(item.get('version'))
result['candidates'] = candidates
if len(versions) > 1:
result['conflict_versions'] = versions
return result
default_version = self._get_default_nodejs_version()
return {
'source': 'default',
'version': default_version,
'path': '',
'candidates': []
}
def _detect_nodejs_version(self, process_info, pids=None, listen_info=None):
return self._detect_nodejs_version_detail(process_info, pids, listen_info).get('version', '')
def _version_key(self, version):
nums = re.findall(r'\d+', str(version))
return tuple([int(i) for i in nums[:4]]) if nums else (0,)
def _get_default_nodejs_version(self):
try:
res = self.get_nodejs_version(public.to_dict_obj({}))
nodejs_list = res.get('message', []) if isinstance(res, dict) else []
if nodejs_list:
return sorted(nodejs_list, key=self._version_key, reverse=True)[0]
except:
pass
return ''
def _get_existing_node_scan_targets(self):
existing_names = set()
existing_paths = set()
try:
project_list = public.M('sites').where('project_type=?', ('Node',)).field(
'name,path,project_config').select() or []
for project in project_list:
if project.get('name'): existing_names.add(project['name'])
if project.get('path'): existing_paths.add(os.path.realpath(project['path']))
try:
project_config = json.loads(project.get('project_config', '{}'))
if project_config.get('project_cwd'):
existing_paths.add(os.path.realpath(project_config['project_cwd']))
except:
pass
except:
pass
return existing_paths, existing_names
def _inspect_package_manager_process(self, pid, existing_paths=None, existing_names=None, check_existing=True):
if existing_paths is None: existing_paths = set()
if existing_names is None: existing_names = set()
process_info = self._read_process_basic(pid)
if not process_info: return None, 'unreadable'
package_command = self._parse_package_command(process_info['cmdline'])
manager = package_command['manager']
if not manager: return None, 'not_package_manager'
if self._is_pm2_cmdline(process_info['cmdline']): return None, 'unsupported'
if self._is_docker_process(pid): return None, 'unsupported'
project_cwd = self._find_package_root(process_info.get('cwd', ''))
if not project_cwd: return None, 'no_package_json'
project_cwd = os.path.realpath(project_cwd)
if check_existing and project_cwd in existing_paths:
return None, 'already_imported'
package_info = self._read_package_json(project_cwd)
scripts = package_info.get('scripts', {})
if not isinstance(scripts, dict) or not scripts:
return None, 'no_script_match'
package_command = self._parse_package_command(process_info['cmdline'], scripts)
project_script = package_command['script']
if not project_script:
return None, 'no_script_match'
pids = self._collect_child_pids(pid)
listen_info = self._get_listen_info_by_pids(pids)
primary_listen = self._get_primary_listen(listen_info)
raw_name = package_info.get('name') or os.path.basename(project_cwd)
project_name = self._make_unique_project_name(raw_name, existing_names, pid)
normalized_name = self._normalize_project_name(raw_name, pid)
warnings = []
if project_name != raw_name:
warnings.append('Project name normalized from [{}] to [{}].'.format(raw_name, project_name))
if not primary_listen:
warnings.append('No listening port was detected, please confirm manually.')
scan_source = '{}:{}:{}:{}'.format(pid, project_cwd, manager, project_script)
version_detail = self._detect_nodejs_version_detail(process_info, pids, listen_info)
candidate = {
'scan_id': hashlib.sha1(scan_source.encode('utf-8')).hexdigest()[:16],
'pid': int(pid),
'pids': pids,
'ppid': process_info.get('ppid', 0),
'process_name': process_info.get('name', ''),
'process_cwd': process_info.get('cwd', ''),
'cmdline': process_info.get('cmdline', []),
'detected_command': ' '.join(process_info.get('cmdline', [])),
'project_cwd': project_cwd,
'project_name': project_name,
'raw_project_name': raw_name,
'normalized_project_name': normalized_name,
'package_manager': manager,
'pkg_manager': manager,
'project_script': project_script,
'start_command': '{} run {}'.format(manager, project_script),
'script_source': 'package.json scripts.{}'.format(project_script),
'port': int(primary_listen.get('port', 0)) if primary_listen else 0,
'ports': [i['port'] for i in listen_info],
'listen_addr': primary_listen.get('local_addr', '') if primary_listen else '',
'listen_info': listen_info,
'run_user': process_info.get('user', 'www') or 'www',
'nodejs_version': version_detail.get('version', ''),
'status': 'importable' if primary_listen else 'need_confirm',
# 'confidence': 90 if primary_listen else 75,
'warnings': warnings,
'start_type': 'package_script'
}
return candidate, ''
def scan_import_projects(self, get):
'''
@name 扫描可导入的Node项目
@return dict
'''
existing_paths, existing_names = self._get_existing_node_scan_targets()
skipped = {
'unsupported': 0,
'no_package_json': 0,
'no_script_match': 0,
'already_imported': 0,
'unreadable': 0,
'duplicated': 0
}
candidates = []
seen_keys = set()
matched_processes = 0
try:
pids = psutil.pids()
except:
pids = []
for pid in pids:
candidate, reason = self._inspect_package_manager_process(pid, existing_paths, existing_names, True)
if reason == 'not_package_manager':
continue
if not candidate:
if reason in skipped:
skipped[reason] += 1
else:
skipped['unsupported'] += 1
continue
matched_processes += 1
scan_key = '{}|{}|{}'.format(candidate['project_cwd'], candidate['package_manager'],
candidate['project_script'])
if scan_key in seen_keys:
skipped['duplicated'] += 1
continue
seen_keys.add(scan_key)
existing_names.add(candidate['project_name'])
candidates.append(candidate)
data = {
'total': len(candidates),
'matched_processes': matched_processes,
'skipped': skipped,
'supported_package_managers': ['npm', 'pnpm', 'yarn'],
'data': candidates
}
public.set_module_logs('node_site', 'scan_import_projects')
return public.return_message(0, 0, data)
def create_scan_import_project(self, get):
'''
@name 导入扫描到的Node项目
@param get<dict_obj>{
pid: int<扫描到的主进程PID>
project_cwd: string<项目目录>
project_name: string<项目名称>
package_manager: string<包管理器 npm/pnpm/yarn>
project_script: string<项目脚本>
port: int<项目实际监听端口>
run_user: string<运行用户>
nodejs_version: string<nodejs版本>
bind_extranet: int<是否绑定外网> 1:是 0:否
domains: list<域名列表> ["domain1:80","domain2:80"] // 在bind_extranet=1时,需要填写
}
@return dict
'''
if not isinstance(get, public.dict_obj):
get = public.to_dict_obj(get)
if not self._is_install_nodejs(get):
return self._nodejs_error(public.lang('Please install nodejs version manager first'))
try:
pid = int(get.get('pid', 0))
except:
pid = 0
if pid <= 0 or not os.path.exists('/proc/{}'.format(pid)):
return self._nodejs_error('The scanned process no longer exists, please rescan.')
candidate, reason = self._inspect_package_manager_process(pid, set(), set(), False)
if not candidate:
return self._nodejs_error(
'The scanned process is no longer a supported npm/pnpm/yarn project: {}'.format(reason))
project_cwd = os.path.realpath(str(get.get('project_cwd', candidate['project_cwd'])).strip())
if project_cwd != os.path.realpath(candidate['project_cwd']):
return self._nodejs_error(
'The project directory does not match the current running process, please rescan.')
package_info = self._read_package_json(project_cwd)
scripts = package_info.get('scripts', {})
if not isinstance(scripts, dict) or not scripts:
return self._nodejs_error('No scripts were found in package.json.')
project_script = str(get.get('project_script', candidate['project_script'])).strip()
if not project_script:
return self._nodejs_error(public.lang('Start command cannot be empty'))
if project_script not in scripts:
return self._nodejs_error('The startup script does not exist in package.json: {}'.format(project_script))
if not re.match(r'^[0-9A-Za-z_:\-\.\/]+$', project_script):
return self._nodejs_error(
'The startup script name contains unsupported characters: {}'.format(project_script))
pkg_manager = self._normalize_package_manager(
get.get('package_manager', get.get('pkg_manager', candidate['package_manager'])))
try:
port = int(str(get.get('port', candidate.get('port', 0)) or 0).strip())
except:
return self._nodejs_error('The project port is invalid: {}'.format(get.get('port', '')))
if port < 0 or port > 65535:
return self._nodejs_error('The project port is invalid: {}'.format(port))
if port == 443:
return self._nodejs_error(public.lang('Do not use system ports 443.'))
if port and self.check_port_is_used(port, True):
return self._nodejs_error('This port is already occupied by an aaPanel Node project, port: {}'.format(port))
existing_paths, existing_names = self._get_existing_node_scan_targets()
if project_cwd in existing_paths:
return self._nodejs_error('The project directory has already been imported: {}'.format(project_cwd))
project_name = self._make_unique_project_name(get.get('project_name', candidate['project_name']),
existing_names, pid)
run_user = str(get.get('run_user', candidate.get('run_user', 'www')) or 'www').strip()
nodejs_version = str(get.get('nodejs_version', candidate.get('nodejs_version', '')) or '').strip()
if not nodejs_version:
nodejs_version = self._get_default_nodejs_version()
if not nodejs_version:
return self._nodejs_error('Please install or select a Node.js version first.')
if not self.get_node_bin(nodejs_version):
return self._nodejs_error(public.lang('The specified nodejs version does not exist!'))
try:
bind_extranet = int(get.get('bind_extranet', 0) or 0)
except:
bind_extranet = 0
try:
is_power_on = int(get.get('is_power_on', 0) or 0)
except:
is_power_on = 0
try:
max_memory_limit = int(get.get('max_memory_limit', 0) or 0)
except:
max_memory_limit = 0
format_domains, error_msg = self._format_and_validate_domains(bind_extranet, get.get('domains', []), port)
if error_msg: return self._nodejs_error(error_msg)
project_config = {
'project_name': project_name,
'project_cwd': project_cwd,
'project_script': project_script,
'project_type': 'nodejs',
'start_type': 'package_script',
'import_source': 'scan',
'pkg_manager': pkg_manager,
'package_manager': pkg_manager,
'bind_extranet': bind_extranet,
'domains': [],
'is_power_on': is_power_on,
'run_user': run_user,
'max_memory_limit': max_memory_limit,
'nodejs_version': nodejs_version,
'port': port,
'scan_pid': pid,
'scan_pids': candidate.get('pids', []),
'scan_time': public.getDate()
}
pdata = {
'name': project_name,
'path': project_cwd,
'ps': str(get.get('project_ps', get.get('ps', 'Scan-Imported')) or 'Scan-Imported'),
'status': 1,
'type_id': 0,
'project_type': 'Node',
'project_config': json.dumps(project_config),
'addtime': public.getDate()
}
project_id = public.M('sites').insert(pdata)
pid_file = "{}/{}.pid".format(self._node_pid_path, project_name)
public.writeFile(pid_file, str(pid))
if bind_extranet:
get.project_name = project_name
get.domains = format_domains
self.project_add_domain(get)
public.WriteLog(self._log_name, 'Import scanned Node.js project {}'.format(project_name))
return_message = public.return_data(True, 'Imported project successfully', project_id)
return_message['project_name'] = project_name
return_message['pid'] = pid
if 'status' in return_message: del return_message['status']
public.set_module_logs('node_site', 'create_scan_import_project')
return public.return_message(0, 0, return_message)
def create_project(self, get):
'''
@name 创建新的项目
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
project_cwd: string<项目目录>
project_script: string<项目脚本>
project_ps: string<项目备注信息>
bind_extranet: int<是否绑定外网> 1:是 0:否
domains: list<域名列表> ["domain1:80","domain2:80"] // 在bind_extranet=1时,需要填写
is_power_on: int<是否开机启动> 1:是 0:否
run_user: string<运行用户>
max_memory_limit: int<最大内存限制> // 超出此值项目将被强制重启
nodejs_version: string<nodejs版本>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_cwd').String(),
Param('project_name').String(),
Param('project_script').String(),
Param('port').String(),
Param('run_user').String(),
Param('nodejs_version').String(),
Param('project_ps').String(),
# Param('domains').List(),
Param('project_env').String(),
Param('bind_extranet').Integer(),
Param('is_power_on').Integer(),
Param('max_memory_limit').Integer(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
public.set_module_logs('node_site_nodejs', 'create_app', 1)
public.set_module_logs('node_site', 'create_app', 1)
if not isinstance(get, public.dict_obj):
return_message = public.return_error(public.lang('The parameter type is wrong, need dict obj object'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not self._is_install_nodejs(get):
return_message = public.return_error(public.lang('Please install nodejs version manager first'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_name = get.project_name.strip()
if not re.match(r"^\w+$", project_name):
return_message = public.return_error(public.lang(
'The project name format is incorrect and supports letters, numbers, underscores, and expressions: ^[0-9A-Za-z_]$'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if public.M('sites').where('name=?', (get.project_name,)).count():
return_message = public.return_error(
'The specified project name already exists: {}'.format(get.project_name))
del return_message['status']
return public.return_message(-1, 0, return_message)
get.project_cwd = get.project_cwd.strip()
if not os.path.exists(get.project_cwd):
return_message = public.return_error('The project directory does not exist: {}'.format(get.project_cwd))
del return_message['status']
return public.return_message(-1, 0, return_message)
# 端口占用检测
if get.get('port') == '443':
return_message = public.return_error(public.lang('Do not use system ports 443.'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if self.check_port_is_used(get.get('port/port')):
return_message = public.return_error(
'This port is already occupied, please modify your project port, port: {}'.format(get.port))
del return_message['status']
return public.return_message(-1, 0, return_message)
format_domains, error_msg = self._format_and_validate_domains(get.bind_extranet, get.get('domains', []),
get.port)
if error_msg: return self._nodejs_error(error_msg)
pdata = {
'name': get.project_name,
'path': get.project_cwd,
'ps': get.project_ps,
'status': 1,
'type_id': 0,
'project_type': 'Node',
'project_config': json.dumps(
{
'project_name': get.project_name,
'project_cwd': get.project_cwd,
'project_script': get.project_script,
'bind_extranet': int(get.bind_extranet),
'domains': [],
'is_power_on': int(get.get('is_power_on', 0)),
'run_user': get.run_user,
'max_memory_limit': get.max_memory_limit,
'nodejs_version': get.nodejs_version,
'pkg_manager': self._normalize_package_manager(
get.get('pkg_manager', get.get('package_manager', 'npm'))),
'port': int(get.port)
}
),
'addtime': public.getDate()
}
project_id = public.M('sites').insert(pdata)
if int(get.bind_extranet) == 1:
get.domains = format_domains
self.project_add_domain(get)
self.set_config(get.project_name)
public.WriteLog(self._log_name, 'Add Node.js project {}'.format(get.project_name))
self.install_packages(get)
self.start_project(get)
return_message = public.return_data(True, 'Added project successfully', project_id)
# ================ git start ======================
try:
if get.get('deploy_type') in ['ssh', 'github']:
if get.get('deploy_type') == 'ssh':
from git_tools import GitTools
git_obj = GitTools()
# 已clone项目,使用.git导入
res = git_obj.get_git_directory(public.to_dict_obj({"site_id": project_id}))
if res['status'] != 0:
return public.return_message(-1, 0, res['message'])
res = res['message']
res = git_obj.import_existing_repository(
public.to_dict_obj({"site_id": project_id, "repo": res['repo'],
"branch": res['branch'], "key_path": res['key_path'],
"project_type": 'node'}))
if res['status'] != 0:
return public.return_message(-1, 0, res['message'])
except Exception as e:
print(e)
# 失败不删除项目
return public.return_message(-1, 0, public.lang("There was an error while configuring Git tools!"))
# ================ git end ======================
del return_message['status']
return public.return_message(0, 0, return_message)
def modify_project(self, get):
'''
@name 修改指定项目
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
project_cwd: string<项目目录>
project_script: string<项目脚本>
project_ps: string<项目备注信息>
is_power_on: int<是否开机启动> 1:是 0:否
run_user: string<运行用户>
max_memory_limit: int<最大内存限制> // 超出此值项目将被强制重启
nodejs_version: string<nodejs版本>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_cwd').String(),
Param('project_name').String(),
Param('project_script').String(),
Param('port').String(),
Param('run_user').String(),
Param('nodejs_version').String(),
Param('project_ps').String(),
Param('domains').String(),
Param('bind_extranet').Integer(),
Param('is_power_on').Integer(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
if not isinstance(get, public.dict_obj):
return_message = public.return_error(public.lang('The parameter type is wrong, need dict obj'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not self._is_install_nodejs(get):
return_message = public.return_error(
public.lang('Please install nodejs version manager before installing at least one nodejs'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error('Item does not exist: {}'.format(get.project_name))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not os.path.exists(get.project_cwd):
return_message = public.return_error('The project directory does not exist: {}'.format(get.project_cwd))
del return_message['status']
return public.return_message(-1, 0, return_message)
rebuild = False
if hasattr(get, 'port'):
if int(project_find['project_config']['port']) != int(get.port):
if self.check_port_is_used(get.get('port/port'), True):
return_message = public.return_error(
'The port is already occupied, please modify your port, port: {}'.format(get.port))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_find['project_config']['port'] = int(get.port)
if hasattr(get, 'project_cwd'): project_find['project_config']['project_cwd'] = get.project_cwd
if hasattr(get, 'project_script'):
if not get.project_script.strip():
return_message = public.return_error(public.lang('Start command cannot be empty'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_find['project_config']['project_script'] = get.project_script.strip()
if hasattr(get, 'is_power_on'): project_find['project_config']['is_power_on'] = get.is_power_on
if hasattr(get, 'run_user'): project_find['project_config']['run_user'] = get.run_user
if hasattr(get, 'max_memory_limit'): project_find['project_config']['max_memory_limit'] = get.max_memory_limit
if hasattr(get, 'nodejs_version'):
if project_find['project_config']['nodejs_version'] != get.nodejs_version:
rebuild = True
project_find['project_config']['nodejs_version'] = get.nodejs_version
pdata = {
'path': get.project_cwd,
'ps': get.project_ps,
'project_config': json.dumps(project_find['project_config'])
}
public.M('sites').where('name=?', (get.project_name,)).update(pdata)
self.set_config(get.project_name)
public.WriteLog(self._log_name, 'Modify Node.js project {}'.format(get.project_name))
if rebuild:
self.rebuild_project(get.project_name)
return_message = public.return_data(True, 'Modify the project successfully')
del return_message['status']
return public.return_message(0, 0, return_message)
def rebuild_project(self, project_name):
'''
@name 重新构建指定项目
@author hwliang<2021-08-26>
@param project_name: string<项目名称>
@return bool
'''
project_find = self.get_project_find(project_name)
if not project_find: return False
nodejs_version = project_find['project_config']['nodejs_version']
npm_bin = self.get_npm_bin(nodejs_version)
public.ExecShell(
self.get_last_env(nodejs_version) + "cd {} && {} rebuild >> {} 2>&1".format(project_find['path'], npm_bin,
self._npm_exec_log))
return True
def remove_project(self, get):
'''
@name 删除指定项目
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error('The specified item does not exist: {}'.format(get.project_name))
del return_message['status']
return public.return_message(-1, 0, return_message)
self.stop_project(get)
self.clear_config(get.project_name)
public.M('domain').where('pid=?', (project_find['id'],)).delete()
public.M('sites').where('name=?', (get.project_name,)).delete()
pid_file = "{}/{}.pid".format(self._node_pid_path, get.project_name)
if os.path.exists(pid_file): os.remove(pid_file)
script_file = '{}/{}.sh'.format(self._node_run_scripts, get.project_name)
if os.path.exists(script_file): os.remove(script_file)
log_file = '{}/{}.log'.format(self._node_logs_path, get.project_name)
if os.path.exists(log_file): os.remove(log_file)
public.WriteLog(self._log_name, 'Delete Node.js project {}'.format(get.project_name))
return_message = public.return_data(True, 'Successfully deleted item')
del return_message['status']
return public.return_message(0, 0, return_message)
# 批量操作项目
def batch_operation_project(self, get):
'''
@name 批量操作项目
@param project_names []
@return dict
'''
try:
get.validate([
Param('project_names'),
Param('operation_type'),
])
project_names = json.loads(get.get('project_names', []))
operation_type = get.get('operation_type')
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
if not project_names:
return public.return_message(-1, 0, public.lang('The project list is empty.'))
if operation_type not in ['delete', 'start', 'stop', 'restart']:
return public.return_message(-1, 0, public.lang('Operation type is empty'))
project_list = public.S('sites').where_in("name", project_names, 'OR').select()
if not project_list:
return public.return_message(-1, 0, public.lang('The project list is empty.'))
success_count = 0
msg_list = []
from mod.project.nodejs import comMod
for project in project_list:
try:
project['project_config'] = json.loads(project['project_config'])
p_type = 'pm2' if project['project_config'].get('pm2_name') else 'nodejs'
if operation_type == 'delete':
temp_get = public.to_dict_obj({'project_name': project['name'], 'project_type': p_type,
'pm2_name': project['project_config'].get('pm2_name')})
res = comMod.main().delete(temp_get)
elif operation_type in ['start', 'stop', 'restart']:
res = comMod.main().set_project_status(public.to_dict_obj(
{"project_name": project['name'], "project_type": p_type, "status": operation_type,
"pm2_name": project['project_config'].get('pm2_name'),
"run_user": project['project_config'].get('run_user')}))
if res['status'] != 0:
msg_list.append({'name': project['name'], 'status': False, 'msg': res['message']['result']})
else:
success_count += 1
msg_list.append({'name': project['name'], 'status': True, 'msg': res['message']['result']})
if operation_type == 'delete':
# 删除git
from git_tools import GitTools
GitTools().del_site_git(public.to_dict_obj({'site_id': project['id']}))
except Exception as e:
msg_list.append({'name': project['name'], 'status': False, 'msg': str(e)})
msg = f"Successfully {success_count} items.Failed on {len(project_names) - success_count} projects."
return public.return_message(0, 0, {"msg": msg, "msg_list": msg_list})
def project_get_domain(self, get):
'''
@name 获取指定项目的域名列表
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
project_id = public.M('sites').where('name=?', (get.project_name,)).getField('id')
if not project_id:
return public.return_message(0, 0, [])
domains = public.M('domain').where('pid=?', (project_id,)).order('id desc').select()
return public.return_message(0, 0, domains)
def project_add_domain(self, get):
'''
@name 为指定项目添加域名
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
domains: list<域名列表>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
# Param('domains').List(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('The specified item does not exist'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_id = project_find['id']
domains = get.domains
success_list = []
error_list = []
for domain in domains:
domain = domain.strip()
if not domain:
return_message = public.return_error(public.lang('Domain name cannot be empty'))
del return_message['status']
return public.return_message(-1, 0, return_message)
domain_arr = domain.split(':')
if len(domain_arr) == 1:
domain_arr.append(80)
domain += ':80'
else:
if domain_arr[1] == '443':
error_list.append(domain)
continue
if not public.M('domain').where('name=?', (domain_arr[0],)).count():
public.M('domain').add('name,pid,port,addtime',
(domain_arr[0], project_id, domain_arr[1], public.getDate()))
if not domain in project_find['project_config']['domains']:
project_find['project_config']['domains'].append(domain)
public.WriteLog(self._log_name, 'Successfully added the domain [{}] to the project [{}]'.format(domain,
get.project_name))
success_list.append(domain)
else:
public.WriteLog(self._log_name, 'Domain [{}] already exists'.format(domain))
error_list.append(domain)
if success_list:
public.M('sites').where('id=?', (project_id,)).save('project_config',
json.dumps(project_find['project_config']))
self.set_config(get.project_name)
return_message = public.return_data(True, "[{}] domain names added successfully, [{}] failed!".format(
len(success_list), len(error_list)), error_msg=error_list)
del return_message['status']
return public.return_message(0, 0, return_message)
return_message = public.return_data(False, "[{}] domain names added successfully, [{}] failed!".format(
len(success_list), len(error_list)), error_msg=error_list)
del return_message['status']
return public.return_message(-1, 0, return_message)
def project_remove_domain(self, get):
'''
@name 为指定项目删除域名
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
domain: string<域名>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
Param('domain').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('The specified item does not exist'))
del return_message['status']
return public.return_message(-1, 0, return_message)
last_domain = get.domain
domain_arr = get.domain.split(':')
if len(domain_arr) == 1:
domain_arr.append(80)
project_id = public.M('sites').where('name=?', (get.project_name,)).getField('id')
if project_find['project_config']['bind_extranet']:
domain_count = public.M('domain').where('pid=?', (project_id,)).count()
if domain_count <= 1:
return_message = public.return_error(
public.lang('At least one domain name is required for the mapped project'))
del return_message['status']
return public.return_message(-1, 0, return_message)
domain_id = public.M('domain').where('name=? AND pid=?', (domain_arr[0], project_id)).getField('id')
if not domain_id:
return_message = public.return_error(public.lang('The specified domain name does not exist'))
del return_message['status']
return public.return_message(-1, 0, return_message)
public.M('domain').where('id=?', (domain_id,)).delete()
if get.domain in project_find['project_config']['domains']:
project_find['project_config']['domains'].remove(get.domain)
if get.domain + ":80" in project_find['project_config']['domains']:
project_find['project_config']['domains'].remove(get.domain + ":80")
# 处理端口残留
domains = []
for domain in project_find['project_config']['domains']:
if get.domain == domain.split(':')[0]:
continue
domains.append(domain)
project_find['project_config']['domains'] = domains
public.M('sites').where('id=?', (project_id,)).save('project_config',
json.dumps(project_find['project_config']))
public.WriteLog(self._log_name,
'From project: [{}], delete domain name [{}]'.format(get.project_name, get.domain))
self.set_config(get.project_name)
return_message = public.return_data(True, 'Domain name deleted successfully')
del return_message['status']
return public.return_message(0, 0, return_message)
def bind_extranet(self, get):
'''
@name 绑定外网
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
# Param('domains').List(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_name = get.project_name.strip()
project_find = self.get_project_find(project_name)
if not project_find:
return_message = public.return_error(public.lang('Item does not exist'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not project_find['project_config']['domains']:
return_message = public.return_error(
public.lang('Please add at least one domain name in the [Domain Management] option'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not project_find['project_config'].get('port'):
return_message = public.return_error(public.lang('Please set the running port for the project first.'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_find['project_config']['bind_extranet'] = 1
public.M('sites').where("id=?", (project_find['id'],)).setField('project_config',
json.dumps(project_find['project_config']))
self.set_config(project_name)
public.WriteLog(self._log_name, 'Node project{}, enable mapping'.format(project_name))
return_message = public.return_data(True, 'Enable the mapping successfully')
del return_message['status']
return public.return_message(0, 0, return_message)
def set_config(self, project_name):
'''
@name 设置项目配置
@author hwliang<2021-08-09>
@param project_name: string<项目名称>
@return bool
'''
project_find = self.get_project_find(project_name)
if not project_find: return False
if not project_find['project_config']: return False
if not project_find['project_config']['bind_extranet']: return False
if not project_find['project_config']['domains']: return False
self.set_nginx_config(project_find)
self.set_apache_config(project_find)
self.set_ols_config(project_find)
public.serviceReload()
return True
def clear_config(self, project_name):
'''
@name 清除项目配置
@author hwliang<2021-08-09>
@param project_name: string<项目名称>
@return bool
'''
project_find = self.get_project_find(project_name)
if not project_find: return False
self.clear_nginx_config(project_find)
self.clear_apache_config(project_find)
self.clear_ols_config(project_find)
public.serviceReload()
return True
def clear_ols_config(self, project_find):
'''
@name 删除OLS配置
@param project_find: dict<项目信息>
@return bool
'''
project_name = project_find['name']
vhost_dir = public.get_panel_path() + '/vhost/openlitespeed/'
detail_file = vhost_dir + 'detail/' + project_name + '.conf'
main_conf_file = vhost_dir + project_name + '.conf'
listen_dir = vhost_dir + 'listen/'
listen_list = [listen_dir + '80.conf', listen_dir + '443.conf']
# 删除虚拟主机配置文件
if os.path.exists(main_conf_file):
os.remove(main_conf_file)
if os.path.exists(detail_file):
os.remove(detail_file)
# 清理监听器中的映射 (Map) 关系
if os.path.exists(listen_dir):
for conf_name in listen_list:
content = public.readFile(conf_name)
if content:
pattern = r"^\s*map\s+{}\s+.*?\n".format(re.escape(project_name))
if re.search(pattern, content, flags=re.M):
new_content = re.sub(pattern, "", content, flags=re.M)
public.writeFile(conf_name, new_content)
return public.return_message(0, 0, True)
def clear_apache_config(self, project_find):
'''
@name 清除apache配置
@author hwliang<2021-08-09>
@param project_find: dict<项目信息>
@return bool
'''
project_name = project_find['name']
config_file = "{}/apache/node_{}.conf".format(self._vhost_path, project_name)
if os.path.exists(config_file):
os.remove(config_file)
return True
def clear_nginx_config(self, project_find):
'''
@name 清除nginx配置
@author hwliang<2021-08-09>
@param project_find: dict<项目信息>
@return bool
'''
project_name = project_find['name']
config_file = "{}/nginx/node_{}.conf".format(self._vhost_path, project_name)
if os.path.exists(config_file):
os.remove(config_file)
rewrite_file = "{panel_path}/vhost/rewrite/node_{project_name}.conf".format(panel_path=self._panel_path,
project_name=project_name)
if os.path.exists(rewrite_file):
os.remove(rewrite_file)
return True
def set_nginx_config(self, project_find):
'''
@name 设置Nginx配置
@author hwliang<2021-08-09>
@param project_find: dict<项目信息>
@return bool
'''
project_name = project_find['name']
ports = []
domains = []
for d in project_find['project_config']['domains']:
domain_tmp = d.split(':')
if len(domain_tmp) == 1: domain_tmp.append(80)
if not int(domain_tmp[1]) in ports:
ports.append(int(domain_tmp[1]))
if not domain_tmp[0] in domains:
domains.append(domain_tmp[0])
listen_ipv6 = public.listen_ipv6()
listen_ports = ''
for p in ports:
listen_ports += " listen {};\n".format(p)
if listen_ipv6:
listen_ports += " listen [::]:{};\n".format(p)
listen_ports = listen_ports.strip()
is_ssl, is_force_ssl = self.exists_nginx_ssl(project_name)
ssl_config = ''
if is_ssl:
http3_header = ""
if self.is_nginx_http3():
http3_header = '''\n add_header Alt-Svc 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';'''
http3_header += "\n quic_retry on;\n quic_gso on;"
if self.ng_ssl_early_data_enabled():
http3_header += "\n ssl_early_data on;"
listen_ports += "\n listen 443 ssl;"
if listen_ipv6: listen_ports += "\n listen [::]:443 ssl;"
ssl_config = '''ssl_certificate {vhost_path}/cert/{priject_name}/fullchain.pem;
ssl_certificate_key {vhost_path}/cert/{priject_name}/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";{http3_header}
error_page 497 https://$host$request_uri;'''.format(vhost_path=self._vhost_path, priject_name=project_name,
http3_header=http3_header)
if is_force_ssl:
ssl_config += '''
#HTTP_TO_HTTPS_START
if ($server_port !~ 443){
rewrite ^(/.*)$ https://$host$1 permanent;
}
#HTTP_TO_HTTPS_END'''
config_file = "{}/nginx/node_{}.conf".format(self._vhost_path, project_name)
template_file = "{}/template/nginx/node_http.conf".format(self._vhost_path)
config_body = public.readFile(template_file)
config_body = config_body.format(
site_path=project_find['path'],
domains=' '.join(domains),
project_name=project_name,
panel_path=self._panel_path,
log_path=public.get_logs_path(),
url='http://127.0.0.1:{}'.format(project_find['project_config']['port']),
host='$host',
listen_ports=listen_ports,
ssl_config=ssl_config
)
# # 恢复旧的SSL配置
# ssl_config = self.get_nginx_ssl_config(project_name)
# if ssl_config:
# config_body.replace('#error_page 404/404.html;',ssl_config)
rewrite_file = "{panel_path}/vhost/rewrite/node_{project_name}.conf".format(panel_path=self._panel_path,
project_name=project_name)
if not os.path.exists(rewrite_file): public.writeFile(rewrite_file,
'# Please fill in the URLrewrite or custom NGINX config here\n')
public.writeFile(config_file, config_body)
return True
def get_nginx_ssl_config(self, project_name):
'''
@name 获取项目Nginx SSL配置
@author hwliang<2021-08-09>
@param project_name: string<项目名称>
@return string
'''
result = ''
config_file = "{}/nginx/node_{}".format(self._vhost_path, project_name)
if not os.path.exists(config_file):
return result
config_body = public.readFile(config_file)
if not config_body:
return result
if config_body.find('ssl_certificate') == -1:
return result
ssl_body = re.search("#SSL-START(.|\n)+#SSL-END", config_body)
if not ssl_body: return result
result = ssl_body.group()
return result
def exists_nginx_ssl(self, project_name):
'''
@name 判断项目是否配置Nginx SSL配置
@author hwliang<2021-08-09>
@param project_name: string<项目名称>
@return tuple
'''
config_file = "{}/nginx/node_{}.conf".format(public.get_vhost_path(), project_name)
if not os.path.exists(config_file):
return False, False
config_body = public.readFile(config_file)
if not config_body:
return False, False
is_ssl, is_force_ssl = False, False
if config_body.find('ssl_certificate') != -1:
is_ssl = True
if config_body.find('HTTP_TO_HTTPS_START') != -1:
is_force_ssl = True
return is_ssl, is_force_ssl
def exists_apache_ssl(self, project_name):
'''
@name 判断项目是否配置Apache SSL配置
@author hwliang<2021-08-09>
@param project_name: string<项目名称>
@return bool
'''
config_file = "{}/apache/node_{}.conf".format(public.get_vhost_path(), project_name)
if not os.path.exists(config_file):
return False, False
config_body = public.readFile(config_file)
if not config_body:
return False, False
is_ssl, is_force_ssl = False, False
if config_body.find('SSLCertificateFile') != -1:
is_ssl = True
if config_body.find('HTTP_TO_HTTPS_START') != -1:
is_force_ssl = True
return is_ssl, is_force_ssl
def set_apache_config(self, project_find):
'''
@name 设置Apache配置
@author hwliang<2021-08-09>
@param project_find: dict<项目信息>
@return bool
'''
project_name = project_find['name']
# 处理域名和端口
ports = []
domains = []
for d in project_find['project_config']['domains']:
domain_tmp = d.split(':')
if len(domain_tmp) == 1: domain_tmp.append(80)
if not int(domain_tmp[1]) in ports:
ports.append(int(domain_tmp[1]))
if not domain_tmp[0] in domains:
domains.append(domain_tmp[0])
config_file = "{}/apache/node_{}.conf".format(self._vhost_path, project_name)
template_file = "{}/template/apache/node_http.conf".format(self._vhost_path)
config_body = public.readFile(template_file)
apache_config_body = ''
# 旧的配置文件是否配置SSL
is_ssl, is_force_ssl = self.exists_apache_ssl(project_name)
if is_ssl:
if not 443 in ports: ports.append(443)
from panelSite import panelSite
s = panelSite()
# 根据端口列表生成配置
for p in ports:
# 生成SSL配置
ssl_config = ''
if p == 443 and is_ssl:
ssl_key_file = "{vhost_path}/cert/{project_name}/privkey.pem".format(project_name=project_name,
vhost_path=public.get_vhost_path())
if not os.path.exists(ssl_key_file): continue # 不存在证书文件则跳过
ssl_config = '''#SSL
SSLEngine On
SSLCertificateFile {vhost_path}/cert/{project_name}/fullchain.pem
SSLCertificateKeyFile {vhost_path}/cert/{project_name}/privkey.pem
SSLCipherSuite EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5
SSLProtocol All -SSLv2 -SSLv3 -TLSv1
SSLHonorCipherOrder On'''.format(project_name=project_name, vhost_path=public.get_vhost_path())
else:
if is_force_ssl:
ssl_config = '''#HTTP_TO_HTTPS_START
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{SERVER_PORT} !^443$
RewriteRule (.*) https://%{SERVER_NAME}$1 [L,R=301]
</IfModule>
#HTTP_TO_HTTPS_END'''
# 多服务下切换端口
if public.get_multi_webservice_status() and str(p) in ['80', '443']:
if str(p) == '80':
p = 8288
else:
p = 8290
# 生成vhost主体配置
apache_config_body += config_body.format(
site_path=project_find['path'],
server_name='{}.{}'.format(p, project_name),
domains=' '.join(domains),
log_path=public.get_logs_path(),
server_admin='admin@{}'.format(project_name),
url='http://127.0.0.1:{}'.format(project_find['project_config']['port']),
port=p,
ssl_config=ssl_config,
project_name=project_name
)
apache_config_body += "\n"
# 添加端口到主配置文件
if not p in [80] and not public.get_multi_webservice_status():
s.apacheAddPort(p)
# 写.htaccess
rewrite_file = "{}/.htaccess".format(project_find['path'])
if not os.path.exists(rewrite_file): public.writeFile(rewrite_file,
'# Please fill in the URLrewrite rules or custom Apache config here\n')
# 写配置文件
public.writeFile(config_file, apache_config_body)
return True
def set_ols_config(self, project_find):
project_name = project_find['name']
project_port = project_find['project_config']['port']
domains = project_find['project_config']['domains']
# 路径与基础检查
vhost_dir = public.get_panel_path() + '/vhost/openlitespeed/'
detail_dir = vhost_dir + 'detail/'
listen_dir = vhost_dir + 'listen/'
for path in [detail_dir, listen_dir]:
if not os.path.exists(path): os.makedirs(path)
# 生成虚拟主机
vhost_main_file = vhost_dir + project_name + '.conf'
vhost_main_body = """virtualhost {name} {{
vhRoot {path}
configFile {detail_dir}{name}.conf
allowSymbolLink 1
enableScript 1
restrained 1
setUIDMode 0
}}""".format(name=project_name, path=project_find['path'], detail_dir=detail_dir)
public.writeFile(vhost_main_file, vhost_main_body)
# 生成Host
detail_file = detail_dir + project_name + '.conf'
conf_body = '''docRoot $VH_ROOT
vhDomain $VH_NAME
adminEmails admin@{name}
enableGzip 1
errorlog /www/wwwlogs/{name}_ols.error_log {{
useServer 0
logLevel ERROR
rollingSize 10M
}}
accesslog /www/wwwlogs/{name}_ols.access_log {{
useServer 0
logFormat '%{{X-Forwarded-For}}i %h %l %u %t "%r" %>s %b "%{{Referer}}i" "%{{User-Agent}}i"'
logHeaders 5
rollingSize 10M
keepDays 10
}}
extprocessor {name}_proxy {{
type proxy
address 127.0.0.1:{node_port}
maxConns 100
pcKeepAliveTimeout 60
initTimeout 60
retryTimeout 0
respBuffer 0
}}
context / {{
type proxy
handler {name}_proxy
addDefaultCharset off
}}
rewrite {{
enable 1
autoLoadHtaccess 1
}}
'''.format(name=project_name, node_port=project_port)
public.writeFile(detail_file, conf_body)
# 处理监听器映射
# 提取端口和对应的域名
port_map = {}
for d in domains:
tmp = d.split(':')
p = tmp[1] if len(tmp) > 1 else "80"
if p not in port_map: port_map[p] = []
port_map[p].append(tmp[0])
# 检查 SSL 状态 (参考原 Apache 逻辑)
is_ssl, _ = self.exists_apache_ssl(project_name)
if is_ssl and "443" not in port_map:
# 如果有证书但域名列表没写443,默认把所有域名映射到443
port_map["443"] = [d.split(':')[0] for d in domains]
for p, ds in port_map.items():
listen_file = "{}{}.conf".format(listen_dir, p)
if not os.path.exists(listen_file): continue
content = public.readFile(listen_file)
# 构造映射行:map 项目名 域名1,域名2
domain_str = ",".join(ds)
map_line = "\tmap\t{}\t{}\n".format(project_name, domain_str)
# 如果该项目已在 map 中,先正则替换或跳过,这里采用简单判断
if "map\t{}\t".format(project_name) in content:
import re
content = re.sub(r"map\t{}\t.*?\n".format(project_name), map_line, content)
else:
# 插入到最后一个花括号之前
last_brace_index = content.rfind('}')
if last_brace_index != -1:
content = content[:last_brace_index] + map_line + content[last_brace_index:]
public.writeFile(listen_file, content)
return public.return_message(0, 0, True)
def unbind_extranet(self, get):
'''
@name 解绑外网
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_name = get.project_name.strip()
self.clear_config(project_name)
public.serviceReload()
project_find = self.get_project_find(project_name)
project_find['project_config']['bind_extranet'] = 0
public.M('sites').where("id=?", (project_find['id'],)).setField('project_config',
json.dumps(project_find['project_config']))
public.WriteLog(self._log_name, 'Node project {}, disable the mapping'.format(project_name))
return_message = public.return_data(True, 'Disabled successfully')
del return_message['status']
return public.return_message(0, 0, return_message)
def get_project_pids(self, get=None, pid=None, without_request=False):
'''
@name 获取项目进程pid列表
@author hwliang<2021-08-10>
@param pid: string<项目pid>
@return list
'''
if get: pid = int(get.pid)
if not self._pids: self._pids = psutil.pids()
project_pids = []
for i in self._pids:
try:
p = psutil.Process(i)
if p.status() == "zombie":
continue
if p.ppid() == pid:
if i in project_pids:
continue
project_pids.append(i)
except:
continue
other_pids = []
for i in project_pids:
other_pids += self.get_project_pids(pid=i)['message']
if os.path.exists('/proc/{}'.format(pid)):
project_pids.append(pid)
all_pids = list(set(project_pids + other_pids))
if not all_pids:
all_pids = self.get_other_pids(pid)['message']
# public.print_log("all_pids -- {}".format(all_pids))
def convert_to_int(item):
try:
return int(item)
except ValueError:
return item
# 将所有元素转换为整数后进行排序
sorted_pids = sorted(all_pids, key=convert_to_int)
if without_request:
return sorted_pids
return public.return_message(0, 0, sorted_pids)
def get_other_pids(self, pid):
'''
@name 获取其他进程pid列表
@author hwliang<2021-08-10>
@param pid: string<项目pid>
@return list
'''
project_name = None
for pid_name in os.listdir(self._node_pid_path):
pid_file = '{}/{}'.format(self._node_pid_path, pid_name)
# s_pid = int(public.readFile(pid_file))
data = public.readFile(pid_file)
if isinstance(data, str) and data:
data = data.strip()
if not data.isdigit():
return public.return_message(0, 0, [])
s_pid = int(data)
else:
return public.return_message(0, 0, [])
if pid == s_pid:
project_name = pid_name[:-4]
break
project_find = self.get_project_find(project_name)
if not project_find: return public.return_message(0, 0, [])
if not self._pids: self._pids = psutil.pids()
all_pids = []
for i in self._pids:
try:
p = psutil.Process(i)
if p.cwd() == project_find['path']:
pname = p.name()
if pname in ['node', 'npm', 'pm2', 'yarn'] or pname.find('node ') == 0:
cmdline = ','.join(p.cmdline())
if cmdline.find('God Daemon') != -1: continue
env_list = p.environ()
if 'name' in env_list:
if not env_list['name'] == project_name: continue
if 'NODE_PROJECT_NAME' in env_list:
if not env_list['NODE_PROJECT_NAME'] == project_name: continue
all_pids.append(i)
except:
continue
return public.return_message(0, 0, all_pids)
def get_project_state_by_cwd(self, project_name):
'''
@name 通过cwd获取项目状态
@author hwliang<2022-01-17>
@param project_name<string> 项目名称
@return bool or list
'''
project_find = self.get_project_find(project_name)
self._pids = psutil.pids()
if not project_find: return []
all_pids = []
for i in self._pids:
try:
p = psutil.Process(i)
if p.cwd() == project_find['path']:
pname = p.name()
if pname in ['node', 'npm', 'pm2', 'yarn'] or pname.find('node ') == 0:
cmdline = ','.join(p.cmdline())
if cmdline.find('God Daemon') != -1: continue
env_list = p.environ()
if 'name' in env_list:
if not env_list['name'] == project_name: continue
if 'NODE_PROJECT_NAME' in env_list:
if not env_list['NODE_PROJECT_NAME'] == project_name: continue
all_pids.append(i)
except:
continue
if all_pids:
pid_file = "{}/{}.pid".format(self._node_pid_path, project_name)
public.writeFile(pid_file, str(all_pids[0]))
return all_pids
return False
def kill_pids(self, get=None, pids=None):
'''
@name 结束进程列表
@author hwliang<2021-08-10>
@param pids: string<进程pid列表>
@return dict
'''
if get: pids = get.pids
if not pids:
return_message = public.return_data(True, 'No process')
del return_message['status']
return public.return_message(0, 0, return_message)
pids = sorted(pids, reverse=True)
for i in pids:
try:
p = psutil.Process(i)
p.kill()
except:
pass
return_message = public.return_data(True, 'The process has all ended')
del return_message['status']
return public.return_message(0, 0, return_message)
def start_project(self, get):
'''
@name 启动项目
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
pid_file = "{}/{}.pid".format(self._node_pid_path, get.project_name)
if os.path.exists(pid_file):
self.stop_project(get)
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('Item does not exist'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not os.path.exists(project_find['path']):
error_msg = 'Startup failed, Nodejs project {}, running directory {} does not exist!'.format(
get.project_name, project_find['path'])
public.WriteLog(self._log_name, error_msg)
return_message = public.return_error(error_msg)
del return_message['status']
return public.return_message(-1, 0, return_message)
# 是否安装依赖模块?
package_file = "{}/package.json".format(project_find['path'])
package_info = {}
if os.path.exists(package_file):
node_modules_path = "{}/node_modules".format(project_find['path'])
if not os.path.exists(node_modules_path):
return_message = public.return_error(public.lang(
'Please go to the [Module] and click [One-key install] to install the module dependencies!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
package_info = json.loads(public.readFile(package_file))
if not package_info: package_info['scripts'] = {}
if 'scripts' not in package_info: package_info['scripts'] = {}
try:
scripts_keys = package_info['scripts'].keys()
except:
scripts_keys = []
# 前置准备
nodejs_version = project_find['project_config']['nodejs_version']
node_bin = self.get_node_bin(nodejs_version)
pkg_manager = self._normalize_package_manager(project_find['project_config'].get('pkg_manager', project_find[
'project_config'].get('package_manager', 'npm')))
project_script = project_find['project_config']['project_script'].strip().replace(' ', ' ')
if project_script[:3] == 'pm2': # PM2启动方式处理
project_script = project_script.replace('pm2 ', 'pm2 -u {} -n {} '.format(
project_find['project_config']['run_user'], get.project_name))
project_find['project_config']['run_user'] = 'root'
log_file = "{}/{}.log".format(self._node_logs_path, get.project_name)
if not project_script:
return_message = public.return_error(public.lang('No startup script configured'))
del return_message['status']
return public.return_message(-1, 0, return_message)
last_env = self.get_last_env(nodejs_version, project_find['path'])
project_port = project_find['project_config'].get('port', '')
port_export = 'export PORT="{}"\n'.format(project_port) if project_port else ''
# 生成启动脚本
if os.path.exists(project_script):
start_cmd = '''{last_env}
export NODE_PROJECT_NAME="{project_name}"
{port_export}cd {project_cwd}
nohup {node_bin} {project_script} >> {log_file} 2>&1 &
echo $! > {pid_file}
'''.format(
project_cwd=project_find['path'],
node_bin=node_bin,
project_script=project_script,
log_file=log_file,
pid_file=pid_file,
last_env=last_env,
project_name=get.project_name,
port_export=port_export
)
elif project_script in scripts_keys:
package_run_cmd = self._get_package_manager_run_cmd(nodejs_version, pkg_manager, project_script)
if not package_run_cmd:
return_message = public.return_error(
'The specified package manager is not available: {}'.format(pkg_manager))
del return_message['status']
return public.return_message(-1, 0, return_message)
start_cmd = '''{last_env}
export NODE_PROJECT_NAME="{project_name}"
{port_export}cd {project_cwd}
nohup {package_run_cmd} >> {log_file} 2>&1 &
echo $! > {pid_file}
'''.format(
project_cwd=project_find['path'],
package_run_cmd=package_run_cmd,
pid_file=pid_file,
log_file=log_file,
last_env=last_env,
project_name=get.project_name,
port_export=port_export
)
else:
start_cmd = '''{last_env}
export NODE_PROJECT_NAME="{project_name}"
{port_export}cd {project_cwd}
nohup {project_script} >> {log_file} 2>&1 &
echo $! > {pid_file}
'''.format(
project_cwd=project_find['path'],
project_script=project_script,
pid_file=pid_file,
log_file=log_file,
last_env=last_env,
project_name=get.project_name,
port_export=port_export
)
script_file = "{}/{}.sh".format(self._node_run_scripts, get.project_name)
# 写入启动脚本
public.writeFile(script_file, start_cmd)
if os.path.exists(pid_file): os.remove(pid_file)
# 处理前置权限
public.ExecShell("chown -R {user}:{user} {project_cwd}".format(user=project_find['project_config']['run_user'],
project_cwd=project_find['path']))
public.ExecShell("chown -R www:www {}/vhost".format(self._nodejs_path))
public.ExecShell("chmod 755 {} {} {}".format(self._nodejs_path, public.get_setup_path(), '/www'))
public.set_own(script_file, project_find['project_config']['run_user'],
project_find['project_config']['run_user'])
public.set_mode(script_file, 755)
p = public.ExecShell("bash {}".format(script_file), user=project_find['project_config']['run_user'])
time.sleep(1)
n = 0
while n < 5:
if self.get_project_state_by_cwd(get.project_name): break
n += 1
if not os.path.exists(pid_file):
p = '\n'.join(p)
public.writeFile(log_file, p, "a+")
if p.find('[Errno 0]') != -1:
if os.path.exists('{}/bt_security'.format(public.get_plugin_path())):
return_message = public.return_error(
'The start command was intercepted by [Fort Tower Defense Privilege], please turn off {} user protection'.format(
project_find['project_config']['run_user']))
del return_message['status']
return public.return_message(-1, 0, return_message)
return_message = public.return_error(public.lang(
'The startup command was intercepted by unknown security software, please check the installation software log'))
del return_message['status']
return public.return_message(-1, 0, return_message)
return_message = public.return_error('failed to activate<pre>{}</pre>'.format(p))
del return_message['status']
return public.return_message(-1, 0, return_message)
# 获取PID
try:
pid = int(public.readFile(pid_file))
except:
return public.return_error('Startup failed <br>{}'.format(public.GetNumLines(log_file, 20)))
pids = self.get_project_pids(pid=pid, without_request=True)
if not pids:
if os.path.exists(pid_file): os.remove(pid_file)
return_message = public.return_error('failed to activate<br>{}'.format(public.GetNumLines(log_file, 20)))
del return_message['status']
return public.return_message(-1, 0, return_message)
return_message = public.return_data(True, 'Successfully started', pids)
del return_message['status']
return public.return_message(0, 0, return_message)
def stop_project(self, get):
'''
@name 停止项目
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
is_power_on: boolean<<UNK>> #标记是否自动关闭自动重启
}
@return dict
'''
project_find = self.get_project_find(get.project_name)
if not project_find:
return_message = public.return_error(public.lang('Project does not exist'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_script = project_find['project_config']['project_script'].strip().replace(' ', ' ')
pid_file = "{}/{}.pid".format(self._node_pid_path, get.project_name)
if project_script.find('pm2 start') != -1: # 处理PM2启动的项目
nodejs_version = project_find['project_config']['nodejs_version']
last_env = self.get_last_env(nodejs_version, project_find['path'])
project_script = project_script.replace('pm2 start', 'pm2 stop')
public.ExecShell('''{}
cd {}
{}'''.format(last_env, project_find['path'], project_script))
else:
pid_file = "{}/{}.pid".format(self._node_pid_path, get.project_name)
if not os.path.exists(pid_file):
return_message = public.return_error(public.lang('Project did not start'))
del return_message['status']
return public.return_message(-1, 0, return_message)
data = public.readFile(pid_file)
if isinstance(data, str) and data:
pid = int(data)
pids = self.get_project_pids(pid=pid, without_request=True)
else:
return_message = public.return_error(public.lang('Project did not start'))
del return_message['status']
return public.return_message(-1, 0, return_message)
if not pids:
return_message = public.return_error(public.lang('Project did not start'))
del return_message['status']
return public.return_message(-1, 0, return_message)
self.kill_pids(pids=pids)
if os.path.exists(pid_file): os.remove(pid_file)
time.sleep(0.5)
pids = self.get_project_state_by_cwd(get.project_name)
if pids: self.kill_pids(pids=pids)
# 停用项目自启
if get.get('is_power_on') in ['True', 'true', 1, '1']:
data = project_find['project_config']
data = {
"project_cwd": data['project_cwd'],
"project_name": data['project_name'],
"project_script": data['project_script'],
"port": str(data['port']),
"run_user": data['run_user'],
"nodejs_version": data['nodejs_version'],
"project_ps": project_find['ps'],
"is_power_on": 0
}
self.modify_project(public.to_dict_obj(data))
return_message = public.return_data(True, 'Stopped successfully')
del return_message['status']
return public.return_message(0, 0, return_message)
def restart_project(self, get):
'''
@name 重启项目
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
res = self.stop_project(get)
if res['status'] == -1: return res
res = self.start_project(get)
if res['status'] == -1: return res
return_message = public.return_data(True, 'Successful restart')
del return_message['status']
return public.return_message(0, 0, return_message)
# xss 防御
def xsssec(self, text):
return text.replace('<', '&lt;').replace('>', '&gt;')
def get_project_log(self, get):
'''
@name 获取项目日志
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_find = self.get_project_find(get.project_name)
if not project_find: return public.returnMsg(False, '项目不存在')
if "log_path" not in project_find['project_config']:
log_file = "{}/{}.log".format(self._node_logs, project_find["name"])
else:
log_file = "{}/{}.log".format(project_find['project_config']["log_path"], project_find["name"])
if not os.path.exists(log_file):
return public.return_message(-1, 0, public.lang("The log file does not exist."))
return public.return_message(0, 0, self.xsssec(public.GetNumLines(log_file, 200)))
def get_project_load_info(self, get=None, project_name=None):
'''
@name 获取项目负载信息
@author hwliang<2021-08-12>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
if get: project_name = get.project_name.strip()
load_info = {}
pid_file = "{}/{}.pid".format(self._node_pid_path, project_name)
if not os.path.exists(pid_file):
return public.return_message(0, 0, load_info)
data = public.readFile(pid_file)
if isinstance(data, str) and data:
pid = int(data)
pids = self.get_project_pids(pid=pid)['message']
else:
return public.return_message(0, 0, load_info)
if not pids:
return public.return_message(0, 0, load_info)
for i in pids:
process_info = self.get_process_info_by_pid(i)
if process_info: load_info[i] = process_info
return public.return_message(0, 0, load_info)
def object_to_dict(self, obj):
'''
@name 将对象转换为字典
@author hwliang<2021-08-09>
@param obj<object>
@return dict
'''
result = {}
for name in dir(obj):
value = getattr(obj, name)
if not name.startswith('__') and not callable(value) and not name.startswith('_'): result[name] = value
return result
def list_to_dict(self, data):
'''
@name 将列表转换为字典
@author hwliang<2021-08-09>
@param data<list>
@return dict
'''
result = []
for s in data:
result.append(self.object_to_dict(s))
return result
def get_connects(self, pid):
'''
@name 获取进程连接信息
@author hwliang<2021-08-09>
@param pid<int>
@return dict
'''
connects = 0
try:
if pid == 1: return connects
tp = '/proc/' + str(pid) + '/fd/'
if not os.path.exists(tp): return connects
for d in os.listdir(tp):
fname = tp + d
if os.path.islink(fname):
l = os.readlink(fname)
if l.find('socket:') != -1: connects += 1
except:
pass
return connects
def format_connections(self, connects):
'''
@name 获取进程网络连接信息
@author hwliang<2021-08-09>
@param connects<pconn>
@return list
'''
result = []
for i in connects:
raddr = i.raddr
if not i.raddr:
raddr = ('', 0)
laddr = i.laddr
if not i.laddr:
laddr = ('', 0)
result.append({
"fd": i.fd,
"family": i.family,
"local_addr": laddr[0],
"local_port": laddr[1],
"client_addr": raddr[0],
"client_rport": raddr[1],
"status": i.status
})
return result
def get_process_info_by_pid(self, pid):
'''
@name 获取进程信息
@author hwliang<2021-08-12>
@param pid: int<进程id>
@return dict
'''
process_info = {}
try:
if not os.path.exists('/proc/{}'.format(pid)): return process_info
p = psutil.Process(pid)
status_ps = {'sleeping': 'Sleeping', 'running': 'Running'}
with p.oneshot():
p_mem = p.memory_full_info()
if p_mem.uss + p_mem.rss + p_mem.pss + p_mem.data == 0: return process_info
p_state = p.status()
if p_state in status_ps: p_state = status_ps[p_state]
# process_info['exe'] = p.exe()
process_info['name'] = p.name()
process_info['pid'] = pid
process_info['ppid'] = p.ppid()
process_info['create_time'] = int(p.create_time())
process_info['status'] = p_state
process_info['user'] = p.username()
process_info['memory_used'] = p_mem.uss
process_info['cpu_percent'] = self.get_cpu_precent(p)
process_info['io_write_bytes'], process_info['io_read_bytes'] = self.get_io_speed(p)
process_info['connections'] = self.format_connections(p.connections())
process_info['connects'] = self.get_connects(pid)
process_info['open_files'] = self.list_to_dict(p.open_files())
process_info['threads'] = p.num_threads()
process_info['exe'] = ' '.join(p.cmdline())
return process_info
except:
return process_info
def get_io_speed(self, p):
'''
@name 获取磁盘IO速度
@author hwliang<2021-08-12>
@param p: Process<进程对像>
@return list
'''
skey = "io_speed_{}".format(p.pid)
old_pio = cache.get(skey)
if not hasattr(p, 'io_counters'): return 0, 0
pio = p.io_counters()
if not old_pio:
cache.set(skey, [pio, time.time()], 3600)
# time.sleep(0.1)
old_pio = cache.get(skey)
pio = p.io_counters()
old_write_bytes = old_pio[0].write_bytes
old_read_bytes = old_pio[0].read_bytes
old_time = old_pio[1]
new_time = time.time()
write_bytes = pio.write_bytes
read_bytes = pio.read_bytes
cache.set(skey, [pio, new_time], 3600)
write_speed = int((write_bytes - old_write_bytes) / (new_time - old_time))
read_speed = int((read_bytes - old_read_bytes) / (new_time - old_time))
return write_speed, read_speed
def get_cpu_precent(self, p):
'''
@name 获取进程cpu使用率
@author hwliang<2021-08-09>
@param p: Process<进程对像>
@return dict
'''
skey = "cpu_pre_{}".format(p.pid)
old_cpu_times = cache.get(skey)
process_cpu_time = self.get_process_cpu_time(p.cpu_times())
if not old_cpu_times:
cache.set(skey, [process_cpu_time, time.time()], 3600)
# time.sleep(0.1)
old_cpu_times = cache.get(skey)
process_cpu_time = self.get_process_cpu_time(p.cpu_times())
old_process_cpu_time = old_cpu_times[0]
old_time = old_cpu_times[1]
new_time = time.time()
cache.set(skey, [process_cpu_time, new_time], 3600)
percent = round(100.00 * (process_cpu_time - old_process_cpu_time) / (new_time - old_time) / psutil.cpu_count(),
2)
return percent
def get_process_cpu_time(self, cpu_times):
cpu_time = 0.00
for s in cpu_times: cpu_time += s
return cpu_time
def get_project_run_state(self, get=None, project_name=None):
'''
@name 获取项目运行状态
@author hwliang<2021-08-12>
@param get<dict_obj>{
project_name: string<项目名称>
}
@param project_name<string> 项目名称
@return bool
'''
if get: project_name = get.project_name.strip()
pid_file = "{}/{}.pid".format(self._node_pid_path, project_name)
if not os.path.exists(pid_file):
return public.return_message(0, 0, False)
data = public.readFile(pid_file)
if isinstance(data, str) and data:
if not data.isdigit():
return public.return_message(0, 0, False)
pid = int(data)
pids = self.get_project_pids(pid=pid, without_request=True)
else:
return public.return_message(0, 0, self.get_project_state_by_cwd(project_name))
if not pids:
return self.get_project_state_by_cwd(project_name)
return public.return_message(0, 0, True)
def _get_project_run_state(self, get=None, project_name=None):
'''
@name 获取项目运行状态
@author hwliang<2021-08-12>
@param get<dict_obj>{
project_name: string<项目名称>
}
@param project_name<string> 项目名称
@return bool
'''
if get: project_name = get.project_name.strip()
pid_file = "{}/{}.pid".format(self._node_pid_path, project_name)
if not os.path.exists(pid_file):
return False
data = public.readFile(pid_file)
if isinstance(data, str) and data:
pid = int(data)
pids = self.get_project_pids(pid=pid)['message']
else:
return self.get_project_state_by_cwd(project_name)
if not pids:
return self.get_project_state_by_cwd(project_name)
return True
def get_project_find(self, project_name):
'''
@name 获取指定项目配置
@author hwliang<2021-08-09>
@param project_name<string> 项目名称
@return dict
'''
project_info = public.M('sites').where('project_type=? AND name=?', ('Node', project_name)).find()
if not project_info: return False
project_info['project_config'] = json.loads(project_info['project_config'])
return project_info
def get_project_info(self, get):
'''
@name 获取指定项目信息
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
# 校验参数
try:
get.validate([
Param('project_name').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_info = public.M('sites').where('project_type=? AND name=?', ('Node', get.project_name)).find()
if not project_info:
return_message = public.return_error(public.lang('The specified item does not exist!'))
del return_message['status']
return public.return_message(-1, 0, return_message)
project_info = self.get_project_stat(project_info)
return public.return_message(0, 0, project_info)
def get_project_stat(self, project_info):
'''
@name 获取项目状态信息
@author hwliang<2021-08-09>
@param project_info<dict> 项目信息
@return list
'''
project_info['project_config'] = json.loads(project_info['project_config'])
project_info['load_info'] = {}
if ("pm2_name" in project_info['project_config'] and
"project_type" in project_info['project_config'] and
project_info['project_config']["project_type"] == "pm2") or "pm2" in project_info['project_config'][
'project_script']:
from mod.project.nodejs import pm2Mod
pm2_list = pm2Mod.main().get_jlist()
project_info['run'] = False
pm2_name = project_info['project_config'].get("pm2_name", project_info['name'])
pm2_pids = []
project_info['project_config']['watch'] = True if project_info['project_config'].get('watch', False) in [
'True', 'true', True] else False
for pm2_info in pm2_list:
if pm2_info.get("name") in ("pm2-sysmonit", "pm2-logrotate"):
continue
env_projectname = ""
if "pm2_env" in pm2_info and "NODE_PROJECT_NAME" in pm2_info["pm2_env"]:
env_projectname = pm2_info["pm2_env"]["NODE_PROJECT_NAME"]
if pm2_name != pm2_info.get("name") and pm2_name != env_projectname:
continue
project_info['run'] = pm2_info.get("pm2_env").get("status") == "online"
if project_info['run']:
pm2_pids.append(pm2_info.get("pid"))
break
project_info['load_info'] = self.get_pm2_load_info_new(pm2_pids)
else:
project_info['run'] = self._get_project_run_state(project_name=project_info['name'])
if project_info['run']:
project_info['load_info'] = self.get_project_load_info(project_name=project_info['name'])['message']
project_info['ssl'] = self.get_ssl_end_date(project_name=project_info['name'])
project_info['listen'] = []
project_info['listen_ok'] = True
if project_info['load_info']:
for pid in project_info['load_info'].keys():
if not pid: continue
if not 'connections' in project_info['load_info'][pid]:
project_info['load_info'][pid]['connections'] = []
for conn in project_info['load_info'][pid]['connections']:
if not conn['status'] == 'LISTEN': continue
if not conn['local_port'] in project_info['listen']:
project_info['listen'].append(conn['local_port'])
if project_info['listen']:
if project_info['project_config']['port'] is None:
# 端口回填
project_info['project_config']['port'] = project_info['listen'][0]
try:
update_data = {
'project_config': json.dumps(project_info['project_config'])
}
public.M('sites').where('name=?', (project_info['name'],)).update(update_data)
except:
pass
project_info['listen_ok'] = project_info['project_config']['port'] in project_info['listen']
return project_info
def get_pm2_load_info_new(self, pids: list):
'''
@name 获取项目负载信息
@author hwliang<2021-08-12>
@param get<dict_obj>{
project_name: string<项目名称>
}
@return dict
'''
load_info = {}
for i in pids:
process_info = self.get_process_info_by_pid(i)
if process_info: load_info[i] = process_info
return load_info
# 获取项目状态,支持PM2/node
def get_project_run(self, project_name, project_type):
try:
if project_type == 'node':
return self._get_project_run_state(project_name=project_name)
else:
from mod.project.nodejs import pm2Mod
pm2_list = pm2Mod.main().get_jlist()
pm2_name = project_name
for pm2_info in pm2_list:
if pm2_info.get("name") in ("pm2-sysmonit", "pm2-logrotate"):
continue
env_projectname = ""
if "pm2_env" in pm2_info and "NODE_PROJECT_NAME" in pm2_info["pm2_env"]:
env_projectname = pm2_info["pm2_env"]["NODE_PROJECT_NAME"]
if pm2_name != pm2_info.get("name") and pm2_name != env_projectname:
continue
return pm2_info.get("pm2_env").get("status") == "online"
return False
except Exception as e:
return False
def get_project_state(self, project_name):
'''
@name 获取项目状态
@author hwliang<2021-08-09>
@param project_name: string<项目名称>
@return dict
'''
project_info = public.M('sites').where('project_type=? AND name=?', ('Node', project_name)).find()
if not project_info: return False
return project_info['status']
def get_project_listen(self, project_name):
'''
@name 获取项目监听端口
@author hwliang<2021-08-09>
@param project_name: string<项目名称>
@return dict
'''
project_config = json.loads(public.M('sites').where('name=?', project_name).getField('project_config'))
if 'listen_port' in project_config:
return project_config['listen_port']
return False
def set_project_listen(self, get):
'''
@name 设置项目监听端口(请设置与实际端口相符的,仅在自动获取不正确时使用)
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
port: int<端口>
}
@return dict
'''
project_config = json.loads(public.M('sites').where('name=?', get.project_name).getField('project_config'))
project_config['listen_port'] = get.port
public.M('sites').where('name=?', get.project_name).save('project_config', json.dumps(project_config))
public.WriteLog(self._log_name,
'Modify the port of the project [' + get.project_name + '] to [' + get.port + ']')
return_message = public.return_data(True, 'Set successfully')
del return_message['status']
return public.return_message(0, 0, return_message)
def set_project_nodejs_version(self, get):
'''
@name 设置nodejs版本
@author hwliang<2021-08-09>
@param get<dict_obj>{
project_name: string<项目名称>
nodejs_version: string<nodejs版本>
}
@return dict
'''
project_config = json.loads(public.M('sites').where('name=?', get.project_name).getField('project_config'))
project_config['nodejs_version'] = get.nodejs_version
public.M('sites').where('name=?', get.project_name).save('project_config', json.dumps(project_config))
public.WriteLog(self._log_name,
'Modify the nodejs version of the project [' + get.project_name + '] to [' + get.nodejs_version + ']')
return_message = public.return_data(True, 'Set successfully')
del return_message['status']
return public.return_message(0, 0, return_message)
def get_project_nodejs_version(self, project_name):
'''
@name 获取nodejs版本
@author hwliang<2021-08-09>
@param project_name: string<项目名称>
@return string
'''
project_config = json.loads(public.M('sites').where('name=?', project_name).getField('project_config'))
if 'nodejs_version' in project_config: return project_config['nodejs_version']
return False
def check_port_is_used(self, port, sock=False):
'''
@name 检查端口是否被占用
@author hwliang<2021-08-09>
@param port: int<端口>
@return bool
'''
if not isinstance(port, int): port = int(port)
if port == 0: return False
project_list = public.M('sites').where('status=? AND project_type=?', (1, 'Node')).field(
'name,path,project_config').select()
for project_find in project_list:
project_config = json.loads(project_find['project_config'])
if not 'port' in project_config: continue
try:
if int(project_config['port']) == port: return True
except:
pass
if sock: return False
return public.check_tcp('127.0.0.1', port)
def get_project_run_state_byaotu(self, project_name):
'''
@name 获取项目运行状态
@author hwliang<2021-08-09>
@param project_name: string<项目名称>
@return dict
'''
pid_file = "{}/{}.pid".format(self._node_pid_path, project_name)
if not os.path.exists(pid_file): return False
pid = public.readFile(pid_file)
pids = self.get_project_pids(pid=pid, without_request=True)
if not pids: return False
return True
# 废弃,重定向于script/project_daemon.py
def auto_run(self):
'''
@name 自动启动所有项目
@author hwliang<2021-08-09>
@return bool
'''
project_list = public.M('sites').where('project_type=?', ('Node',)).field('name,path,project_config').select()
get = public.dict_obj()
success_count = 0
error_count = 0
for project_find in project_list:
try:
project_config = json.loads(project_find['project_config'])
if project_config['is_power_on'] in [0, False, '0', None]: continue
project_name = project_find['name']
project_state = self._get_project_run_state(project_name=project_name)
if not project_state:
get.project_name = project_name
result = self.start_project(get)['message']
if result['status'] == -1:
error_count += 1
error_msg = 'Automatically start Nodej project [' + project_name + '] failed!'
public.WriteLog(self._log_name, error_msg)
public.print_log(error_msg + ", " + result['error_msg'], 'ERROR')
else:
success_count += 1
success_msg = 'Automatically start the Nodej project [' + project_name + '] successfully!'
public.WriteLog(self._log_name, success_msg)
public.print_log(success_msg, 'INFO')
except:
error_count += 1
public.print_log(public.get_error_info(), 'ERROR')
if (success_count + error_count) < 1: return False
dene_msg = 'A total of {} Nodejs projects need to be started, {} successfully and {} failed'.format(
success_count + error_count, success_count, error_count)
public.WriteLog(self._log_name, dene_msg)
public.print_log(dene_msg, 'INFO')
return True