Compare commits

...

10 Commits
1.1.2 ... 1.4.1

Author SHA1 Message Date
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
flucont
2c4a139a13 update 2022-07-28 18:54:51 +08:00
42 changed files with 1245 additions and 188 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,15 +187,24 @@ class Plugins
$userinfo = $btapi->get_user_info();
if(isset($userinfo['uid'])){
$src = file_get_contents($main_filepath);
$data = explode("\n", $src)[0];
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);
$iv = md5($key.$serverid);
$key = substr($key, 8, 16);
$iv = substr($iv, 8, 16);
$de_text = openssl_decrypt($data, 'aes-128-cbc', $key, 0, $iv);
if(!empty($de_text)){
$data_arr = explode("\n", $src);
$de_text = '';
foreach($data_arr as $data){
$data = trim($data);
if(!empty($data) && strlen($data)!=24){
$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($main_filepath, $de_text);
return true;
}
@@ -206,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);
@@ -240,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.5"
Windows_Version="7.7.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',
],
];

View File

View File

View File

View File

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.5'),
('update_msg', '暂无更新日志'),
('update_date', '2022-07-13'),
('new_version_win', '7.6.0'),
('update_date', '2022-11-02'),
('new_version_win', '7.7.0'),
('update_msg_win', '暂无更新日志'),
('update_date_win', '2022-06-01'),
('update_date_win', '2022-09-09'),
('updateall_type', '0'),
('syskey', 'UqP94LtI8eWAIgCP');

View File

@@ -184,6 +184,11 @@ 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);
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}
@@ -282,7 +287,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 +309,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 +341,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
@@ -484,6 +501,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
@@ -555,7 +576,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)
RE_NUM=$(expr $RANDOM % 3)
if [ "${RE_NUM}" == "1" ];then
panelPort=$(expr $RANDOM % 55535 + 10000)
fi
@@ -660,6 +681,8 @@ Set_Bt_Panel(){
echo "/${auth_path}" > ${admin_auth}
fi
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})
@@ -826,6 +849,8 @@ echo "
+----------------------------------------------------------------------
| The WebPanel URL will be http://SERVER_IP:8888 when installed.
+----------------------------------------------------------------------
| 为了您的正常使用,请确保使用全新或纯净的系统安装宝塔面板,不支持已部署项目/环境的系统安装
+----------------------------------------------------------------------
"
while [ "$go" != 'y' ] && [ "$go" != 'n' ]
do
@@ -836,7 +861,14 @@ 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
echo > /www/server/panel/data/bind.pl
echo -e "=================================================================="
echo -e "\033[32mCongratulations! Installed successfully!\033[0m"
@@ -854,5 +886,3 @@ endTime=`date +%s`
((outTime=($endTime-$startTime)/60))
echo -e "Time consumed:\033[32m $outTime \033[0mMinute!"

View File

@@ -11,6 +11,11 @@ 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);
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}

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.5'
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.5'
fi
}

Binary file not shown.

Binary file not shown.

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,231 @@ 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 () {
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 +274,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 +375,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

@@ -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,9 +10,11 @@
- 将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
@@ -38,8 +40,12 @@
在 def check_domain_cloud(domain): 这一行下面加上 return
在 def get_improvement(): 这一行下面加上 return False
- class/panelPlugin.py 文件download_icon方法内替换 public.GetConfigValue('home') => 'https://www.bt.cn'
- class/panelPlugin.py 文件删除public.total_keyword(get.query)这一行
- class/panelPlugin.py 文件set_pyenv方法内temp_file = public.readFile(filename)这行代码下面加上
```python
@@ -61,18 +67,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 +97,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,7 +50,7 @@ Windows版宝塔由于加密文件太多无法全部解密因此无法做
- 去除面板日志上报script/site_task.py 文件
删除最下面 logs_analysis() 这1行
- 删除最下面 logs_analysis() 这1行
- 去除首页广告BTPanel/static/js/index.js 文件删除最下面index.recommend_paid_version()这一行
@@ -72,5 +72,5 @@ Windows版宝塔由于加密文件太多无法全部解密因此无法做
删除 if not os.path.exists(self.sitePath + '/.htaccess') 这一行
- [可选]关闭自动生成访问日志:在 BTPanel/\_\_init\_\_.py 删除public.write_request_log()这一行