Compare commits

..

13 Commits
1.1.3 ... 1.6

Author SHA1 Message Date
flucont
53a878def3 update 2022-12-24 10:44:30 +08:00
flucont
b6350dbed8 update wiki 2022-12-22 09:52:19 +08:00
flucont
2c9f84c121 update 2022-12-17 17:25:27 +08:00
flucont
ab81af3b94 update 2022-12-03 14:14:52 +08:00
flucont
98f9ca0e5b update 2022-11-25 18:28:44 +08:00
flucont
50cfdaa06f update 2022-11-04 20:49:46 +08:00
flucont
ae4ed95573 update 2022-11-04 20:46:46 +08:00
flucont
e98206ce0c update 2022-09-24 11:37:51 +08:00
flucont
8946b4fd11 update 2022-09-24 11:27:12 +08:00
flucont
5bd1670955 update 2022-09-09 20:11:02 +08:00
flucont
1fc393c8e9 update 2022-08-22 17:55:19 +08:00
flucont
6a66f3db07 update 2022-08-15 18:39:40 +08:00
flucont
605fe7a687 update 2022-08-05 10:55:43 +08:00
47 changed files with 1680 additions and 407 deletions

View File

@@ -5,13 +5,13 @@ DEFAULT_TIMEZONE = Asia/Shanghai
[DATABASE]
TYPE = mysql
HOSTNAME = localhost
DATABASE = btcloud
USERNAME = btcloud
PASSWORD = 123456
HOSTPORT = 3306
HOSTNAME = {dbhost}
DATABASE = {dbname}
USERNAME = {dbuser}
PASSWORD = {dbpwd}
HOSTPORT = {dbport}
CHARSET = utf8mb4
PREFIX = cloud_
PREFIX = {dbprefix}
DEBUG = false
[LANG]

View File

@@ -5,7 +5,7 @@
网站后台管理可一键同步宝塔官方的插件列表与增量更新插件包还有云端使用记录、IP黑白名单、操作日志、定时任务等功能。
本项目自带的宝塔安装包和更新包是7.9.3最新版已修改适配此第三方云端并且全开源无so等加密文件。
本项目自带的宝塔安装包和更新包是7.9.5最新版已修改适配此第三方云端并且全开源无so等加密文件。
觉得该项目不错的可以给个Star~
@@ -28,19 +28,14 @@
- 如果是下载的源码包,需要执行 `composer install --no-dev` 安装依赖如果是下载的Release包则不需要
- 设置网站运行目录为`public`
- 设置伪静态为`ThinkPHP`
- 导入`install.sql`到数据库
-`.env`里面修改数据库信息包括数据库地址HOSTNAME、数据库名DATABASE、用户名USERNAME、密码PASSWORD
- 访问`/admin`进入网站后台默认管理员用户名密码admin/123456
- 访问网站,会自动跳转到安装页面,根据提示安装完成
## 使用方法
-`系统基本设置`修改宝塔面板接口设置。你需要一个官方最新脚本安装并绑定账号的宝塔面板,用于获取最新插件列表及插件包。并根据界面提示安装好专用插件
-`批量替换工具`执行页面显示的命令可将bt安装包、更新包和脚本文件里面的`http://www.example.com`批量替换成当前网站的网址
-`系统基本设置`修改宝塔面板接口设置。你需要准备一个使用官方最新脚本安装并绑定账号的宝塔面板,用于获取最新插件列表及插件包。并根据界面提示安装好专用插件。
-`定时任务设置`执行所显示的命令从宝塔官方获取最新的插件列表并批量下载插件包(增量更新)。当然你也可以去插件列表,一个一个点击下载。
- 在public/install/src和update文件夹里面分别是Linux面板安装包和更新包解压后源码里面全部的 www.example.com 替换成你自己搭建的云端域名如果云端用了强制https也需要单独改然后重新打包。可使用VSCode等支持批量替换的软件
- 在public/win/panel/panel_x.x.x.zip是Windows面板的更新包同样方法替换域名。
- Linux面板安装脚本public/install/install_6.0.sh和更新脚本update6.sh里面的 www.example.com 替换成你自己搭建的云端域名。
- Windows面板更新脚本 public/win/install/panel_update.py、public/win/panel/data/setup.py、api.py 里面的 www.example.com 替换成你自己搭建的云端域名。
- 访问网站`/download`查看使用此第三方云端的一键安装脚本
- 访问网站`/download`查看使用此第三方云端的一键安装脚本
## 其他

115
app/command/DecryptFile.php Normal file
View File

@@ -0,0 +1,115 @@
<?php
declare (strict_types = 1);
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
use think\facade\Config;
use app\lib\Plugins;
class DecryptFile extends Command
{
protected function configure()
{
$this->setName('decrypt')
->addArgument('type', Argument::REQUIRED, '文件类型,plugin:插件文件,module:模块文件,classdir:宝塔class目录,all:所有py文件')
->addArgument('file', Argument::REQUIRED, '文件路径')
->addArgument('os', Argument::OPTIONAL, '操作系统:Windows/Linux')
->setDescription('解密宝塔面板python文件');
}
protected function execute(Input $input, Output $output)
{
$type = trim($input->getArgument('type'));
$file = trim($input->getArgument('file'));
if(!file_exists($file)){
$output->writeln('文件不存在');
return;
}
if($type == 'plugin'){
$os = trim($input->getArgument('os'));
try{
if(Plugins::decode_plugin_main_local($file, $os)){
$output->writeln('文件解密成功!');
}else{
$output->writeln('文件解密失败!');
}
}catch(\Exception $e){
$output->writeln($e->getMessage());
}
}elseif($type == 'module'){
try{
$res = Plugins::decode_module_file($file);
if($res == 2){
$output->writeln('文件解密失败!');
}elseif($res == 1){
$output->writeln('文件解密成功!');
}
}catch(\Exception $e){
$output->writeln($e->getMessage());
}
}elseif($type == 'classdir'){
$file = rtrim($file, '/');
if(!file_exists($file.'/common.py')){
$output->writeln('当前路径非宝塔面板class目录');
return;
}
$dirs = glob($file.'/*Model');
foreach($dirs as $dir){
if(!is_dir($dir))continue;
$files = glob($dir.'/*Model.py');
foreach($files as $file){
try{
$res = Plugins::decode_module_file($file);
if($res == 2){
$output->writeln('文件解密失败:'.$file);
}elseif($res == 1){
$output->writeln('文件解密成功:'.$file);
}
}catch(\Exception $e){
$output->writeln($e->getMessage().''.$file);
}
}
}
}elseif($type == 'all'){
$file = rtrim($file, '/');
$this->scan_all_file($input, $output, $file);
}else{
$output->writeln('未知文件类型');
}
}
private function scan_all_file(Input $input, Output $output, $path) {
$dir = opendir($path);
while(false !== ( $file = readdir($dir)) ) {
if (( $file != '.' ) && ( $file != '..' )) {
$filepath = $path . '/' . $file;
if ( is_dir($filepath) ) {
$this->scan_all_file($input, $output, $filepath);
}
elseif(substr($filepath, -3) == '.py') {
try{
$res = Plugins::decode_module_file($filepath);
if($res == 2){
$output->writeln('文件解密失败:'.$filepath);
}elseif($res == 1){
$output->writeln('文件解密成功:'.$filepath);
}
}catch(\Exception $e){
$output->writeln($e->getMessage().''.$filepath);
}
}
}
}
closedir($dir);
}
}

View File

@@ -6,6 +6,7 @@ use app\BaseController;
use think\facade\Db;
use think\facade\View;
use think\facade\Request;
use think\facade\Cache;
use app\lib\Btapi;
use app\lib\Plugins;
@@ -357,4 +358,31 @@ class Admin extends BaseController
}
return json(['code'=>-1, 'msg'=>'no act']);
}
public function deplist(){
$deplist_linux = get_data_dir().'config/deployment_list.json';
$deplist_win = get_data_dir('Windows').'config/deployment_list.json';
$deplist_linux_time = file_exists($deplist_linux) ? date("Y-m-d H:i:s", filemtime($deplist_linux)) : '不存在';
$deplist_win_time = file_exists($deplist_win) ? date("Y-m-d H:i:s", filemtime($deplist_win)) : '不存在';
View::assign('deplist_linux_time', $deplist_linux_time);
View::assign('deplist_win_time', $deplist_win_time);
return view();
}
public function refresh_deplist(){
$os = input('get.os');
if(!$os) $os = 'Linux';
try{
Plugins::refresh_deplist($os);
Db::name('log')->insert(['uid' => 0, 'action' => '刷新一键部署列表', 'data' => '刷新'.$os.'一键部署列表成功', 'addtime' => date("Y-m-d H:i:s")]);
return json(['code'=>0,'msg'=>'获取最新一键部署列表成功!']);
}catch(\Exception $e){
return json(['code'=>-1, 'msg'=>$e->getMessage()]);
}
}
public function cleancache(){
Cache::clear();
return json(['code'=>0,'msg'=>'succ']);
}
}

View File

@@ -272,6 +272,12 @@ class Api extends BaseController
return json($json_arr);
}
//获取宝塔SSL列表
public function get_ssl_list(){
$data = bin2hex('[]');
return json(['status'=>true, 'msg'=>'', 'data'=>$data]);
}
public function return_success(){
return json(['status'=>true, 'msg'=>1, 'data'=>(object)[]]);
}

View File

@@ -0,0 +1,83 @@
<?php
namespace app\controller;
use PDO;
use Exception;
use app\BaseController;
use think\facade\View;
use think\facade\Cache;
class Install extends BaseController
{
public function index()
{
if (file_exists(app()->getRootPath().'.env')){
return '当前已经安装成功,如果需要重新安装,请手动删除根目录.env文件';
}
if(request()->isPost()){
$mysql_host = input('post.mysql_host', null, 'trim');
$mysql_port = intval(input('post.mysql_port', '3306'));
$mysql_user = input('post.mysql_user', null, 'trim');
$mysql_pwd = input('post.mysql_pwd', null, 'trim');
$mysql_name = input('post.mysql_name', null, 'trim');
$mysql_prefix = input('post.mysql_prefix', 'cloud_', 'trim');
$admin_username = input('post.admin_username', null, 'trim');
$admin_password = input('post.admin_password', null, 'trim');
if(!$mysql_host || !$mysql_user || !$mysql_pwd || !$mysql_name || !$admin_username || !$admin_password){
return json(['code'=>0, 'msg'=>'必填项不能为空']);
}
$configdata = file_get_contents(app()->getRootPath().'.env.example');
$configdata = str_replace(['{dbhost}','{dbname}','{dbuser}','{dbpwd}','{dbport}','{dbprefix}'], [$mysql_host, $mysql_name, $mysql_user, $mysql_pwd, $mysql_port, $mysql_prefix], $configdata);
try{
$DB=new PDO("mysql:host=".$mysql_host.";dbname=".$mysql_name.";port=".$mysql_port,$mysql_user,$mysql_pwd);
}catch(Exception $e){
if($e->getCode() == 2002){
$errorMsg='连接数据库失败:数据库地址填写错误!';
}elseif($e->getCode() == 1045){
$errorMsg='连接数据库失败:数据库用户名或密码填写错误!';
}elseif($e->getCode() == 1049){
$errorMsg='连接数据库失败:数据库名不存在!';
}else{
$errorMsg='连接数据库失败:'.$e->getMessage();
}
return json(['code'=>0, 'msg'=>$errorMsg]);
}
$DB->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$DB->exec("set sql_mode = ''");
$DB->exec("set names utf8");
$sqls=file_get_contents(app()->getRootPath().'install.sql');
$sqls=explode(';', $sqls);
$sqls[]="REPLACE INTO `".$mysql_prefix."config` VALUES ('syskey', '".random(16)."')";
$sqls[]="REPLACE INTO `".$mysql_prefix."config` VALUES ('admin_username', '".addslashes($admin_username)."')";
$sqls[]="REPLACE INTO `".$mysql_prefix."config` VALUES ('admin_password', '".addslashes($admin_password)."')";
$success=0;$error=0;$errorMsg=null;
foreach ($sqls as $value) {
$value=trim($value);
if(empty($value))continue;
$value = str_replace('cloud_',$mysql_prefix,$value);
if($DB->exec($value)===false){
$error++;
$dberror=$DB->errorInfo();
$errorMsg.=$dberror[2]."\n";
}else{
$success++;
}
}
if(empty($errorMsg)){
if(!file_put_contents(app()->getRootPath().'.env', $configdata)){
return json(['code'=>0, 'msg'=>'保存失败,请确保网站根目录有写入权限']);
}
Cache::clear();
return json(['code'=>1, 'msg'=>'安装完成成功执行SQL语句'.$success.'条']);
}else{
return json(['code'=>0, 'msg'=>$errorMsg]);
}
}
return view();
}
}

View File

@@ -193,7 +193,7 @@ class Btapi
return $output;
}
private function curl_download($url, $localpath, $timeout = 60)
private function curl_download($url, $localpath, $timeout = 300)
{
//定义cookie保存位置
$cookie_file=app()->getRuntimePath().md5($this->BT_PANEL).'.cookie';

View File

@@ -48,8 +48,7 @@ class Plugins
$list[] = $plugin;
}
$data['list'] = $list;
if($data['pro']>-1) $data['pro'] = 0;
if($data['ltd']>-1) $data['ltd'] = strtotime('+1 year');
$data['ltd'] = strtotime('+10 year');
$json_file = get_data_dir($os).'config/plugin_list.json';
if(!file_put_contents($json_file, json_encode($data))){
throw new Exception('保存插件列表失败,文件无写入权限');
@@ -188,6 +187,8 @@ class Plugins
$userinfo = $btapi->get_user_info();
if(isset($userinfo['uid'])){
$src = file_get_contents($main_filepath);
if($src===false)throw new Exception('文件打开失败');
if(!$src || strpos($src, 'import ')!==false)return true;
$uid = $userinfo['uid'];
$serverid = $userinfo['serverid'];
$key = md5(substr($serverid, 10, 16).$uid.$serverid);
@@ -203,7 +204,7 @@ class Plugins
if($tmp) $de_text .= $tmp;
}
}
if(!empty($de_text)){
if(!empty($de_text) && strpos($de_text, 'import ')!==false){
file_put_contents($main_filepath, $de_text);
return true;
}
@@ -213,6 +214,28 @@ class Plugins
}
}
public static function decode_module_file($filepath){
$src = file_get_contents($filepath);
if($src===false)throw new Exception('文件打开失败');
if(!$src || strpos($src, 'import ')!==false)return 0;
$key = 'Z2B87NEAS2BkxTrh';
$iv = 'WwadH66EGWpeeTT6';
$data_arr = explode("\n", $src);
$de_text = '';
foreach($data_arr as $data){
$data = trim($data);
if(!empty($data)){
$tmp = openssl_decrypt($data, 'aes-128-cbc', $key, 0, $iv);
if($tmp) $de_text .= $tmp;
}
}
if(!empty($de_text) && strpos($de_text, 'import ')!==false){
file_put_contents($filepath, $de_text);
return 1;
}
return 2;
}
//去除插件主程序文件授权校验
public static function noauth_plugin_main($main_filepath){
$data = file_get_contents($main_filepath);
@@ -247,8 +270,9 @@ class Plugins
self::download_file($btapi, $filename, $filepath);
if(file_exists($filepath)){
if($filemd5 && md5_file($filepath) != $filemd5){
$msg = filesize($filepath) < 300 ? file_get_contents($filepath) : '插件文件MD5校验失败';
@unlink($filepath);
throw new Exception('插件文件MD5校验失败');
throw new Exception($msg);
}
return true;
}else{

View File

@@ -12,10 +12,12 @@ class AuthAdmin
$cookie = cookie('admin_token');
if($cookie){
$token=authcode($cookie, 'DECODE', config_get('syskey'));
list($user, $sid, $expiretime) = explode("\t", $token);
$session=md5(config_get('admin_username').config_get('admin_password'));
if($session==$sid && $expiretime>time()) {
$islogin = true;
if($token){
list($user, $sid, $expiretime) = explode("\t", $token);
$session=md5(config_get('admin_username').config_get('admin_password'));
if($session==$sid && $expiretime>time()) {
$islogin = true;
}
}
}
request()->islogin = $islogin;

View File

@@ -17,6 +17,16 @@ class LoadConfig
*/
public function handle($request, \Closure $next)
{
if (!file_exists(app()->getRootPath().'.env')){
if(strpos(request()->url(),'/installapp')===false){
return redirect((string)url('/installapp'))->header([
'Cache-Control' => 'no-store, no-cache, must-revalidate',
'Pragma' => 'no-cache',
]);
}else{
return $next($request);
}
}
$res = Db::name('config')->cache('configs',0)->column('value','key');
Config::set($res, 'sys');

76
app/script/convert.sh Normal file
View File

@@ -0,0 +1,76 @@
#!/bin/bash
Linux_Version="7.9.7"
Windows_Version="7.8.0"
FILES=(
public/install/src/panel6.zip
public/install/update/LinuxPanel-${Linux_Version}.zip
public/install/install_6.0.sh
public/install/update_panel.sh
public/install/update6.sh
public/win/install/panel_update.py
public/win/panel/panel_${Windows_Version}.zip
public/win/panel/data/api.py
public/win/panel/data/setup.py
)
DIR=$1
SITEURL=$2
if [ ! -d "$DIR" ]; then
echo "网站目录不存在"
exit 1
fi
if [ "$SITEURL" = "" ]; then
echo "网站URL不正确"
exit 1
fi
function handleFile()
{
Filename=$1
if [ "${Filename##*.}" = "zip" ]; then
handleZipFile $Filename
else
handleTextFile $Filename
fi
}
function handleZipFile()
{
Filename=$1
mkdir -p /tmp/package
unzip -o -q $Filename -d /tmp/package
grep -rl --include=\*.py --include=\*.sh --include=index.js 'http://www.example.com' /tmp/package | xargs -I @ sed -i "s|http://www.example.com|${SITEURL}|g" @
Sprit_SITEURK=${SITEURL//\//\\\\\/}
grep -rl --include=\*.sh 'http:\\\/\\\/www.example.com' /tmp/package | xargs -I @ sed -i "s|http:\\\/\\\/www.example.com|${Sprit_SITEURK}|g" @
rm -f $Filename
cd /tmp/package && zip -9 -q -r $Filename * && cd -
rm -rf /tmp/package
}
function handleTextFile()
{
sed -i "s|http://www.example.com|${SITEURL}|g" $1
}
echo "=========================="
echo "正在处理中..."
echo "=========================="
for File in ${FILES[@]}
do
Filename="${DIR}${File}"
if [ -f "$Filename" ]; then
handleFile $Filename
echo -e "成功处理文件:\033[32m${Filename}\033[0m"
else
echo -e "文件不存在:\033[33m${Filename}\033[0m"
fi
done
echo "=========================="
echo "处理完成"
echo "=========================="

View File

@@ -0,0 +1,49 @@
{extend name="admin/layout" /}
{block name="title"}一键部署列表{/block}
{block name="main"}
<div class="container" style="padding-top:70px;">
<div class="col-sm-12 col-md-10 col-lg-8 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">一键部署列表</h3></div>
<div class="panel-body">
<div class="list-group">
<div class="list-group-item list-group-item-warning">Linux面板</div>
<div class="list-group-item" style="line-height:35px">列表文件更新时间:<font color="blue">{$deplist_linux_time}</font><a href="javascript:refresh_deplist('Linux')" class="btn btn-success pull-right"><i class="fa fa-refresh"></i>重新获取</a></div>
</div>
<div class="list-group">
<div class="list-group-item list-group-item-warning">Windows面板</div>
<div class="list-group-item" style="line-height:35px">列表文件更新时间:<font color="blue">{$deplist_win_time}</font><a href="javascript:refresh_deplist('Windows')" class="btn btn-success pull-right"><i class="fa fa-refresh"></i>重新获取</a></div>
</div>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script>
function refresh_deplist(os){
var confirm = layer.confirm('是否确定从宝塔官方获取最新一键部署列表?', {
btn: ['确定','取消']
}, function(){
layer.close(confirm)
var ii = layer.msg('正在获取一键部署列表,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'GET',
url : '/admin/refresh_deplist?os='+os,
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){window.location.reload()});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}, function(){
layer.close(confirm)
});
}
</script>
{/block}

View File

@@ -2,6 +2,7 @@
{block name="title"}宝塔第三方云端管理中心{/block}
{block name="main"}
<style>
.table>tbody>tr>td{white-space: normal;}
.query-title {
background-color:#f5fafe;
word-break: keep-all;

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>{block name="title"}标题{/block}</title>
<link href="//cdn.staticfile.org/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet" />
<link href="//cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
@@ -34,11 +34,12 @@
<li class="{:checkIfActive('index')}">
<a href="/admin"><i class="fa fa-home"></i> 后台首页</a>
</li>
<li class="{:checkIfActive('plugins,pluginswin')}">
<li class="{:checkIfActive('plugins,pluginswin,deplist')}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-cubes"></i> 插件列表<b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="{:checkIfActive('plugins')}"><a href="/admin/plugins">Linux面板</a></li>
<li class="{:checkIfActive('pluginswin')}"><a href="/admin/pluginswin">Windows面板</a></li>
<li class="{:checkIfActive('deplist')}"><a href="/admin/deplist">一键部署列表</a></li>
</ul>
</li>
<li class="{:checkIfActive('record')}">
@@ -56,6 +57,7 @@
<ul class="dropdown-menu">
<li><a href="/admin/set">系统基本设置</a></li>
<li><a href="/admin/set/mod/task">定时任务设置</a></li>
<li><a href="/admin/set/mod/tools">批量替换工具</a></li>
<li><a href="/admin/set/mod/account">管理账号设置</a></li>
</ul>
</li>

View File

@@ -70,12 +70,14 @@ function submitlogin(){
var pass = $("input[name='pass']").val();
var code = $("input[name='code']").val();
if(user=='' || pass==''){layer.alert('用户名或密码不能为空!');return false;}
$.ajax({
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '{:request()->url()}',
data: {username:user, password:pass, code:code},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.msg('登录成功,正在跳转', {icon: 1,shade: 0.01,time: 15000});
window.location.href='/admin';
@@ -87,6 +89,7 @@ function submitlogin(){
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});

View File

@@ -160,6 +160,18 @@
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
<a href="javascript:cleancache()" class="btn btn-default btn-sm btn-block">清理缓存</a>
</form>
</div>
</div>
{elseif $mod=='tools'}
<div class="col-sm-12 col-md-10 col-lg-8 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">批量替换工具</h3></div>
<div class="panel-body">
<form onsubmit="return saveAccount(this)" method="post" class="form" role="form">
<div class="alert alert-info" style="word-break:break-all;">使用以下命令可以将bt安装包、更新包和脚本文件里面的<code>http://www.example.com</code>批量替换成当前网址<code>{:request()->root(true)}</code>,每次更新版本后只需要执行一次即可。</div>
<div class="list-group-item" style="word-break:break-all;">cd {:app()->getRootPath()}app/script && chmod +x convert.sh && ./convert.sh {:app()->getRootPath()} {:request()->root(true)}</div><br/>
</form>
</div>
</div>
@@ -290,5 +302,21 @@ function saveAccount(obj){
});
return false;
}
function cleancache(){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'GET',
url : '/admin/cleancache',
dataType : 'json',
success : function(data) {
layer.close(ii);
layer.msg('清理缓存成功', {icon: 1});
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
}
</script>
{/block}

268
app/view/install/index.html Normal file
View File

@@ -0,0 +1,268 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>宝塔第三方云端 - 安装程序</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="renderer" content="webkit">
<style>
body {
background: #f1f6fd;
margin: 0;
padding: 0;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body, input, button {
font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, 'Microsoft Yahei', Arial, sans-serif;
font-size: 14px;
color: #7E96B3;
}
.container {
max-width: 480px;
margin: 0 auto;
padding: 20px;
text-align: center;
}
a {
color: #4e73df;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1 {
margin-top: 0;
margin-bottom: 10px;
}
h2 {
font-size: 28px;
font-weight: normal;
color: #3C5675;
margin-bottom: 0;
margin-top: 0;
}
form {
margin-top: 40px;
}
.form-group {
margin-bottom: 20px;
}
.form-group .form-field:first-child input {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.form-group .form-field:last-child input {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.form-field input {
background: #fff;
margin: 0 0 2px;
border: 2px solid transparent;
transition: background 0.2s, border-color 0.2s, color 0.2s;
width: 100%;
padding: 15px 15px 15px 180px;
box-sizing: border-box;
}
.form-field input:focus {
border-color: #4e73df;
background: #fff;
color: #444;
outline: none;
}
.form-field label {
float: left;
width: 160px;
text-align: right;
margin-right: -160px;
position: relative;
margin-top: 15px;
font-size: 14px;
pointer-events: none;
opacity: 0.7;
}
button, .btn {
background: #3C5675;
color: #fff;
border: 0;
font-weight: bold;
border-radius: 4px;
cursor: pointer;
padding: 15px 30px;
-webkit-appearance: none;
}
button[disabled] {
opacity: 0.5;
}
.form-buttons {
height: 52px;
line-height: 52px;
}
.form-buttons .btn {
margin-right: 5px;
}
#error, .error, #success, .success, #warmtips, .warmtips {
background: #D83E3E;
color: #fff;
padding: 15px 20px;
border-radius: 4px;
margin-bottom: 20px;
}
#success {
background: #3C5675;
}
#error a, .error a {
color: white;
text-decoration: underline;
}
#warmtips {
background: #fff;
font-size: 14px;
color: #3C5675;
border: 2px solid #4e73df;
text-align: left;
}
</style>
</head>
<body>
<div class="container">
<h1>
<svg t="1660545699809" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4887" width="100px" height="100px">
<path d="M811.4 418.7C765.6 297.9 648.9 212 512.2 212S258.8 297.8 213 418.6C127.3 441.1 64 519.1 64 612c0 110.5 89.5 200 199.9 200h496.2C870.5 812 960 722.5 960 612c0-92.7-63.1-170.7-148.6-193.3z m36.3 281c-23.4 23.4-54.5 36.3-87.6 36.3H263.9c-33.1 0-64.2-12.9-87.6-36.3-23.4-23.4-36.3-54.6-36.3-87.7 0-28 9.1-54.3 26.2-76.3 16.7-21.3 40.2-36.8 66.1-43.7l37.9-9.9 13.9-36.6c8.6-22.8 20.6-44.1 35.7-63.4 14.9-19.2 32.6-35.9 52.4-49.9 41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.2c19.9 14 37.5 30.8 52.4 49.9 15.1 19.3 27.1 40.7 35.7 63.4l13.8 36.5 37.8 10c54.3 14.5 92.1 63.8 92.1 120 0 33.1-12.9 64.3-36.3 87.7z" p-id="4888" fill="#4e73df"></path>
</svg>
</h1>
<h2>宝塔第三方云端 - 安装程序</h2>
<div>
<form method="post">
<div id="error" style="display:none"></div>
<div id="success" style="display:none"></div>
<div id="warmtips" style="display:none"><p>安装完成后,你还需要进行以下操作:</p><p>1、在后台使用批量替换工具执行命令一键替换压缩包与安装脚本中的域名。</p><p></p>2、在后台配置面板对接同步插件列表与插件包。</p></div>
<div class="form-group">
<div class="form-field">
<label>MySQL 数据库地址</label>
<input type="text" name="mysql_host" value="localhost" required="">
</div>
<div class="form-field">
<label>MySQL 数据库端口</label>
<input type="number" name="mysql_port" value="3306">
</div>
<div class="form-field">
<label>MySQL 用户名</label>
<input type="text" name="mysql_user" value="" required="">
</div>
<div class="form-field">
<label>MySQL 密码</label>
<input type="text" name="mysql_pwd" value="" required="">
</div>
<div class="form-field">
<label>MySQL 数据库名</label>
<input type="text" name="mysql_name" value="" required="">
</div>
<div class="form-field">
<label>MySQL 数据表前缀</label>
<input type="text" name="mysql_prefix" value="cloud_">
</div>
</div>
<div class="form-group">
<div class="form-field">
<label>管理员用户名</label>
<input type="text" name="admin_username" value="admin" required=""/>
</div>
<div class="form-field">
<label>管理员密码</label>
<input type="text" name="admin_password" value="123456" required="">
</div>
</div>
<div class="form-buttons">
<!--@formatter:off-->
<button type="submit" >点击安装</button>
<!--@formatter:on-->
</div>
</form>
</div>
</div>
<script src="//cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<script>
$(function () {
$('form').on('submit', function (e) {
e.preventDefault();
var form = this;
var $error = $("#error");
var $success = $("#success");
var $button = $(this).find('button')
.text("安装中...")
.prop('disabled', true);
$.ajax({
url: "",
type: "POST",
dataType: "json",
data: $(this).serialize(),
success: function (ret) {
if (ret.code == 1) {
$error.hide();
$(".form-group", form).remove();
$button.remove();
$("#success").text(ret.msg).show();
$("#warmtips").show();
$buttons = $(".form-buttons", form);
$('<a class="btn" href="/admin" style="background:#4e73df">进入后台</a>').appendTo($buttons);
} else {
$error.show().text(ret.msg);
$button.prop('disabled', false).text("点击安装");
$("html,body").animate({
scrollTop: 0
}, 500);
}
},
error: function (xhr) {
$error.show().text(xhr.responseText);
$button.prop('disabled', false).text("点击安装");
$("html,body").animate({
scrollTop: 0
}, 500);
}
});
return false;
});
});
</script>
</body>
</html>

View File

@@ -6,5 +6,6 @@ return [
// 指令定义
'commands' => [
'updateall' => 'app\command\UpdateAll',
'decrypt' => 'app\command\DecryptFile',
],
];

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

View File

View File

View File

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

View File

View File

View File

@@ -12,12 +12,12 @@ INSERT INTO `cloud_config` (`key`, `value`) VALUES
('bt_key', ''),
('whitelist', '0'),
('download_page', '1'),
('new_version', '7.9.3'),
('new_version', '7.9.7'),
('update_msg', '暂无更新日志'),
('update_date', '2022-07-13'),
('new_version_win', '7.6.0'),
('update_date', '2022-12-21'),
('new_version_win', '7.8.0'),
('update_msg_win', '暂无更新日志'),
('update_date_win', '2022-06-01'),
('update_date_win', '2022-12-08'),
('updateall_type', '0'),
('syskey', 'UqP94LtI8eWAIgCP');

View File

@@ -54,6 +54,27 @@ GetSysInfo(){
echo -e ${SYS_VERSION}
echo -e Bit:${SYS_BIT} Mem:${MEM_TOTAL}M Core:${CPU_INFO}
echo -e ${SYS_INFO}
if [ -z "${os_version}" ];then
echo -e "============================================"
echo -e "检测到为非常用系统安装,建议更换至Centos-7或Debian-10+或Ubuntu-20+系统安装宝塔面板"
echo -e "详情请查看系统兼容表https://docs.qq.com/sheet/DUm54VUtyTVNlc21H?tab=BB08J2"
echo -e "特殊情况可通过以下联系方式寻求安装协助情况"
fi
is64bit=$(getconf LONG_BIT)
if [ "${is64bit}" == '32' ];then
echo -e "宝塔面板不支持32位系统进行安装请使用64位系统/服务器架构进行安装宝塔"
exit 1
fi
S390X_CHECK=$(uname -a|grep s390x)
if [ "${S390X_CHECK}" ];then
echo -e "宝塔面板不支持s390x架构进行安装请使用64位系统/服务器架构进行安装宝塔"
exit 1
fi
echo -e "============================================"
echo -e "请截图以上报错信息发帖至论坛www.bt.cn/bbs求助"
}
Red_Error(){
@@ -97,6 +118,33 @@ System_Check(){
Install_Check
fi
}
Set_Ssl(){
echo -e ""
echo -e "----------------------------------------------------------------------"
echo -e "为了您的面板使用安全建议您开启面板SSL开启后请使用https访问宝塔面板"
echo -e "输入y回车即开启面板SSL并进行下一步安装"
echo -e "输入n回车跳过面板SSL配置直接进行安装"
echo -e "10秒后将跳过SSL配置直接进行面板安装"
echo -e "----------------------------------------------------------------------"
echo -e ""
read -t 10 -p "是否确定开启面板SSL ? (y/n): " yes
if [ $? != 0 ];then
SET_SSL=false
else
case "$yes" in
y)
SET_SSL=true
;;
n)
SET_SSL=false
rm -f /www/server/panel/data/ssl.pl
;;
*)
Set_Ssl
esac
fi
}
Get_Pack_Manager(){
if [ -f "/usr/bin/yum" ] && [ -d "/etc/yum.repos.d" ]; then
PM="yum"
@@ -183,7 +231,12 @@ get_node_url(){
echo '---------------------------------------------';
echo "Selected download node...";
nodes=(http://dg2.bt.cn http://dg1.bt.cn http://125.90.93.52:5880 http://36.133.1.8:5880 http://123.129.198.197 http://38.34.185.130 http://116.213.43.206:5880 http://128.1.164.196);
nodes=(https://dg2.bt.cn https://dg1.bt.cn https://download.bt.cn);
if [ "$1" ];then
nodes=($(echo ${nodes[*]}|sed "s#${1}##"))
fi
tmp_file1=/dev/shm/net_test1.pl
tmp_file2=/dev/shm/net_test2.pl
[ -f "${tmp_file1}" ] && rm -f ${tmp_file1}
@@ -220,7 +273,7 @@ get_node_url(){
if [ -z "$NODE_URL" ];then
NODE_URL=$(cat $tmp_file2|sort -g -t " " -k 1|head -n 1|awk '{print $2}')
if [ -z "$NODE_URL" ];then
NODE_URL='http://download.bt.cn';
NODE_URL='https://download.bt.cn';
fi
fi
rm -f $tmp_file1
@@ -282,7 +335,7 @@ Install_RPM_Pack(){
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
#yum remove -y python-requests python3-requests python-greenlet python3-greenlet
yumPacks="libcurl-devel wget tar gcc make zip unzip openssl openssl-devel gcc libxml2 libxml2-devel libxslt* zlib zlib-devel libjpeg-devel libpng-devel libwebp libwebp-devel freetype freetype-devel lsof pcre pcre-devel vixie-cron crontabs icu libicu-devel c-ares libffi-devel bzip2-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel"
yumPacks="libcurl-devel wget tar gcc make zip unzip openssl openssl-devel gcc libxml2 libxml2-devel libxslt* zlib zlib-devel libjpeg-devel libpng-devel libwebp libwebp-devel freetype freetype-devel lsof pcre pcre-devel vixie-cron crontabs icu libicu-devel c-ares libffi-devel bzip2-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel qrencode"
yum install -y ${yumPacks}
for yumPack in ${yumPacks}
@@ -304,7 +357,19 @@ Install_RPM_Pack(){
}
Install_Deb_Pack(){
ln -sf bash /bin/sh
UBUNTU_22=$(cat /etc/issue|grep "Ubuntu 22")
if [ "${UBUNTU_22}" ];then
apt-get remove needrestart -y
fi
ALIYUN_CHECK=$(cat /etc/motd|grep "Alibaba Cloud ")
if [ "${ALIYUN_CHECK}" ] && [ "${UBUNTU_22}" ];then
apt-get remove libicu70 -y
fi
apt-get update -y
apt-get install bash -y
if [ -f "/usr/bin/bash" ];then
ln -sf /usr/bin/bash /bin/sh
fi
apt-get install ruby -y
apt-get install lsb-release -y
#apt-get install ntp ntpdate -y
@@ -324,12 +389,12 @@ Install_Deb_Pack(){
apt-get install curl -y
fi
debPacks="wget curl libcurl4-openssl-dev gcc make zip unzip tar openssl libssl-dev gcc libxml2 libxml2-dev zlib1g zlib1g-dev libjpeg-dev libpng-dev lsof libpcre3 libpcre3-dev cron net-tools swig build-essential libffi-dev libbz2-dev libncurses-dev libsqlite3-dev libreadline-dev tk-dev libgdbm-dev libdb-dev libdb++-dev libpcap-dev xz-utils git";
debPacks="wget curl libcurl4-openssl-dev gcc make zip unzip tar openssl libssl-dev gcc libxml2 libxml2-dev zlib1g zlib1g-dev libjpeg-dev libpng-dev lsof libpcre3 libpcre3-dev cron net-tools swig build-essential libffi-dev libbz2-dev libncurses-dev libsqlite3-dev libreadline-dev tk-dev libgdbm-dev libdb-dev libdb++-dev libpcap-dev xz-utils git qrencode";
apt-get install -y $debPacks --force-yes
for debPack in ${debPacks}
do
packCheck=$(dpkg -l ${debPack})
packCheck=$(dpkg -l|grep ${debPack})
if [ "$?" -ne "0" ] ;then
apt-get install -y $debPack
fi
@@ -409,7 +474,7 @@ Install_Python_Lib(){
if [ "$is_package" = "" ];then
wget -O $pyenv_path/pyenv/pip.txt $download_Url/install/pyenv/pip.txt -T 5
$pyenv_path/pyenv/bin/pip install -U pip
$pyenv_path/pyenv/bin/pip install -U setuptools
$pyenv_path/pyenv/bin/pip install -U setuptools==65.5.0
$pyenv_path/pyenv/bin/pip install -r $pyenv_path/pyenv/pip.txt
fi
source $pyenv_path/pyenv/bin/activate
@@ -484,6 +549,10 @@ Install_Python_Lib(){
if [ "${os_version}" != "" ];then
pyenv_file="/www/pyenv.tar.gz"
wget -O $pyenv_file $download_Url/install/pyenv/pyenv-${os_type}${os_version}-x${is64bit}.tar.gz -T 10
if [ "$?" != "0" ];then
get_node_url $download_Url
wget -O $pyenv_file $download_Url/install/pyenv/pyenv-${os_type}${os_version}-x${is64bit}.tar.gz -T 10
fi
tmp_size=$(du -b $pyenv_file|awk '{print $1}')
if [ $tmp_size -lt 703460 ];then
rm -f $pyenv_file
@@ -539,7 +608,7 @@ Install_Python_Lib(){
ln -sf $pyenv_path/pyenv/bin/python3.7 /usr/bin/btpython
chmod -R 700 $pyenv_path/pyenv/bin
$pyenv_path/pyenv/bin/pip install -U pip
$pyenv_path/pyenv/bin/pip install -U setuptools
$pyenv_path/pyenv/bin/pip install -U setuptools==65.5.0
$pyenv_path/pyenv/bin/pip install -U wheel==0.34.2
$pyenv_path/pyenv/bin/pip install -r $pyenv_path/pyenv/pip.txt
source $pyenv_path/pyenv/bin/activate
@@ -555,10 +624,7 @@ Install_Bt(){
if [ -f ${setup_path}/server/panel/data/port.pl ];then
panelPort=$(cat ${setup_path}/server/panel/data/port.pl)
else
RE_NUM=$(expr $RANDOM % 5)
if [ "${RE_NUM}" == "1" ];then
panelPort=$(expr $RANDOM % 55535 + 10000)
fi
panelPort=$(expr $RANDOM % 55535 + 10000)
fi
mkdir -p ${setup_path}/server/panel/logs
mkdir -p ${setup_path}/server/panel/vhost/apache
@@ -659,11 +725,19 @@ Set_Bt_Panel(){
auth_path=$(cat /dev/urandom | head -n 16 | md5sum | head -c 8)
echo "/${auth_path}" > ${admin_auth}
fi
auth_path=$(cat /dev/urandom | head -n 16 | md5sum | head -c 8)
echo "/${auth_path}" > ${admin_auth}
chmod -R 700 $pyenv_path/pyenv/bin
/www/server/panel/pyenv/bin/pip3 install pymongo
/www/server/panel/pyenv/bin/pip3 install psycopg2-binary
/www/server/panel/pyenv/bin/pip3 install flask -U
/www/server/panel/pyenv/bin/pip3 install flask-sock
auth_path=$(cat ${admin_auth})
cd ${setup_path}/server/panel/
if [ "$SET_SSL" == true ]; then
btpip install -I pyOpenSSl
btpython /www/server/panel/tools.py ssl
fi
/etc/init.d/bt start
$python_bin -m py_compile tools.py
$python_bin tools.py username
@@ -675,7 +749,7 @@ Set_Bt_Panel(){
/etc/init.d/bt restart
sleep 3
isStart=$(ps aux |grep 'BT-Panel'|grep -v grep|awk '{print $2}')
LOCAL_CURL=$(curl 127.0.0.1:8888/login 2>&1 |grep -i html)
LOCAL_CURL=$(curl 127.0.0.1:${panelPort}/login 2>&1 |grep -i html)
if [ -z "${isStart}" ] && [ -z "${LOCAL_CURL}" ];then
/etc/init.d/bt 22
cd /www/server/panel/pyenv/bin
@@ -753,7 +827,6 @@ Get_Ip_Address(){
isHosts=$(cat /etc/hosts|grep 'www.bt.cn')
if [ -z "${isHosts}" ];then
echo "" >> /etc/hosts
echo "116.213.43.206 www.bt.cn" >> /etc/hosts
getIpAddress=$(curl -sS --connect-timeout 10 -m 60 https://www.bt.cn/Api/getIpAddress)
if [ -z "${getIpAddress}" ];then
sed -i "/bt.cn/d" /etc/hosts
@@ -789,6 +862,7 @@ Setup_Count(){
echo /www > /var/bt_setupPath.conf
}
Install_Main(){
#Set_Ssl
startTime=`date +%s`
Lock_Clear
System_Check
@@ -826,6 +900,8 @@ echo "
+----------------------------------------------------------------------
| The WebPanel URL will be http://SERVER_IP:8888 when installed.
+----------------------------------------------------------------------
| 为了您的正常使用,请确保使用全新或纯净的系统安装宝塔面板,不支持已部署项目/环境的系统安装
+----------------------------------------------------------------------
"
while [ "$go" != 'y' ] && [ "$go" != 'n' ]
do
@@ -836,23 +912,39 @@ if [ "$go" == 'n' ];then
exit;
fi
ARCH_LINUX=$(cat /etc/os-release |grep "Arch Linux")
if [ "${ARCH_LINUX}" ] && [ -f "/usr/bin/pacman" ];then
pacman -Sy
pacman -S curl wget unzip firewalld openssl pkg-config make gcc cmake libxml2 libxslt libvpx gd libsodium oniguruma sqlite libzip autoconf inetutils sudo --noconfirm
fi
Install_Main
PANEL_SSL=$(cat /www/server/panel/data/ssl.pl 2> /dev/null)
if [ "${PANEL_SSL}" == "True" ];then
HTTP_S="https"
else
HTTP_S="http"
fi
echo > /www/server/panel/data/bind.pl
echo -e "=================================================================="
echo -e "\033[32mCongratulations! Installed successfully!\033[0m"
echo -e "=================================================================="
echo "外网面板地址: http://${getIpAddress}:${panelPort}${auth_path}"
echo "内网面板地址: http://${LOCAL_IP}:${panelPort}${auth_path}"
echo "外网面板地址: ${HTTP_S}://${getIpAddress}:${panelPort}${auth_path}"
echo "内网面板地址: ${HTTP_S}://${LOCAL_IP}:${panelPort}${auth_path}"
echo -e "username: $username"
echo -e "password: $password"
echo -e "\033[33mIf you cannot access the panel,\033[0m"
echo -e "\033[33mrelease the following panel port [${panelPort}] in the security group\033[0m"
echo -e "\033[33m若无法访问面板请检查防火墙/安全组是否有放行面板[${panelPort}]端口\033[0m"
if [ "${HTTP_S}" == "https" ];then
echo -e "\033[33m因已开启面板自签证书访问面板会提示不匹配证书请参考以下链接配置证书\033[0m"
echo -e "\033[33mhttps://www.bt.cn/bbs/thread-105443-1-1.html\033[0m"
fi
echo -e "=================================================================="
endTime=`date +%s`
((outTime=($endTime-$startTime)/60))
echo -e "Time consumed:\033[32m $outTime \033[0mMinute!"

View File

@@ -10,7 +10,12 @@ export LANG=en_US.UTF-8
export LANGUAGE=en_US:en
get_node_url(){
nodes=(http://dg2.bt.cn http://dg1.bt.cn http://36.133.1.8:5880 http://123.129.198.197 http://38.34.185.130 http://116.213.43.206:5880 http://128.1.164.196);
nodes=(https://dg2.bt.cn https://dg1.bt.cn https://download.bt.cn https://hk1-node.bt.cn https://na1-node.bt.cn https://jp1-node.bt.cn);
if [ "$1" ];then
nodes=($(echo ${nodes[*]}|sed "s#${1}##"))
fi
tmp_file1=/dev/shm/net_test1.pl
tmp_file2=/dev/shm/net_test2.pl
[ -f "${tmp_file1}" ] && rm -f ${tmp_file1}
@@ -47,7 +52,7 @@ get_node_url(){
if [ -z "$NODE_URL" ];then
NODE_URL=$(cat $tmp_file2|sort -g -t " " -k 1|head -n 1|awk '{print $2}')
if [ -z "$NODE_URL" ];then
NODE_URL='http://download.bt.cn';
NODE_URL='https://download.bt.cn';
fi
fi
rm -f $tmp_file1
@@ -103,7 +108,7 @@ send_check(){
chmod +x /etc/init.d/bt
p_path2=/www/server/panel/class/common.py
p_version=$(cat $p_path2|grep "version = "|awk '{print $3}'|tr -cd [0-9.])
curl -sS --connect-timeout 3 -m 60 http://www.bt.cn/api/panel/notpro?version=$p_version
curl -sS --connect-timeout 3 -m 60 https://www.bt.cn/api/panel/notpro?version=$p_version
NODE_URL=""
exit 0;
}
@@ -142,3 +147,4 @@ if [ ! $NODE_URL ];then
get_node_url
fi

Binary file not shown.

View File

@@ -42,7 +42,7 @@ download_Url=$NODE_URL
setup_path=/www
version=$(curl -Ss --connect-timeout 5 -m 2 $Btapi_Url/api/panel/get_version)
if [ "$version" = '' ];then
version='7.9.3'
version='7.9.7'
fi
armCheck=$(uname -m|grep arm)
if [ "${armCheck}" ];then

View File

@@ -70,7 +70,7 @@ select_node(){
get_version(){
version=$(curl -Ss --connect-timeout 5 -m 2 $Btapi_Url/api/panel/get_version)
if [ "$version" = '' ];then
version='7.9.3'
version='7.9.7'
fi
}

Binary file not shown.

View File

@@ -22,7 +22,7 @@ def readReg(path,key):
return False
panelPath = readReg(r'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\宝塔面板','PanelPath')
if not panelPath:
if not panelPath:
panelPath = os.getenv('BT_PANEL')
if not panelPath: exit();
@@ -44,11 +44,11 @@ class Sql():
__OPT_FIELD = "*" # field条件
__OPT_PARAM = () # where值
__LOCK = panelPath + '/data/sqlite_lock.pl'
def __init__(self):
self.__DB_FILE = panelPath + '/data/default.db'
def __GetConn(self):
def __GetConn(self):
#取数据库对象
try:
if self.__DB_CONN == None:
@@ -62,8 +62,8 @@ class Sql():
#设置表名
self.__DB_TABLE = table
return self
def where(self,where,param):
#WHERE条件
if where:
@@ -73,7 +73,7 @@ class Sql():
def __to_tuple(self,param):
#将参数转换为tuple
if type(param) != tuple:
if type(param) != tuple:
if type(param) == list:
param = tuple(param)
else:
@@ -85,7 +85,7 @@ class Sql():
if not pdata: return False
keys,param = self.__format_pdata(pdata)
return self.save(keys,param)
#构造数据
def __format_pdata(self,pdata):
keys = pdata.keys()
@@ -98,17 +98,17 @@ class Sql():
#FIELD条件
if len(field):
self.__OPT_FIELD = field
return self
return self
def getField(self,keyName):
#取回指定字段
result = self.field(keyName).select()
print(result)
if len(result) != 0:
return result[0][keyName]
return result
def __format_field(self,field):
import re
fields = []
@@ -159,16 +159,16 @@ class Sql():
return data
except Exception as ex:
return "error: " + str(ex)
def setField(self,keyName,keyValue):
#更新指定字段
return self.save(keyName,(keyValue,))
return self.save(keyName,(keyValue,))
def commit(self):
self.__close()
self.__DB_CONN.commit()
def save(self,keys,param):
#更新数据
self.write_lock()
@@ -180,7 +180,7 @@ class Sql():
opt += key + "=?,"
opt = opt[0:len(opt)-1]
sql = "UPDATE " + self.__DB_TABLE + " SET " + opt+self.__OPT_WHERE
#处理拼接WHERE与UPDATE参数
tmp = list(self.__to_tuple(param))
for arg in self.__OPT_PARAM:
@@ -193,8 +193,8 @@ class Sql():
return result.rowcount
except Exception as ex:
return "error: " + str(ex)
def execute(self,sql,param = ()):
#执行SQL语句返回受影响行
self.write_lock()
@@ -225,7 +225,7 @@ class Sql():
def rm_lock(self):
if os.path.exists(self.__LOCK):
os.remove(self.__LOCK)
def query(self,sql,param = ()):
#执行SQL语句返回数据集
self.__GetConn()
@@ -236,7 +236,7 @@ class Sql():
return data
except Exception as ex:
return "error: " + str(ex)
def __close(self):
#清理条件属性
self.__OPT_WHERE = ""
@@ -244,8 +244,8 @@ class Sql():
self.__OPT_ORDER = ""
self.__OPT_LIMIT = ""
self.__OPT_PARAM = ()
def close(self):
#释放资源
try:
@@ -254,7 +254,7 @@ class Sql():
except:
pass
def GetLocalIp():
"""
取本地外网IP
@@ -264,17 +264,16 @@ def GetLocalIp():
filename = panelPath + '/data/iplist.txt'
ipaddress = readFile(filename)
if not ipaddress:
url = 'http://pv.sohu.com/cityjson?ie=utf-8'
url = 'http://www.example.com/api/getIpAddress';
str = httpGet(url)
ipaddress = re.search('\d+.\d+.\d+.\d+',str).group(0)
writeFile(filename,ipaddress)
ipaddress = re.search('\d+.\d+.\d+.\d+',ipaddress).group(0);
return ipaddress
except:
try:
url = 'http://www.example.com/api/getIpAddress';
url = 'https://www.bt.cn/Api/getIpAddress';
str = httpGet(url)
writeFile(filename,ipaddress)
return str
@@ -302,12 +301,12 @@ def start_service(name):
while get_server_status(name) == 0:
try:
win32serviceutil.StartService(name)
time.sleep(1);
except : time.sleep(1);
time.sleep(1);
except : time.sleep(1);
timeout += 1
if timeout > 10:break
if get_server_status(name) != 0:
if get_server_status(name) != 0:
return True,None
return False,'操作失败10秒内未完成启动服务【{}'.format(name)
except :
@@ -319,12 +318,12 @@ def stop_service(name):
while get_server_status(name) == 1:
try:
win32serviceutil.StopService(name)
time.sleep(1);
except : time.sleep(1);
time.sleep(1);
except : time.sleep(1);
timeout += 1
if timeout > 10:break
if get_server_status(name) != 1:
if get_server_status(name) != 1:
return True,None
return False,'操作失败10秒内未完成启动服务【{}'.format(name)
except :
@@ -364,15 +363,15 @@ def downloadFileByWget(url,filename):
if os.path.exists(logPath): os.remove(logPath)
except : pass
loacl_path = '{}/script/wget.exe'.format(panelPath)
if not os.path.exists(loacl_path): downloadFile(get_url()+'/win/panel/data/wget.exe',loacl_path)
if not os.path.exists(loacl_path): downloadFile(get_url()+'/win/panel/data/wget.exe',loacl_path)
if os.path.getsize(loacl_path) < 10:
os.remove(loacl_path)
downloadFile(url,filename)
else:
shell = "{} {} -O {} -t 5 -T 60 --no-check-certificate --auth-no-challenge --force-directorie > {} 2>&1".format(loacl_path,url,filename,logPath)
shell = "{} {} -O {} -t 5 -T 60 --no-check-certificate --auth-no-challenge --force-directorie > {} 2>&1".format(loacl_path,url,filename,logPath)
os.system(shell)
num = 0
re_size = 0
while num <= 5:
@@ -382,17 +381,17 @@ def downloadFileByWget(url,filename):
break;
else:
re_size = cr_size
time.sleep(0.5)
time.sleep(0.5)
num += 1
if os.path.exists(filename):
if os.path.exists(filename):
if os.path.getsize(filename) < 1:
os.remove(filename)
downloadFile(url,filename)
else:
downloadFile(url,filename)
def writeFile(filename,s_body,mode='w+',encoding = 'utf-8'):
def writeFile(filename,s_body,mode='w+',encoding = 'utf-8'):
try:
fp = open(filename, mode,encoding = encoding);
fp.write(s_body)
@@ -402,7 +401,7 @@ def writeFile(filename,s_body,mode='w+',encoding = 'utf-8'):
return False
def readFile(filename,mode = 'r'):
import os,chardet
if not os.path.exists(filename): return False
if not os.path.isfile(filename): return False
@@ -425,15 +424,15 @@ def readFile(filename,mode = 'r'):
encoding = 'ansi'
fp = open(filename, mode,encoding = encoding)
f_body = fp.read()
try:
if f_body[0] == '\ufeff':
if f_body[0] == '\ufeff':
#处理带bom格式
new_code = chardet.detect(f_body.encode(encoding))["encoding"]
f_body = f_body.encode(encoding).decode(new_code);
except : pass
fp.close()
except : pass
fp.close()
return f_body
def httpGet(url,timeout = 60,headers = {}):
@@ -445,11 +444,11 @@ def httpGet(url,timeout = 60,headers = {}):
req = urllib.request.Request(url,headers = headers)
response = urllib.request.urlopen(req,timeout = timeout)
result = response.read()
if type(result) == bytes:
if type(result) == bytes:
try:
result = result.decode('utf-8')
except :
result = result.decode('gb2312')
result = result.decode('gb2312')
return result
except Exception as ex:
if headers: return False
@@ -470,9 +469,9 @@ def httpPost(url, data, timeout=60, headers={}):
return result
except Exception as ex:
return str(ex);
def get_timeout(url,timeout=3):
@@ -484,10 +483,10 @@ def get_timeout(url,timeout=3):
def get_url(timeout = 0.5):
import json
try:
try:
#
node_list = [{"protocol":"http://","address":"dg1.bt.cn","port":"80","ping":500},{"protocol":"http://","address":"dg2.bt.cn","port":"80","ping":500},{"protocol":"http://","address":"node.aapanel.com","port":"80","ping":500},{"protocol":"http://","address":"download.bt.cn","port":"80","ping":500}]
node_list = [{"protocol":"http://","address":"dg2.bt.cn","port":"80","ping":500},{"protocol":"http://","address":"dg1.bt.cn","port":"80","ping":500},{"protocol":"http://","address":"download.bt.cn","port":"80","ping":500},{"protocol":"http://","address":"hk1-node.bt.cn","port":"80","ping":500},{"protocol":"http://","address":"na1-node.bt.cn","port":"80","ping":500},{"protocol":"http://","address":"jp1-node.bt.cn","port":"80","ping":500}]
mnode1 = []
mnode2 = []
mnode3 = []
@@ -510,12 +509,12 @@ def get_url(timeout = 0.5):
mnode = sorted(mnode3,key= lambda x:x['net'],reverse=True)
else: #终选中等延迟,中等带宽
mnode = sorted(mnode2,key= lambda x:x['ping'],reverse=False)
if not mnode: return 'http://download.bt.cn'
if not mnode: return 'https://download.bt.cn'
#return mnode[0]['protocol'] + mnode[0]['address'] + ':' + mnode[0]['port']
return "https://" + mnode[0]['address']
except:
return 'http://download.bt.cn'
return 'https://download.bt.cn'
@@ -529,12 +528,12 @@ def del_file_access(filename,user):
sd = win32security.GetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION)
dacl = sd.GetSecurityDescriptorDacl()
ace_count = dacl.GetAceCount()
for i in range(ace_count ,0 ,-1):
for i in range(ace_count ,0 ,-1):
try:
data = {}
data['rev'], data['access'], usersid = dacl.GetAce(i-1)
data['user'],data['group'], data['type'] = win32security.LookupAccountSid('', usersid)
data['user'],data['group'], data['type'] = win32security.LookupAccountSid('', usersid)
if data['user'].lower() == user.lower(): dacl.DeleteAce(i-1) #删除旧的dacl
if data['user'].lower() == 'users': dacl.DeleteAce(i-1) #删除旧的dacl
@@ -542,35 +541,35 @@ def del_file_access(filename,user):
try:
#处理拒绝访问
dacl.DeleteAce(i-1)
except : pass
except : pass
sd.SetSecurityDescriptorDacl(1, dacl, 0)
win32security.SetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION, sd)
except :
pass
return True
def set_file_access(filename,user,access):
def set_file_access(filename,user,access):
try:
sd = win32security.GetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION)
dacl = sd.GetSecurityDescriptorDacl()
ace_count = dacl.GetAceCount()
for i in range(ace_count, 0,-1):
for i in range(ace_count, 0,-1):
try:
data = {}
data['rev'], data['access'], usersid = dacl.GetAce(i-1)
data['user'],data['group'], data['type'] = win32security.LookupAccountSid('', usersid)
data['user'],data['group'], data['type'] = win32security.LookupAccountSid('', usersid)
if data['user'].lower() == user.lower(): dacl.DeleteAce(i-1) #删除旧的dacl
if data['user'].lower() == 'users': dacl.DeleteAce(i-1) #删除旧的dacl
except :
pass
try:
userx, domain, type = win32security.LookupAccountName("", user)
except :
userx, domain, type = win32security.LookupAccountName("", 'IIS APPPOOL\\' + user)
userx, domain, type = win32security.LookupAccountName("", 'IIS APPPOOL\\' + user)
if access > 0: dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, 3, access, userx)
sd.SetSecurityDescriptorDacl(1, dacl, 0)
win32security.SetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION, sd)
return True,None
@@ -582,24 +581,24 @@ def ExecShell(cmdstring, cwd=None, timeout=None, shell=True):
cmdstring_list = cmdstring
else:
cmdstring_list = shlex.split(cmdstring)
if timeout:
end_time = datetime.datetime.now() + datetime.timedelta(seconds=timeout)
sub = subprocess.Popen(cmdstring_list, cwd=cwd, stdin=subprocess.PIPE,shell=shell,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
sub = subprocess.Popen(cmdstring_list, cwd=cwd, stdin=subprocess.PIPE,shell=shell,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
while sub.poll() is None:
time.sleep(0.1)
if timeout:
if end_time <= datetime.datetime.now():
raise Exception("Timeout%s"%cmdstring)
a,e = sub.communicate()
if type(a) == bytes:
if type(a) == bytes:
try:
a = a.decode('utf-8')
except :
except :
a = a.decode('gb2312','ignore')
if type(e) == bytes:
if type(e) == bytes:
try:
e = e.decode('utf-8')
except :
@@ -626,7 +625,7 @@ def GetRandomString1(length):
strings += chars[random.randint(0, chrlen)]
return strings
def GetRandomString2(length):
def GetRandomString2(length):
from random import Random
strings = ''
chars = '!@#$%^&*()_+.,?[]-='
@@ -637,7 +636,7 @@ def GetRandomString2(length):
return strings
def chdck_salt():
sql = Sql()
sql.table('users').execute("ALTER TABLE 'users' ADD 'salt' TEXT",())
@@ -657,7 +656,7 @@ def md5(strings):
"""
import hashlib
m = hashlib.md5()
m.update(strings.encode('utf-8'))
return m.hexdigest()
@@ -673,18 +672,18 @@ def password_salt(password,username=None,uid=None):
salt = sql.table('users').where('id=?',(uid,)).getField('salt')
return md5(md5(password+'_bt.cn')+salt)
def check_user(username):
def check_user(username):
resume = 0
while True:
data, total, resume = win32net.NetUserEnum(None, 3, win32netcon.FILTER_NORMAL_ACCOUNT, resume)
for user in data:
if user['name'] == username: return True
if not resume: break
return False
return False
def add_user(username,password,ps):
try:
if not check_user(username):
if not check_user(username):
d = {}
d['name'] = username
d['password'] = password
@@ -692,7 +691,7 @@ def add_user(username,password,ps):
d['flags'] = win32netcon.UF_NORMAL_ACCOUNT | win32netcon.UF_SCRIPT
d['priv'] = win32netcon.USER_PRIV_USER
win32net.NetUserAdd(None, 1, d)
#设置用户允许登录服务
handle = win32security.LsaOpenPolicy(None, win32security.POLICY_ALL_ACCESS)
sid_obj, domain, tmp = win32security.LookupAccountName(None, username)
@@ -713,25 +712,25 @@ def add_user_bywww():
pwd = GetRandomString(64) + GetRandomString1(32) + GetRandomString2(32)
status,error = add_user('www',pwd,'用于启动宝塔安装的程序,删除后会导致部分软件无法启动,请勿删除')
if not status:
if not status:
writeFile(error_path,error)
return False
return True
def add_user_bymysql():
pwd = GetRandomString(64) + GetRandomString1(32) + GetRandomString2(32)
status,error = add_user('mysql',pwd,'用于启动宝塔安装的程序,删除后会导致部分软件无法启动,请勿删除')
if not status:
if not status:
writeFile(error_path,error)
return False
return True
def getIP(url):
import socket,re
tmp = re.search('http://(.+)\:\d*',url)
if tmp:
if tmp:
domain = tmp.groups()[0]
myaddr = socket.getaddrinfo(domain, 'http')
return myaddr[0][4][0]
@@ -756,14 +755,14 @@ def add_panel_dir():
]
is_break = False
for sobj in slist:
for sobj in slist:
if not os.path.exists(sobj[0]):
os.makedirs(sobj[0])
os.makedirs(sobj[0])
n = 0
while n < 5:
if os.path.exists(sobj[0]): break
os.makedirs(sobj[0])
os.makedirs(sobj[0])
time.sleep(0.5)
n += 1
@@ -773,17 +772,17 @@ def add_panel_dir():
del_file_access(sobj[0],'users')
for user in sobj[1]:
for user in sobj[1]:
n = 0
while n < 3:
while n < 3:
status,error = set_file_access(sobj[0],user,2032127)
if status: break
if status: break
time.sleep(0.5)
if not status:
writeFile(error_path,"目录{}设置{}权限设置错误 -> {}".format(sobj[0],user,error))
break
del_file_access(setupPath,'users')
url = get_url()
@@ -793,18 +792,18 @@ def add_panel_dir():
download_url = '{}/win/panel/data/{}'.format(url,f_name)
n = 0
while n < 10:
while n < 10:
n += 1;
try:
if os.path.exists(local_path) and os.path.getsize(local_path) < 10: os.remove(local_path)
if not os.path.exists(local_path): downloadFileByWget(download_url,local_path)
if os.path.getsize(local_path) and os.path.getsize(local_path) > 10: break;
if os.path.exists(local_path) and os.path.getsize(local_path) < 10: os.remove(local_path)
if not os.path.exists(local_path): downloadFileByWget(download_url,local_path)
if os.path.getsize(local_path) and os.path.getsize(local_path) > 10: break;
writeFile(error_path,'download {} error ->> {} \r\n {}'.format(f_name,download_url,""))
writeFile(error_path,'download {} error ->> {} \r\n {}'.format(f_name,download_url,""))
except :
ip = getIP(url)
writeFile(error_path,'download {} error ->> {} \r\n connect {} \r\n {}'.format(ip,f_name,download_url,get_error_info()))
writeFile(error_path,'download {} error ->> {} \r\n connect {} \r\n {}'.format(ip,f_name,download_url,get_error_info()))
if n > 5: return False
time.sleep(0.2)
@@ -816,9 +815,9 @@ def add_panel_dir():
def unzip(src_path,dst_path):
import zipfile
zip_file = zipfile.ZipFile(src_path)
for names in zip_file.namelist():
zip_file.extract(names,dst_path)
zip_file = zipfile.ZipFile(src_path)
for names in zip_file.namelist():
zip_file.extract(names,dst_path)
zip_file.close()
return True
@@ -834,44 +833,44 @@ def download_panel(file_list = []):
#下载面板
loacl_path = setupPath + '/panel.zip'
tmpPath = "{}/temp/panel".format(setupPath)
if os.path.exists(loacl_path): os.remove(loacl_path)
if os.path.exists(loacl_path): os.remove(loacl_path)
if os.path.exists(tmpPath): shutil.rmtree(tmpPath,True)
if not os.path.exists(tmpPath): os.makedirs(tmpPath)
p_ver = sys.argv[2]
downUrl = url + '/win/panel/panel_' + p_ver + '.zip';
downloadFileByWget(downUrl,loacl_path);
unzip(loacl_path,tmpPath)
downloadFileByWget(downUrl,loacl_path);
unzip(loacl_path,tmpPath)
for ff_path in file_list:
if os.path.exists(tmpPath + '/' + ff_path): os.remove(tmpPath + '/' + ff_path)
if os.path.exists(tmpPath + '/' + ff_path): os.remove(tmpPath + '/' + ff_path)
tcPath = '{}\class'.format(tmpPath)
for name in os.listdir(tcPath):
try:
for name in os.listdir(tcPath):
try:
if name.find('win_amd64.pyd') >=0:
oldName = os.path.join(tcPath,name);
lName = name.split('.')[0] + '.pyd'
newName = os.path.join(tcPath,lName)
lName = name.split('.')[0] + '.pyd'
newName = os.path.join(tcPath,lName)
if not os.path.exists(newName):os.rename(oldName,newName)
except :pass
cPath = '{}/panel/class'.format(setupPath)
if os.path.exists(cPath):
if os.path.exists(cPath):
os.system("del /s {}\*.pyc".format(to_path(cPath)))
os.system("del /s {}\*.pyt".format(to_path(cPath)))
for name in os.listdir(cPath):
try:
if name.find('.pyd') >=0:
if name.find('.pyd') >=0:
oldName = os.path.join(cPath,name)
newName = os.path.join(cPath,GetRandomString(8) + '.pyt')
os.rename(oldName,newName)
newName = os.path.join(cPath,GetRandomString(8) + '.pyt')
os.rename(oldName,newName)
except : pass
os.system("del /s {}\*.pyc".format(to_path(cPath)))
os.system("del /s {}\*.pyt".format(to_path(cPath)))
os.system("xcopy /s /c /e /y /r {} {}".format(to_path(tmpPath),to_path(panelPath)))
os.system("xcopy /s /c /e /y /r {} {}".format(to_path(tmpPath),to_path(panelPath)))
try:
os.remove(loacl_path)
except : pass
@@ -880,7 +879,7 @@ def download_panel(file_list = []):
shutil.rmtree(tmpPath,True)
except : pass
s_ver = platform.platform()
s_ver = platform.platform()
net_v = '45'
if s_ver.find('2008') >= 0: net_v = '20'
writeFile('{}/data/net'.format(setupPath),net_v)
@@ -910,7 +909,7 @@ def download_panel(file_list = []):
try:
from gevent import monkey
except :
os.system('"C:\Program Files\python\python.exe" -m pip install gevent')
os.system('"C:\Program Files\python\python.exe" -m pip install gevent')
except :
writeFile(error_path,get_error_info())
@@ -918,7 +917,7 @@ def update_panel():
file_list = ['config/config.json','config/index.json','data/libList.conf','data/plugin.json']
download_panel(file_list)
py_path = 'C:/Program Files/python/python.exe'
ExecShell("\"{}\" {}/panel/runserver.py --startup auto install".format(py_path,setupPath))
@@ -929,7 +928,7 @@ def update_panel():
def init_panel_data():
try:
sql = Sql()
username = sql.table('users').where('id=?',(1,)).getField('username')
username = sql.table('users').where('id=?',(1,)).getField('username')
if username == 'admin':
username = GetRandomString(8)
password = GetRandomString(8)
@@ -937,12 +936,12 @@ def init_panel_data():
sql.table('users').where('id=?',(1,)).setField('username',username)
pwd = password_salt(md5(password),uid=1)
result = sql.table('users').where('id=?',(1,)).setField('password',pwd)
backup_path = panelPath[:2] + '/backup'
www_path = panelPath[:2] + '/wwwroot'
if not os.path.exists(backup_path): os.makedirs(backup_path)
if not os.path.exists(www_path): os.makedirs(www_path)
@@ -953,11 +952,11 @@ def init_panel_data():
if not os.path.exists(bind_path): writeFile(bind_path,'True')
admin_path = panelPath+ '/data/admin_path.pl'
if not os.path.exists(admin_path): writeFile(admin_path,"/" + GetRandomString(8))
if not os.path.exists(admin_path): writeFile(admin_path,"/" + GetRandomString(8))
port_path = panelPath+ '/data/port.pl'
if not os.path.exists(port_path): writeFile(port_path,'8888')
recycle_bin_db = panelPath+ '/data/recycle_bin_db.pl'
if not os.path.exists(recycle_bin_db): writeFile(recycle_bin_db,'True')
@@ -975,26 +974,26 @@ def init_panel_data():
except :
writeFile(error_path,get_error_info())
return False
def add_panel_services(num = 0):
try:
py_path = 'C:/Program Files/python/python.exe'
delete_server('btPanel')
delete_server('btPanel')
ret = ExecShell("\"{}\" {}/panel/runserver.py --startup auto install".format(py_path,setupPath))
delete_server('btTask')
ExecShell("\"{}\" {}/panel/task.py --startup auto install".format(py_path,setupPath))
if get_server_status('btPanel') < 0 or get_server_status('btTask') < 0:
ret1 = ExecShell("\"{}\" {}/panel/task.py --startup auto install".format(py_path,setupPath))
if get_server_status('btPanel') < 0 or get_server_status('btTask') < 0:
if num <= 0 :
localPath = setupPath + "/temp/Time_Zones.reg";
downloadFileByWget(get_url() + '/win/panel/data/Time_Zones.reg',localPath)
ExecShell("regedit /s " + localPath)
add_panel_services(1)
add_panel_services(1)
else:
writeFile(error_path,ret[0] + ret[1])
writeFile(error_path,ret[0] + ret[1] + ret1[0] + ret1[1])
else:
os.system('sc failure btPanel reset=1800 actions=restart/60000/restart/120000/restart/30000')
os.system('sc failure btTask reset=1800 actions=restart/60000/restart/120000/restart/30000')
@@ -1005,9 +1004,9 @@ def add_panel_services(num = 0):
def add_firewall_byport():
conf = ExecShell('netsh advfirewall firewall show rule "宝塔面板"')[0]
if conf.lower().find('tcp') == -1:
if conf.lower().find('tcp') == -1:
ExecShell("netsh advfirewall firewall add rule name=宝塔面板 dir=in action=allow protocol=tcp localport=8888");
ExecShell("netsh advfirewall firewall add rule name=网站访问端口 dir=in action=allow protocol=tcp localport=80");
ExecShell("netsh advfirewall firewall add rule name=远程桌面 dir=in action=allow protocol=tcp localport=3389");
@@ -1029,8 +1028,8 @@ def get_error_log():
return error
if __name__ == "__main__":
stype = sys.argv[1];
if not stype in ['get_error_log']:
stype = sys.argv[1];
if not stype in ['get_error_log']:
if os.path.exists(error_path): os.remove(error_path)
result = eval('{}()'.format(stype))
print(result)

View File

@@ -36,6 +36,9 @@ Route::group('api', function () {
Route::get('/getIpAddress', 'api/get_ip_address');
Route::post('/Auth/GetAuthToken', 'api/get_auth_token');
Route::post('/Auth/GetBindCode', 'api/return_error');
Route::post('/Auth/GetSSLList', 'api/get_ssl_list');
Route::post('/Cert/get_order_list', 'api/return_empty_array');
Route::post('/Cert/get_product_list', 'api/return_success');
Route::get('/Pluginother/get_file', 'api/download_plugin_other');
Route::post('/Pluginother/create_order', 'api/return_error');
@@ -114,9 +117,14 @@ Route::group('admin', function () {
Route::get('/list', 'admin/list');
Route::post('/list_data', 'admin/list_data');
Route::post('/list_op', 'admin/list_op');
Route::get('/deplist', 'admin/deplist');
Route::get('/refresh_deplist', 'admin/refresh_deplist');
Route::get('/cleancache', 'admin/cleancache');
})->middleware(\app\middleware\CheckAdmin::class);
Route::any('/installapp', 'install/index');
Route::miss(function() {
return response('404 Not Found')->code(404);
});

View File

@@ -36,6 +36,7 @@ def get_auth_state():
#执行插件方法(插件名,方法名,参数)
def plugin_run(plugin_name, def_name, args):
if not plugin_name or not def_name: return public.returnMsg(False,'插件名称和插件方法名称不能为空!')
if not path_check(plugin_name) or not path_check(def_name): return public.returnMsg(False,'插件名或方法名不能包含特殊符号!')
p_path = public.get_plugin_path(plugin_name)
if not os.path.exists(p_path + '/index.php') and not os.path.exists(p_path + '/%s_main.py' % plugin_name): return public.returnMsg(False,'插件不存在!')
@@ -73,12 +74,21 @@ def plugin_run(plugin_name, def_name, args):
#执行模块方法(模块名,方法名,参数)
def module_run(mod_name, def_name, args):
if not mod_name or not def_name: return public.returnMsg(False,'模块名称和模块方法名称不能为空!')
if not path_check(mod_name) or not path_check(def_name): return public.returnMsg(False,'模块名或方法名不能包含特殊符号!')
if 'model_index' in args:
if args.model_index:
mod_file = "{}/{}Model/{}Model.py".format(public.get_class_path(),args.model_index,mod_name)
else:
mod_file = "{}/projectModel/{}Model.py".format(public.get_class_path(),mod_name)
else:
module_list = get_module_list()
for module_dir in module_list:
mod_file = "{}/{}/{}Model.py".format(public.get_class_path(),module_dir,mod_name)
if os.path.exists(mod_file): break
mod_file = "{}/projectModel/{}Model.py".format(public.get_class_path(),mod_name)
if not os.path.exists(mod_file):
mod_file = "{}/databaseModel/{}Model.py".format(public.get_class_path(),mod_name)
if not os.path.exists(mod_file):
return public.returnMsg(False,'模块[%s]不存在' % mod_name)
return public.returnMsg(False,'模块[%s]不存在' % mod_name)
def_object = public.get_script_object(mod_file)
if not def_object: return public.returnMsg(False,'模块[%s]不存在!' % mod_name)
@@ -92,3 +102,21 @@ def module_run(mod_name, def_name, args):
result = run_object(args)
return result
#获取模块文件夹列表
def get_module_list():
list = []
class_path = public.get_class_path()
f_list = os.listdir(class_path)
for fname in f_list:
f_path = class_path+'/'+fname
if os.path.isdir(f_path) and len(fname) > 6 and fname.find('.') == -1 and fname.find('Model') != -1:
list.append(fname)
return list
#检查路径是否合法
def path_check(path):
list = ["./","..",",",";",":","?","'","\"","<",">","|","\\","\n","\r","\t","\b","\a","\f","\v","*","%","&","$","#","@","!","~","`","^","(",")","+","=","{","}","[","]"]
for i in path:
if i in list:
return False
return True

View File

@@ -39,86 +39,244 @@ if("undefined" != typeof bt && bt.hasOwnProperty("prompt_confirm")){
}
}
if("undefined" != typeof database && database.hasOwnProperty("del_database")){
database.del_database = function (wid, dbname,obj, callback) {
title = '',
tips = '是否确认【删除数据库】,删除后可能会影响业务使用!';
if(obj && obj.db_type > 0) tips = '远程数据库不支持数据库回收站,删除后将无法恢复,请谨慎操作';
var title = typeof dbname === "function" ?'批量删除数据库':'删除数据库 [ '+ dbname +' ]';
layer.open({
type:1,
title:title,
icon:0,
skin:'delete_site_layer',
area: "530px",
closeBtn: 2,
shadeClose: true,
content:"<div class=\'bt-form webDelete pd30\' id=\'site_delete_form\'>" +
"<i class=\'layui-layer-ico layui-layer-ico0\'></i>" +
"<div class=\'f13 check_title\' style=\'margin-bottom: 20px;\'>"+tips+"</div>" +
"<div style=\'color:red;margin:18px 0 18px 18px;font-size:14px;font-weight: bold;\'>注意:数据无价,请谨慎操作!!!"+(!recycle_bin_db_open?'<br>风险操作:当前数据库回收站未开启,删除数据库将永久消失!':'')+"</div>" +
"</div>",
btn:[lan.public.ok,lan.public.cancel],
yes:function(indexs){
var data = {id: wid,name: dbname};
if(typeof dbname === "function"){
delete data.id;
delete data.name;
}
layer.close(indexs)
if(typeof dbname === "function"){
dbname(data)
}else{
bt.database.del_database(data, function (rdata) {
layer.closeAll()
if (callback) callback(rdata);
bt.msg(rdata);
database.del_database = function (wid, dbname, obj, callback) {
var is_db_type = false, del_data = []
if (typeof wid === 'object') {
del_data = wid
is_db_type = wid.some(function (item) {
return item.db_type > 0
})
var ids = [];
for (var i = 0; i < wid.length; i++) {
ids.push(wid[i].id);
}
wid = ids
}
var type = $('.database-pos .tabs-item.active').data('type'),
title = '',
tips = '';
title = typeof dbname === "function" ? '批量删除数据库' : '删除数据库 - [ ' + dbname + ' ]';
tips = is_db_type || !recycle_bin_db_open || type !== 'mysql' ? '<span class="color-red">当前列表存在彻底删除后无法恢复的数据库</span>,请仔细查看列表,以防误删,是否继续操作?' : '当前列表数据库将迁移至数据库回收站,如需彻底删除请前往数据库回收站,是否继续操作?'
var arrs = wid instanceof Array ? wid : [wid]
var ids = JSON.stringify(arrs),
countDown = 9;
if (arrs.length == 1) countDown = 4
var loadT = bt.load('正在检测数据库数据信息,请稍候...'),
param = { url: 'database/' + bt.data.db_tab_name + '/check_del_data', data: { data: JSON.stringify({ ids: ids }) } }
if (bt.data.db_tab_name == 'mysql') param = { url: 'database?action=check_del_data', data: { ids: ids } }
bt_tools.send(param, function (res) {
loadT.close()
layer.open({
type: 1,
title: title,
area: '740px',
skin: 'verify_site_layer_info',
closeBtn: 2,
shadeClose: true,
content: '<div class="check_delete_site_main hint_confirm pd30">' +
"<div class='hint_title'>\
<i class=\'hint-confirm-icon\'></i>\
<div class=\'hint_con\'>"+ tips + "</div>\
</div>"+
'<div id="check_layer_content" class="ptb15">' +
'</div>' +
'<div class="check_layer_message">' +
(is_db_type ? '<span class="color-red">注意:远程数据库暂不支持数据库回收站,选中的数据库将彻底删除</span><br>' : '') +
(!recycle_bin_db_open ? '<span class="color-red">风险操作:当前数据库回收站未开启,删除数据库将永久消失</span><br>' : '')
+ '<span class="color-red">请仔细阅读以上要删除信息,防止数据库被误删</span></div>' +
'</div>',
btn: ['下一步', lan.public.cancel],
success: function (layers) {
setTimeout(function () { $(layers).css('top', ($(window).height() - $(layers).height()) / 2); }, 50)
var rdata = res.data,
newTime = parseInt(new Date().getTime() / 1000),
t_icon = ' <span class="glyphicon glyphicon-info-sign" style="color: red;width:15px;height: 15px;;vertical-align: middle;"></span>';
for (var j = 0; j < rdata.length; j++) {
for (var i = 0; i < del_data.length; i++) {
if (rdata[j].id == del_data[i].id) {
var is_time_rule = (newTime - rdata[j].st_time) > (86400 * 30) && (rdata[j].total > 1024 * 10),
is_database_rule = res.db_size <= rdata[j].total,
database_time = bt.format_data(rdata[j].st_time, 'yyyy-MM-dd'),
database_size = bt.format_size(rdata[j].total);
var f_size = database_size
var t_size = '注意:此数据库较大,可能为重要数据,请谨慎操作.\n数据库' + database_size;
if (rdata[j].total < 2048) t_size = '注意事项:当前数据库不为空,可能为重要数据,请谨慎操作.\n数据库' + database_size;
if (rdata[j].total === 0) t_size = '';
rdata[j]['t_size'] = t_size
rdata[j]['f_size'] = f_size
rdata[j]['database_time'] = database_time
rdata[j]['is_time_rule'] = is_time_rule
rdata[j]['is_database_rule'] = is_database_rule
rdata[j]['db_type'] = del_data[i].db_type
rdata[j]['conn_config'] = del_data[i].conn_config
}
}
}
var filterData = rdata.filter(function (el) {
return ids.indexOf(el.id) != -1
})
bt_tools.table({
el: '#check_layer_content',
data: filterData,
height: '300px',
column: [
{ fid: 'name', title: '数据库名称' },
{
title: '数据库大小', template: function (row) {
return '<span class="' + (row.is_database_rule ? 'warning' : '') + '" style="width: 110px;" title="' + row.t_size + '">' + row.f_size + (row.is_database_rule ? t_icon : '') + '</span>'
}
},
{
title: '数据库位置', template: function (row) {
var type_column = '-'
switch (row.db_type) {
case 0:
type_column = '本地数据库'
break;
case 1:
case 2:
type_column = '远程数据库'
break;
}
return '<span style="width: 110px;" title="' + type_column + '">' + type_column + '</span>'
}
},
{
title: '创建时间', template: function (row) {
return '<span ' + (is_time_rule && row.total != 0 ? 'class="warning"' : '') + ' title="' + (row.is_time_rule && row.total != 0 ? '重要:此数据库创建时间较早,可能为重要数据,请谨慎操作.' : '') + '时间:' + row.database_time + '">' + row.database_time + '</span>'
}
},
{
title: '删除结果', align: 'right', template: function (row, index, ev, _that) {
var _html = ''
switch (row.db_type) {
case 0:
_html = type !== 'mysql' ? '彻底删除' : (!recycle_bin_db_open ? '彻底删除' : '移至回收站')
break;
case 1:
case 2:
_html = '彻底删除'
break;
}
return '<span style="width: 110px;" class="' + (_html === '彻底删除' ? 'warning' + (row.db_type > 0 ? ' remote_database' : '') : '') + '">' + _html + '</span>'
}
}
],
success: function () {
$('#check_layer_content').find('.glyphicon-info-sign').click(function (e) {
var msg = $(this).parent().prop('title')
msg = msg.replace('数据库:','<br>数据库:')
layer.tips(msg, $(this).parent(), { tips: [1, 'red'], time: 3000 })
$(document).click(function (ev) {
layer.closeAll('tips');
$(this).unbind('click');
ev.stopPropagation();
ev.preventDefault();
});
e.stopPropagation();
e.preventDefault();
});
if ($('.remote_database').length) {
$('.remote_database').each(function (index, el) {
var id = $(el).parent().parent().parent().index()
$('#check_layer_content tbody tr').eq(id).css('background-color', '#ff00000a')
})
}
}
})
},
yes: function (indes, layers) {
title = typeof dbname === "function" ? '二次验证信息,批量删除数据库' : '二次验证信息,删除数据库 - [ ' + dbname + ' ]';
if (type !== 'mysql') {
tips = '<span class="color-red">当前数据库暂不支持数据库回收站,删除后将无法恢复</span>,此操作不可逆,是否继续操作?';
} else {
tips = is_db_type ? '<span class="color-red">远程数据库不支持数据库回收站,删除后将无法恢复</span>,此操作不可逆,是否继续操作?' : recycle_bin_db_open ? '删除后如需彻底删除请前往数据库回收站,是否继续操作?' : '删除后可能会影响业务使用,此操作不可逆,是否继续操作?'
}
layer.open({
type: 1,
title: title,
icon: 0,
skin: 'delete_site_layer',
area: "530px",
closeBtn: 2,
shadeClose: true,
content: "<div class=\'bt-form webDelete hint_confirm pd30\' id=\'site_delete_form\'>" +
"<div class='hint_title'>\
<i class=\'hint-confirm-icon\'></i>\
<div class=\'hint_con\'>"+ tips + "</div>\
</div>"+
"<div style=\'color:red;margin:18px 0 18px 18px;font-size:14px;font-weight: bold;\'>注意:数据无价,请谨慎操作!!!" + (type === 'mysql' && !recycle_bin_db_open ? '<br>风险操作:当前数据库回收站未开启,删除数据库将永久消失!' : '') + "</div>"+
"</div>",
btn: ['确认删除', '取消删除'],
yes: function (indexs) {
var data = {
id: wid,
name: dbname
};
if (typeof dbname === "function") {
delete data.id;
delete data.name;
}
layer.close(indexs)
layer.close(indes)
if (typeof dbname === "function") {
dbname(data)
} else {
data.id = data.id[0]
bt.database.del_database(data, function (rdata) {
layer.closeAll()
if (callback) callback(rdata);
bt.msg(rdata);
})
}
}
})
}
}
})
})
}
}
if("undefined" != typeof site && site.hasOwnProperty("del_site")){
site.del_site = function(wid, wname, callback) {
var title = typeof wname === "function" ?'批量删除站点':'删除站点 [ '+ wname +' ]';
site.del_site = function (wid, wname, callback) {
title = typeof wname === "function" ? '批量删除站点' : '删除站点 [ ' + wname + ' ]';
layer.open({
type:1,
title:title,
icon:0,
skin:'delete_site_layer',
type: 1,
title: title,
icon: 0,
skin: 'delete_site_layer',
area: "440px",
closeBtn: 2,
shadeClose: true,
content:"<div class=\'bt-form webDelete pd30\' id=\'site_delete_form\'>" +
content: "<div class=\'bt-form webDelete pd30\' id=\'site_delete_form\'>" +
'<i class="layui-layer-ico layui-layer-ico0"></i>' +
"<div class=\'f13 check_title\'>是否要删除关联的FTP、数据库、站点目录</div>" +
"<div class=\"check_type_group\">" +
"<label><input type=\"checkbox\" name=\"ftp\"><span>FTP</span></label>" +
"<label><input type=\"checkbox\" name=\"database\"><span>数据库</span>"+ (!recycle_bin_db_open?'<span class="glyphicon glyphicon-info-sign" style="color: red"></span>':'') +"</label>" +
"<label><input type=\"checkbox\" name=\"path\"><span>站点目录</span>"+ (!recycle_bin_open?'<span class="glyphicon glyphicon-info-sign" style="color: red"></span>':'') +"</label>" +
"</div>"+
"<label><input type=\"checkbox\" name=\"database\"><span>数据库</span>" + (!recycle_bin_db_open ? '<span class="glyphicon glyphicon-info-sign" style="color: red"></span>' : '') + "</label>" +
"<label><input type=\"checkbox\" name=\"path\"><span>站点目录</span>" + (!recycle_bin_open ? '<span class="glyphicon glyphicon-info-sign" style="color: red"></span>' : '') + "</label>" +
"</div>" +
"</div>",
btn:[lan.public.ok,lan.public.cancel],
success:function(layers,indexs){
$(layers).find('.check_type_group label').hover(function(){
btn: [lan.public.ok, lan.public.cancel],
success: function (layers, indexs) {
$(layers).find('.check_type_group label').hover(function () {
var name = $(this).find('input').attr('name');
if(name === 'data' && !recycle_bin_db_open){
layer.tips('风险操作:当前数据库回收站未开启,删除数据库将永久消失!', this, {tips: [1, 'red'],time:0})
}else if(name === 'path' && !recycle_bin_open){
layer.tips('风险操作:当前文件回收站未开启,删除站点目录将永久消失!', this, {tips: [1, 'red'],time:0})
if (name === 'database' && !recycle_bin_db_open) {
layer.tips('风险操作:当前数据库回收站未开启,删除数据库将永久消失!', this, { tips: [1, 'red'], time: 0 })
} else if (name === 'path' && !recycle_bin_open) {
layer.tips('风险操作:当前文件回收站未开启,删除站点目录将永久消失!', this, { tips: [1, 'red'], time: 0 })
}
},function(){
}, function () {
layer.closeAll('tips');
})
});
},
yes:function(indexs){
var data = {id: wid,webname: wname};
yes: function (indexs) {
var data = { id: wid, webname: wname };
$('#site_delete_form input[type=checkbox]').each(function (index, item) {
if($(item).is(':checked')) data[$(item).attr('name')] = 1
if ($(item).is(':checked')) data[$(item).attr('name')] = 1
})
var is_database = data.hasOwnProperty('database'),is_path = data.hasOwnProperty('path'),is_ftp = data.hasOwnProperty('ftp');
if((!is_database && !is_path) && (!is_ftp || is_ftp)){
if(typeof wname === "function"){
var is_database = data.hasOwnProperty('database'), is_path = data.hasOwnProperty('path'), is_ftp = data.hasOwnProperty('ftp');
if ((!is_database && !is_path) && (!is_ftp || is_ftp)) {
if (typeof wname === "function") {
wname(data)
return false;
}
@@ -129,22 +287,95 @@ if("undefined" != typeof site && site.hasOwnProperty("del_site")){
})
return false
}
if(typeof wname === "function"){
if (typeof wname === "function") {
delete data.id;
delete data.webname;
}
layer.close(indexs)
if(typeof wname === "function"){
console.log(data)
wname(data)
}else{
bt.site.del_site(data, function (rdata) {
layer.closeAll()
if (rdata.status) site.get_list();
if (callback) callback(rdata);
bt.msg(rdata);
var ids = JSON.stringify(wid instanceof Array ? wid : [wid]), countDown = typeof wname === 'string' ? 4 : 9;
title = typeof wname === "function" ? '二次验证信息,批量删除站点' : '二次验证信息,删除站点 [ ' + wname + ' ]';
var loadT = bt.load('正在检测站点数据信息,请稍候...')
bt.send('check_del_data', 'site/check_del_data', { ids: ids }, function (res) {
loadT.close()
layer.open({
type: 1,
title: title,
closeBtn: 2,
skin: 'verify_site_layer_info',
area: '740px',
content: '<div class="check_delete_site_main pd30">' +
'<i class="layui-layer-ico layui-layer-ico0"></i>' +
'<div class="check_layer_title">堡塔温馨提示您,请冷静几秒钟,确认以下要删除的数据。</div>' +
'<div class="check_layer_content">' +
'<div class="check_layer_item">' +
'<div class="check_layer_site"></div>' +
'<div class="check_layer_database"></div>' +
'</div>' +
'</div>' +
'<div class="check_layer_error ' + (is_database && data['database'] && !recycle_bin_db_open ? '' : 'hide') + '"><span class="glyphicon glyphicon-info-sign"></span>风险事项:当前未开启数据库回收站功能,删除数据库后,数据库将永久消失!</div>' +
'<div class="check_layer_error ' + (is_path && data['path'] && !recycle_bin_open ? '' : 'hide') + '"><span class="glyphicon glyphicon-info-sign"></span>风险事项:当前未开启文件回收站功能,删除站点目录后,站点目录将永久消失!</div>' +
'<div class="check_layer_message"><span style="color:red">注意:请仔细阅读以上要删除信息,防止网站数据被误删</span></div>' +
'</div>',
// recycle_bin_db_open &&
// recycle_bin_open &&
btn: ['确认删除', '取消删除'],
success: function (layers) {
var html = '', rdata = res.data;
for (var i = 0; i < rdata.length; i++) {
var item = rdata[i], newTime = parseInt(new Date().getTime() / 1000),
t_icon = '<span class="glyphicon glyphicon-info-sign" style="color: red;width:15px;height: 15px;;vertical-align: middle;"></span>';
site_html = (function (item) {
if (!is_path) return ''
var is_time_rule = (newTime - item.st_time) > (86400 * 30) && (item.total > 1024 * 10),
is_path_rule = res.file_size <= item.total,
dir_time = bt.format_data(item.st_time, 'yyyy-MM-dd'),
dir_size = bt.format_size(item.total);
var f_html = '<i ' + (is_path_rule ? 'class="warning"' : '') + ' style = "vertical-align: middle;" > ' + (item.limit ? '大于50MB' : dir_size) + '</i> ' + (is_path_rule ? t_icon : '');
var f_title = (is_path_rule ? '注意:此目录较大,可能为重要数据,请谨慎操作.\n' : '') + '目录:' + item.path + '(' + (item.limit ? '大于' : '') + dir_size + ')';
return '<div class="check_layer_site">' +
'<span title="站点:' + item.name + '">站点名:' + item.name + '</span>' +
'<span title="' + f_title + '" >目录:<span style="vertical-align: middle;max-width: 160px;width: auto;">' + item.path + '</span> (' + f_html + ')</span>' +
'<span title="' + (is_time_rule ? '注意:此站点创建时间较早,可能为重要数据,请谨慎操作.\n' : '') + '时间:' + dir_time + '">创建时间:<i ' + (is_time_rule ? 'class="warning"' : '') + '>' + dir_time + '</i></span>' +
'</div>'
}(item)),
database_html = (function (item) {
if (!is_database || !item.database) return '';
var is_time_rule = (newTime - item.st_time) > (86400 * 30) && (item.total > 1024 * 10),
is_database_rule = res.db_size <= item.database.total,
database_time = bt.format_data(item.database.st_time, 'yyyy-MM-dd'),
database_size = bt.format_size(item.database.total);
var f_size = '<i ' + (is_database_rule ? 'class="warning"' : '') + ' style = "vertical-align: middle;" > ' + database_size + '</i> ' + (is_database_rule ? t_icon : '');
var t_size = '注意:此数据库较大,可能为重要数据,请谨慎操作.\n数据库' + database_size;
return '<div class="check_layer_database">' +
'<span title="数据库:' + item.database.name + '">数据库:' + item.database.name + '</span>' +
'<span title="' + t_size + '">大小:' + f_size + '</span>' +
'<span title="' + (is_time_rule && item.database.total != 0 ? '重要:此数据库创建时间较早,可能为重要数据,请谨慎操作.' : '') + '时间:' + database_time + '">创建时间:<i ' + (is_time_rule && item.database.total != 0 ? 'class="warning"' : '') + '>' + database_time + '</i></span>' +
'</div>'
}(item))
if ((site_html + database_html) !== '') html += '<div class="check_layer_item">' + site_html + database_html + '</div>';
}
if (html === '') html = '<div style="text-align: center;width: 100%;height: 100%;line-height: 300px;font-size: 15px;">无数据</div>'
$('.check_layer_content').html(html)
},
yes: function (indes, layers) {
if (typeof wname === "function") {
wname(data)
} else {
bt.site.del_site(data, function (rdata) {
layer.closeAll()
if (rdata.status) site.get_list();
if (callback) callback(rdata);
bt.msg(rdata);
})
}
}
})
}
})
}
})
}
@@ -157,7 +388,10 @@ if("undefined" != typeof bt && bt.hasOwnProperty("firewall") && bt.firewall.hasO
if (port.indexOf('-') != -1) ports = port.split('-');
for (var i = 0; i < ports.length; i++) {
if (!bt.check_port(ports[i])) {
layer.msg(lan.firewall.port_err, { icon: 5 });
layer.msg('可用端口范围1-65535', { icon: 2 });
// layer.msg(lan.firewall.port_err, {
// icon: 5
// });
return;
}
}

View File

@@ -0,0 +1,122 @@
#coding: utf-8
import public,os,sys,json
#获取插件列表(0/1)
def get_plugin_list(force = 0):
api_root_url = 'https://api.bt.cn'
api_url = api_root_url+ '/wpanel/get_plugin_list'
cache_file = 'data/plugin_list.json'
if force==0 and os.path.exists(cache_file):
jsonData = public.readFile(cache_file)
softList = json.loads(jsonData)
else:
try:
jsonData = public.HttpGet(api_url)
except Exception as ex:
raise public.error_conn_cloud(str(ex))
softList = json.loads(jsonData)
if type(softList)!=dict or 'list' not in softList: raise Exception('云端插件列表获取失败')
public.writeFile(cache_file, jsonData)
return softList
#获取授权状态() 返回0.免费版 1.专业版 2.企业版 -1.获取失败
def get_auth_state():
try:
softList = get_plugin_list()
if softList['ltd'] > -1:
return 2
elif softList['pro'] > -1:
return 1
else:
return 0
except:
return -1
#执行插件方法(插件名,方法名,参数)
def plugin_run(plugin_name, def_name, args):
if not plugin_name or not def_name: return public.returnMsg(False,'插件名称和插件方法名称不能为空!')
if not path_check(plugin_name) or not path_check(def_name): return public.returnMsg(False,'插件名或方法名不能包含特殊符号!')
p_path = public.get_plugin_path(plugin_name)
if not os.path.exists(p_path + '/index.php') and not os.path.exists(p_path + '/%s_main.py' % plugin_name): return public.returnMsg(False,'插件不存在!')
is_php = os.path.exists(p_path + '/index.php')
if not is_php:
sys.path.append(p_path)
plugin_main = __import__(plugin_name + '_main')
try:
if sys.version_info[0] == 2:
reload(plugin_main)
else:
from imp import reload
reload(plugin_main)
except:
pass
plu = eval('plugin_main.' + plugin_name + '_main()')
if not hasattr(plu, def_name):
return public.returnMsg(False,'在[%s]插件中找不到[%s]方法' % (plugin_name,def_name))
if 'plugin_get_object' in args and args.plugin_get_object == 1:
if not is_php:
return getattr(plu, def_name)
else:
return None
else:
if not is_php:
data = eval('plu.' + def_name + '(args)')
else:
import panelPHP
args.s = def_name
args.name = plugin_name
data = panelPHP.panelPHP(plugin_name).exec_php_script(args)
return data
#执行模块方法(模块名,方法名,参数)
def module_run(mod_name, def_name, args):
if not mod_name or not def_name: return public.returnMsg(False,'模块名称和模块方法名称不能为空!')
if not path_check(mod_name) or not path_check(def_name): return public.returnMsg(False,'模块名或方法名不能包含特殊符号!')
if 'model_index' in args:
if args.model_index:
mod_file = "{}/{}Model/{}Model.py".format(public.get_class_path(),args.model_index,mod_name)
else:
mod_file = "{}/projectModel/{}Model.py".format(public.get_class_path(),mod_name)
else:
module_list = get_module_list()
for module_dir in module_list:
mod_file = "{}/{}/{}Model.py".format(public.get_class_path(),module_dir,mod_name)
if os.path.exists(mod_file): break
if not os.path.exists(mod_file):
return public.returnMsg(False,'模块[%s]不存在' % mod_name)
def_object = public.get_script_object(mod_file)
if not def_object: return public.returnMsg(False,'模块[%s]不存在!' % mod_name)
try:
run_object = getattr(def_object.main(),def_name,None)
except:
return public.returnMsg(False,'模块入口实例化失败' % mod_name)
if not run_object: return public.returnMsg(False,'在[%s]模块中找不到[%s]方法' % (mod_name,def_name))
if 'module_get_object' in args and args.module_get_object == 1:
return run_object
result = run_object(args)
return result
#获取模块文件夹列表
def get_module_list():
list = []
class_path = public.get_class_path()
f_list = os.listdir(class_path)
for fname in f_list:
f_path = class_path+'/'+fname
if os.path.isdir(f_path) and len(fname) > 6 and fname.find('.') == -1 and fname.find('Model') != -1:
list.append(fname)
return list
#检查路径是否合法
def path_check(path):
list = ["./","..",",",";",":","?","'","\"","<",">","|","\\","\n","\r","\t","\b","\a","\f","\v","*","%","&","$","#","@","!","~","`","^","(",")","+","=","{","}","[","]"]
for i in path:
if i in list:
return False
return True

View File

@@ -43,66 +43,128 @@ if("undefined" != typeof bt && bt.hasOwnProperty("prompt_confirm")){
}
if("undefined" != typeof database && database.hasOwnProperty("del_database")){
database.del_database = function (wid, dbname,obj, callback) {
var tips = '是否确认【删除数据库】,删除后可能会影响业务使用!';
if(obj && obj.db_type > 0) tips = '远程数据库不支持数据库回收站,删除后将无法恢复,请谨慎操作';
var title = typeof dbname === "function" ?'批量删除数据库':'删除数据库 [ '+ dbname +' ]';
layer.open({
type:1,
title:title,
icon:0,
skin:'delete_site_layer',
area: "530px",
closeBtn: 2,
shadeClose: true,
content:"<div class=\'bt-form webDelete pd30\' id=\'site_delete_form\'>" +
"<i class=\'layui-layer-ico layui-layer-ico0\'></i>" +
"<div class=\'f13 check_title\' style=\'margin-bottom: 20px;\'>"+tips+"</div>" +
"<div style=\'color:red;margin:18px 0 18px 18px;font-size:14px;font-weight: bold;\'>注意:数据无价,请谨慎操作!!!"+(!recycle_bin_db_open?'<br>风险操作:当前数据库回收站未开启,删除数据库将永久消失!':'')+"</div>" +
"</div>",
btn:[lan.public.ok,lan.public.cancel],
yes:function(indexs){
var data = {id: wid,name: dbname};
if(typeof dbname === "function"){
delete data.id;
delete data.name;
}
layer.close(indexs)
if(typeof dbname === "function"){
dbname(data)
}else{
bt.database.del_database(data, function (rdata) {
layer.closeAll()
if (rdata.status) database_table.$refresh_table_list(true);
if (callback) callback(rdata);
bt.msg(rdata);
})
}
}
})
var title = '',
tips = '是否确认【删除数据库】,删除后可能会影响业务使用!';
if(obj && obj.db_type > 0) tips = '远程数据库不支持数据库回收站,删除后将无法恢复,请谨慎操作';
title = typeof dbname === "function" ?'批量删除数据库':'删除数据库 [ '+ dbname +' ]';
layer.open({
type:1,
title:title,
icon:0,
skin:'delete_site_layer',
area: "530px",
closeBtn: 2,
shadeClose: true,
content:"<div class=\'bt-form webDelete pd30\' id=\'site_delete_form\'>" +
"<i class=\'layui-layer-ico layui-layer-ico0\'></i>" +
"<div class=\'f13 check_title\' style=\'margin-bottom: 20px;\'>"+tips+"</div>" +
"<div style=\'color:red;margin:18px 0 18px 18px;font-size:14px;font-weight: bold;\'>注意:数据无价,请谨慎操作!!!"+(!recycle_bin_db_open?'<br>风险操作:当前数据库回收站未开启,删除数据库将永久消失!':'')+"</div>" +
"</div>",
btn:[lan.public.ok,lan.public.cancel],
yes:function(indexs){
var data = {id: wid,name: dbname};
if(typeof dbname === "function"){
delete data.id;
delete data.name;
}
layer.close(indexs)
var arrs = wid instanceof Array ? wid : [wid]
var ids = JSON.stringify(arrs), countDown = 9;
if (arrs.length == 1) countDown = 4
title = typeof dbname === "function" ?'二次验证信息,批量删除数据库':'二次验证信息,删除数据库 [ ' + dbname + ' ]';
var loadT = bt.load('正在检测数据库数据信息,请稍后...')
bt_tools.send({url:'database/'+bt.data.db_tab_name+'/check_del_data',data:{data:JSON.stringify({ids: ids})}},function(res){
loadT.close()
layer.open({
type:1,
title:title,
closeBtn: 2,
skin: 'verify_site_layer_info',
area: '740px',
content: '<div class="check_delete_site_main pd30">' +
'<i class="layui-layer-ico layui-layer-ico0"></i>' +
'<div class="check_layer_title">堡塔温馨提示您,请冷静几秒钟,确认是否要删除以下数据。</div>' +
'<div class="check_layer_content">' +
'<div class="check_layer_item">' +
'<div class="check_layer_site"></div>' +
'<div class="check_layer_database"></div>' +
'</div>' +
'</div>' +
'<div class="check_layer_error ' + (recycle_bin_db_open ? 'hide' : '') + '"><span class="glyphicon glyphicon-info-sign"></span>风险事项:当前未开启数据库回收站功能,删除数据库后,数据库将永久消失!</div>' +
'<div class="check_layer_message"><span style="color:red">注意:请仔细阅读以上要删除信息,防止数据库被误删</span></div>' +
'</div>',
btn: ['确认删除', '取消删除'],
success: function (layers) {
var html = '', rdata = res.data;
var filterData = rdata.filter(function(el){
return ids.indexOf(el.id) != -1
})
for (var i = 0; i < filterData.length; i++) {
var item = filterData[i], newTime = parseInt(new Date().getTime() / 1000),
t_icon = '<span class="glyphicon glyphicon-info-sign" style="color: red;width:15px;height: 15px;;vertical-align: middle;"></span>';
database_html = (function(item){
var is_time_rule = (newTime - item.st_time) > (86400 * 30) && (item.total > 1024 * 10),
is_database_rule = res.db_size <= item.total,
database_time = bt.format_data(item.st_time, 'yyyy-MM-dd'),
database_size = bt.format_size(item.total);
var f_size = '<i ' + (is_database_rule ? 'class="warning"' : '') + ' style = "vertical-align: middle;" > ' + database_size + '</i> ' + (is_database_rule ? t_icon : '');
var t_size = '注意:此数据库较大,可能为重要数据,请谨慎操作.\n数据库' + database_size;
return '<div class="check_layer_database">' +
'<span title="数据库:' + item.name + '">数据库:' + item.name + '</span>' +
'<span title="' + t_size+'">大小:' + f_size +'</span>' +
'<span title="' + (is_time_rule && item.total != 0 ? '重要:此数据库创建时间较早,可能为重要数据,请谨慎操作.' : '') + '时间:' + database_time+'">创建时间:<i ' + (is_time_rule && item.total != 0 ? 'class="warning"' : '') + '>' + database_time + '</i></span>' +
'</div>'
}(item))
if(database_html !== '') html += '<div class="check_layer_item">' + database_html +'</div>';
}
if(html === '') html = '<div style="text-align: center;width: 100%;height: 100%;line-height: 300px;font-size: 15px;">无数据</div>'
$('.check_layer_content').html(html)
},
yes:function(indes,layers){
if(typeof dbname === "function"){
dbname(data)
}else{
bt.database.del_database(data, function (rdata) {
layer.closeAll()
if (rdata.status) database_table.$refresh_table_list(true);
if (callback) callback(rdata);
bt.msg(rdata);
})
}
}
})
})
}
})
}
}
if("undefined" != typeof site && site.hasOwnProperty("del_site")){
site.del_site = function(wid, wname, callback) {
var title = typeof wname === "function" ?'批量删除站点':'删除站点 [ '+ wname +' ]';
var title = typeof wname === "function" ? '批量删除站点' : '删除站点 [ ' + wname + ' ]';
recycle_bin_open = bt.get_cookie("is_recycle") || bt.get_cookie("is_recycle") == null ? true : false
layer.open({
type:1,
title:title,
icon:0,
skin:'delete_site_layer',
type: 1,
title: title,
icon: 0,
skin: 'delete_site_layer',
area: "440px",
closeBtn: 2,
shadeClose: true,
content:"<div class=\'bt-form webDelete pd30\' id=\'site_delete_form\'>" +
content: "<div class=\'bt-form webDelete pd30\' id=\'site_delete_form\'>" +
'<i class="layui-layer-ico layui-layer-ico0"></i>' +
"<div class=\'f13 check_title\'>是否要删除关联的FTP、数据库、站点目录</div>" +
"<div class=\"check_type_group\">" +
"<label><input type=\"checkbox\" name=\"ftp\"><span>FTP</span></label>" +
"<label><input type=\"checkbox\" name=\"database\"><span>数据库</span>"+ (!recycle_bin_db_open?'<span class="glyphicon glyphicon-info-sign" style="color: red"></span>':'') +"</label>" +
"<label><input type=\"checkbox\" name=\"path\"><span>站点目录</span>"+ (!recycle_bin_open?'<span class="glyphicon glyphicon-info-sign" style="color: red"></span>':'') +"</label>" +
"</div>"+
"<label><input type=\"checkbox\" name=\"database\"><span>数据库</span>" + (!recycle_bin_db_open ? '<span class="glyphicon glyphicon-info-sign" style="color: red"></span>' : '') + "</label>" +
"<label><input type=\"checkbox\" name=\"path\"><span>站点目录</span>" + (!recycle_bin_open ? '<span class="glyphicon glyphicon-info-sign" style="color: red"></span>' : '') + "</label>" +
"</div>" +
"</div>",
btn:[lan.public.ok,lan.public.cancel],
success:function(layers,indexs){
btn: [lan.public.ok, lan.public.cancel],
success: function (layers, indexs) {
$(layers).find('.check_type_group label').hover(function () {
var name = $(this).find('input').attr('name');
if (name === 'data' && !recycle_bin_db_open) {
@@ -114,14 +176,14 @@ if("undefined" != typeof site && site.hasOwnProperty("del_site")){
layer.closeAll('tips');
})
},
yes:function(indexs){
var data = {id: wid,webname: wname};
yes: function (indexs) {
var data = { id: wid, webname: wname };
$('#site_delete_form input[type=checkbox]').each(function (index, item) {
if($(item).is(':checked')) data[$(item).attr('name')] = 1
if ($(item).is(':checked')) data[$(item).attr('name')] = 1
})
var is_database = data.hasOwnProperty('database'),is_path = data.hasOwnProperty('path'),is_ftp = data.hasOwnProperty('ftp');
if((!is_database && !is_path) && (!is_ftp || is_ftp)){
if(typeof wname === "function"){
var is_database = data.hasOwnProperty('database'), is_path = data.hasOwnProperty('path'), is_ftp = data.hasOwnProperty('ftp');
if ((!is_database && !is_path) && (!is_ftp || is_ftp)) {
if (typeof wname === "function") {
wname(data)
return false;
}
@@ -132,24 +194,104 @@ if("undefined" != typeof site && site.hasOwnProperty("del_site")){
})
return false
}
if(typeof wname === "function"){
if (typeof wname === "function") {
delete data.id;
delete data.webname;
}
layer.close(indexs)
if(typeof wname === "function"){
console.log(data)
wname(data)
}else{
bt.site.del_site(data, function (rdata) {
layer.closeAll()
if (rdata.status) site.get_list();
if (callback) callback(rdata);
bt.msg(rdata);
var arrs = wid instanceof Array ? wid : [wid]
var ids = JSON.stringify(arrs), countDown = 9;
if (arrs.length == 1) countDown = 4
title = typeof wname === "function" ? '二次验证信息,批量删除站点' : '二次验证信息,删除站点 [ ' + wname + ' ]';
var loadT = bt.load('正在检测站点数据信息,请稍后...')
bt.send('check_del_data', 'site/check_del_data', { ids: ids }, function (res) {
loadT.close()
layer.open({
type: 1,
title: title,
closeBtn: 2,
skin: 'verify_site_layer_info',
area: '740px',
content: '<div class="check_delete_site_main pd30">' +
'<i class="layui-layer-ico layui-layer-ico0"></i>' +
'<div class="check_layer_title">堡塔温馨提示您,请冷静几秒钟,确认以下要删除的数据。</div>' +
'<div class="check_layer_content">' +
'<div class="check_layer_item">' +
'<div class="check_layer_site"></div>' +
'<div class="check_layer_database"></div>' +
'</div>' +
'</div>' +
'<div class="check_layer_error ' + (data.database && recycle_bin_db_open ? 'hide' : '') + '"><span class="glyphicon glyphicon-info-sign"></span>风险事项:当前未开启数据库回收站功能,删除数据库后,数据库将永久消失!</div>' +
'<div class="check_layer_error ' + (data.path && recycle_bin_open ? 'hide' : '') + '"><span class="glyphicon glyphicon-info-sign"></span>风险事项:当前未开启文件回收站功能,删除站点目录后,站点目录将永久消失!</div>' +
'<div class="check_layer_message"><span style="color:red">注意:请仔细阅读以上要删除信息,防止网站数据被误删</span></div>' +
'</div>',
// recycle_bin_db_open &&
// recycle_bin_open &&
btn: ['确认删除', '取消删除'],
success: function (layers) {
var html = '', rdata = res.data;
for (var i = 0; i < rdata.length; i++) {
var item = rdata[i], newTime = parseInt(new Date().getTime() / 1000),
t_icon = '<span class="glyphicon glyphicon-info-sign" style="color: red;width:15px;height: 15px;;vertical-align: middle;"></span>';
site_html = (function (item) {
if (!is_path) return ''
var is_time_rule = (newTime - item.st_time) > (86400 * 30) && (item.total > 1024 * 10),
is_path_rule = res.file_size <= item.total,
dir_time = bt.format_data(item.st_time, 'yyyy-MM-dd'),
dir_size = bt.format_size(item.total);
var f_html = '<i ' + (is_path_rule ? 'class="warning"' : '') + ' style = "vertical-align: middle;" > ' + dir_size + '</i> ' + (is_path_rule ? t_icon : '');
var f_title = (is_path_rule ? '注意:此目录较大,可能为重要数据,请谨慎操作.\n' : '') + '目录:' + item.path + '(' + (item.limit ? '大于' : '') + dir_size + ')';
return '<div class="check_layer_site">' +
'<span title="站点:' + item.name + '">站点名:' + item.name + '</span>' +
'<span title="' + f_title + '" >目录:<span style="vertical-align: middle;max-width: 160px;width: auto;">' + item.path + '</span> (' + f_html + ')</span>' +
'<span title="' + (is_time_rule ? '注意:此站点创建时间较早,可能为重要数据,请谨慎操作.\n' : '') + '时间:' + dir_time + '">创建时间:<i ' + (is_time_rule ? 'class="warning"' : '') + '>' + dir_time + '</i></span>' +
'</div>'
}(item)),
database_html = (function (item) {
if (!is_database || !item.database) return '';
var is_time_rule = (newTime - item.st_time) > (86400 * 30) && (item.total > 1024 * 10),
is_database_rule = res.db_size <= item.database.total,
database_time = bt.format_data(item.database.st_time, 'yyyy-MM-dd'),
database_size = bt.format_size(item.database.total);
var f_size = '<i ' + (is_database_rule ? 'class="warning"' : '') + ' style = "vertical-align: middle;" > ' + database_size + '</i> ' + (is_database_rule ? t_icon : '');
var t_size = '注意:此数据库较大,可能为重要数据,请谨慎操作.\n数据库' + database_size;
return '<div class="check_layer_database">' +
'<span title="数据库:' + item.database.name + '">数据库:' + item.database.name + '</span>' +
'<span title="' + t_size + '">大小:' + f_size + '</span>' +
'<span title="' + (is_time_rule && item.database.total != 0 ? '重要:此数据库创建时间较早,可能为重要数据,请谨慎操作.' : '') + '时间:' + database_time + '">创建时间:<i ' + (is_time_rule && item.database.total != 0 ? 'class="warning"' : '') + '>' + database_time + '</i></span>' +
'</div>'
}(item))
if ((site_html + database_html) !== '') html += '<div class="check_layer_item">' + site_html + database_html + '</div>';
}
if (html === '') html = '<div style="text-align: center;width: 100%;height: 100%;line-height: 300px;font-size: 15px;">无数据</div>'
$('.check_layer_content').html(html)
},
yes: function (indes, layers) {
if (typeof wname === "function") {
wname(data)
} else {
bt.site.del_site(data, function (rdata) {
layer.closeAll()
if (rdata.status) site.get_list();
if (callback) callback(rdata);
bt.msg(rdata);
})
}
}
})
}
})
}
})
if(bt.get_cookie("is_recycle") || bt.get_cookie("is_recycle")==null){
$('[name="path"]').attr('checked',true)
}else{
$('[name="path"]').removeProp('checked');
}
}
}
if("undefined" != typeof bt && bt.hasOwnProperty("firewall") && bt.firewall.hasOwnProperty("add_accept_port")){

View File

@@ -1,60 +0,0 @@
#coding: utf-8
import public,os,sys,json
class Plugin:
name = False
p_path = None
is_php = False
plu = None
__api_root_url = 'https://api.bt.cn'
__api_url = __api_root_url+ '/wpanel/get_plugin_list'
__cache_file = 'data/plugin_list.json'
def __init__(self, name):
self.name = name
self.p_path = public.get_plugin_path(name)
self.is_php = os.path.exists(self.p_path + '/index.php')
def get_plugin_list(self, force = False):
if force==False and os.path.exists(self.__cache_file):
jsonData = public.readFile(self.__cache_file)
softList = json.loads(jsonData)
else:
try:
jsonData = public.HttpGet(self.__api_url)
except Exception as ex:
raise public.error_conn_cloud(str(ex))
softList = json.loads(jsonData)
if type(softList)!=dict or 'list' not in softList: raise Exception('云端插件列表获取失败')
public.writeFile(self.__cache_file, jsonData)
return softList
def isdef(self, fun):
if not self.is_php:
sys.path.append(self.p_path)
plugin_main = __import__(self.name + '_main')
try:
from imp import reload
reload(plugin_main)
except:
pass
self.plu = eval('plugin_main.' + self.name + '_main()')
if not hasattr(self.plu, fun):
if self.name == 'btwaf' and fun == 'index':
raise Exception("未购买")
return False
return True
def exec_fun(self, args):
fun = args.s
if not self.is_php:
plu = self.plu
data = eval('plu.' + fun + '(args)')
else:
import panelPHP
args.s = fun
args.name = self.name
data = panelPHP.panelPHP(self.name).exec_php_script(args)
return data

View File

@@ -10,11 +10,13 @@
- 将PluginLoader.py复制到class文件夹
- 批量解密模块文件:执行 php think decrypt classdir <面板class文件夹路径>
- 全局搜索替换 https://api.bt.cn => http://www.example.com
- 全局搜索替换 https://www.bt.cn/api/ => http://www.example.com/api/需排除clearModel.py
- 全局搜索替换 https://www.bt.cn/api/ => http://www.example.com/api/需排除clearModel.py、scanningModel.py、ipsModel.py
- 全局搜索替换 http://download.bt.cn/install/update6.sh => http://www.example.com/install/update6.sh
- 全局搜索替换 https://download.bt.cn/install/update6.sh => http://www.example.com/install/update6.sh
- class/ajax.py 文件 \#是否执行升级程序 下面的 public.get_url() 改成 public.GetConfigValue('home')
@@ -38,20 +40,28 @@
在 def check_domain_cloud(domain): 这一行下面加上 return
在 def get_improvement(): 这一行下面加上 return False
在free_login_area方法内get_free_ips_area替换成get_ips_area
- class/panelPlugin.py 文件download_icon方法内替换 public.GetConfigValue('home') => 'https://www.bt.cn'
- class/panelPlugin.py 文件set_pyenv方法内temp_file = public.readFile(filename)这行代码下面加上
删除public.total_keyword(get.query)这一行
set_pyenv方法内temp_file = public.readFile(filename)这行代码下面加上
```python
temp_file = temp_file.replace('wget -O Tpublic.sh', '#wget -O Tpublic.sh')
temp_file = temp_file.replace('\cp -rpa Tpublic.sh', '#\cp -rpa Tpublic.sh')
temp_file = temp_file.replace('http://download.bt.cn/install/public.sh', 'http://www.example.com/install/public.sh')
temp_file = temp_file.replace('https://download.bt.cn/install/public.sh', 'http://www.example.com/install/public.sh')
```
- install/install_soft.sh 在bash执行之前加入以下代码
```shell
sed -i "s/http:\/\/download.bt.cn\/install\/public.sh/http:\/\/www.example.com\/install\/public.sh/" lib.sh
sed -i "s/https:\/\/download.bt.cn\/install\/public.sh/http:\/\/www.example.com\/install\/public.sh/" lib.sh
sed -i "/wget -O Tpublic.sh/d" $name.sh
```
@@ -61,18 +71,18 @@
"update_software_list": update_software_list,
"check_files_panel": check_files_panel,
"check_panel_msg": check_panel_msg,
- 去除面板日志上报script/site_task.py 文件
PluginLoader.daemon_panel()
删除最下面 logs_analysis() 这1
- 去除WebRTC连接BTPanel/static/js/public.js 删除stun.start();这一
- 去除首页广告BTPanel/static/js/index.js 文件删除最下面index.recommend_paid_version()这一行
- 去除首页自动检测更新避免频繁请求云端BTPanel/static/js/index.js 文件注释掉bt.system.check_update这一段代码外的setTimeout
- 去除内页广告BTPanel/templates/default/layout.html 删除getPaymentStatus();这一行
- [可选]去除各种计算题复制bt.js到 BTPanel/static/ ,在 BTPanel/templates/default/layout.html 的\</body\>前面加入
```javascript
@@ -91,9 +101,8 @@
- [可选]关闭未绑定域名提示页面在class/panelSite.pyroot /www/server/nginx/html改成return 400
- [可选]关闭自动生成访问日志:在 BT-PanelWSGIServer((HOST, PORT)里面增加参数 log=None
- [可选]关闭自动生成访问日志:在 BTPanel/\_\_init\_\_.py 删除public.write_request_log()这一行
在 BTPanel/\_\_init\_\_.py 删除public.write_request_log()这一行
解压安装包panel6.zip将更新包改好的文件覆盖到里面然后重新打包即可更新安装包。

View File

@@ -8,11 +8,11 @@
Windows版宝塔由于加密文件太多无法全部解密因此无法做到全开源。
- 删除pluginAuth.cp38-win_amd64.pyd将win/pluginAuth.py复制到class文件夹
- 删除PluginLoader.pyd将win/PluginLoader.py复制到class文件夹
- 全局搜索替换 https://api.bt.cn => http://www.example.com
- 全局搜索替换 https://www.bt.cn/api/ => http://www.example.com/api/
- 全局搜索替换 https://www.bt.cn/api/ => http://www.example.com/api/需排除ipsModel.py
- 全局搜索替换 http://www.bt.cn/api/ => http://www.example.com/api/
@@ -50,12 +50,14 @@ Windows版宝塔由于加密文件太多无法全部解密因此无法做
- 去除面板日志上报script/site_task.py 文件
删除最下面 logs_analysis() 这1行
- 删除最下面 logs_analysis() 这1行
- 去除首页广告BTPanel/static/js/index.js 文件删除最下面index.recommend_paid_version()这一行
- 去除首页广告BTPanel/static/js/index.js 文件删除最下面index.recommend_paid_version()这一行以及index.consultancy_services()这一行
- 去除首页自动检测更新避免频繁请求云端BTPanel/static/js/index.js 文件注释掉bt.system.check_update这一段代码外的setTimeout
- 去除内页广告BTPanel/templates/default/layout.html 删除getPaymentStatus();这一行
- [可选]去除各种计算题复制win/bt.js到 BTPanel/static/ ,在 BTPanel/templates/default/layout.html 的尾部加入
```javascript
@@ -72,5 +74,5 @@ Windows版宝塔由于加密文件太多无法全部解密因此无法做
删除 if not os.path.exists(self.sitePath + '/.htaccess') 这一行
- [可选]关闭自动生成访问日志:在 BTPanel/\_\_init\_\_.py 删除public.write_request_log()这一行