mirror of https://github.com/flucont/btcloud.git

31 changed files with 6478 additions and 128 deletions
-
130README.md
-
52app/command/CleanViteJs.php
-
66app/command/DecryptFile.php
-
8app/common.php
-
17app/controller/Admin.php
-
78app/controller/Api.php
-
11app/lib/BtPlugins.php
-
49app/lib/Plugins.php
-
16app/lib/ThirdPlugins.php
-
6app/script/convert.sh
-
3app/view/admin/layout.html
-
276app/view/admin/pluginsen.html
-
124app/view/admin/set.html
-
56app/view/index/download.html
-
3install.sql
-
2public/install/install_6.0.sh
-
2027public/install/install_7.0_en.sh
-
2033public/install/install_pro_en.sh
-
BINpublic/install/src/panel6.zip
-
BINpublic/install/src/panel_7_en.zip
-
BINpublic/install/update/LinuxPanel-9.3.0.zip
-
BINpublic/install/update/LinuxPanel_EN-7.0.13.zip
-
2public/install/update6.sh
-
1017public/install/update_7.x_en.sh
-
BINpublic/static/file/en/kaixin.zip
-
BINpublic/static/file/kaixin.zip
-
BINpublic/static/images/aapanel.png
-
17route/app.php
-
122wiki/aapanel.md
-
408wiki/files/aapanel/PluginLoader.py
-
83wiki/files/aapanel/bt.js
@ -1,64 +1,66 @@ |
|||
# 宝塔面板第三方云端 |
|||
这是一个用php开发的宝塔面板第三方云端站点程序。 |
|||
|
|||
你可以使用此程序搭建属于自己的宝塔面板第三方云端,实现最新版宝塔面板私有化部署,不与宝塔官方接口通信,满足隐私安全合规需求。同时还可以去除面板强制绑定账号,DIY面板功能等。 |
|||
|
|||
网站后台管理可一键同步宝塔官方的插件列表与增量更新插件包,还有云端使用记录、IP黑白名单、操作日志、定时任务等功能。 |
|||
|
|||
本项目自带的宝塔安装包和更新包是8.0.x最新版,已修改适配此第三方云端,并且全开源,无so等加密文件。 |
|||
|
|||
觉得该项目不错的可以给个Star~ |
|||
|
|||
## 声明 |
|||
|
|||
1.此项目只能以自用为目的,不得侵犯堡塔公司及其他第三方的知识产权和其他合法权利。 |
|||
|
|||
2.搭建使用此项目必须有一定的编程和Linux运维基础,纯小白不建议使用。 |
|||
|
|||
## 环境要求 |
|||
|
|||
* `PHP` >= 7.4 |
|||
* `MySQL` >= 5.6 |
|||
* `fileinfo`扩展 |
|||
* `ZipArchive`扩展 |
|||
|
|||
## 部署方法 |
|||
|
|||
- [下载最新版的Release包](https://github.com/flucont/btcloud/releases) |
|||
- 如果是下载的源码包,需要执行 `composer install --no-dev` 安装依赖,如果是下载的Release包,则不需要 |
|||
- 设置网站运行目录为`public` |
|||
- 设置伪静态为`ThinkPHP` |
|||
- 访问网站,会自动跳转到安装页面,根据提示安装完成 |
|||
|
|||
## 使用方法 |
|||
|
|||
- 在`批量替换工具`,执行页面显示的命令,可将bt安装包、更新包和脚本文件里面的`http://www.example.com`批量替换成当前网站的网址。 |
|||
- 在`系统基本设置`修改宝塔面板接口设置。你需要准备一个使用官方最新脚本安装并绑定账号的宝塔面板,用于获取最新插件列表及插件包。并根据界面提示安装好专用插件。 |
|||
- 在`定时任务设置`执行所显示的命令从宝塔官方获取最新的插件列表并批量下载插件包(增量更新)。当然你也可以去插件列表,一个一个点击下载。 |
|||
- 访问网站`/download`查看使用此第三方云端的一键安装脚本。 |
|||
|
|||
## 更新方法 |
|||
|
|||
- [下载最新版的Release包](https://github.com/flucont/btcloud/releases) |
|||
- 上传覆盖除data文件夹以外的全部文件 |
|||
- 后台使用批量替换工具->获取最新插件列表->修改Linux面板等版本号 |
|||
|
|||
## 其他 |
|||
|
|||
- [Linux面板官方更新包修改记录](./wiki/update.md) |
|||
|
|||
- [Windows面板官方更新包修改记录](./wiki/updatewin.md) |
|||
|
|||
- [宝塔云监控安装包修改记录](./wiki/btmonitor.md) |
|||
|
|||
- 宝塔面板官方版与此第三方云端版对比: |
|||
|
|||
| | 官方版 | 此第三方云端版 | |
|||
| ---------- | ------------------------------------------------------------ | -------------------------------------------------- | |
|||
| 版本更新 | 支持 | 支持 | |
|||
| 面板广告 | 有广告 | 无广告 | |
|||
| 是否全开源 | 没有全开源 | 全开源 | |
|||
| 资源占用 | 各种统计上报等任务,资源占用略高 | 去除了很多无用的定时任务,资源占较少 | |
|||
| 兼容性 | 由于编译的so文件有系统架构限制,兼容的系统仅限已编译的so对应的系统架构 | 由于全开源,没有已编译的so文件,因此无系统架构限制 | |
|||
|
|||
|
|||
# 宝塔面板第三方云端 |
|||
这是一个用php开发的宝塔面板第三方云端站点程序。 |
|||
|
|||
你可以使用此程序搭建属于自己的宝塔面板第三方云端,实现最新版宝塔面板私有化部署,不与宝塔官方接口通信,满足隐私安全合规需求。同时还可以去除面板强制绑定账号,DIY面板功能等。 |
|||
|
|||
网站后台管理可一键同步宝塔官方的插件列表与增量更新插件包,还有云端使用记录、IP黑白名单、操作日志、定时任务等功能。 |
|||
|
|||
本项目自带 宝塔Linux面板、宝塔Windows面板、aaPanel面板、宝塔云监控 的最新版安装包和更新包,已修改适配此第三方云端,并且全开源,无so等加密文件。 |
|||
|
|||
觉得该项目不错的可以给个Star~ |
|||
|
|||
## 声明 |
|||
|
|||
1.此项目只能以自用为目的,不得侵犯堡塔公司及其他第三方的知识产权和其他合法权利。 |
|||
|
|||
2.搭建使用此项目必须有一定的编程和Linux运维基础,纯小白不建议使用。 |
|||
|
|||
## 环境要求 |
|||
|
|||
* `PHP` >= 7.4 |
|||
* `MySQL` >= 5.6 |
|||
* `fileinfo`扩展 |
|||
* `ZipArchive`扩展 |
|||
|
|||
## 部署方法 |
|||
|
|||
- [下载最新版的Release包](https://github.com/flucont/btcloud/releases) |
|||
- 如果是下载的源码包,需要执行 `composer install --no-dev` 安装依赖,如果是下载的Release包,则不需要 |
|||
- 设置网站运行目录为`public` |
|||
- 设置伪静态为`ThinkPHP` |
|||
- 访问网站,会自动跳转到安装页面,根据提示安装完成 |
|||
|
|||
## 使用方法 |
|||
|
|||
- 在`批量替换工具`,执行页面显示的命令,可将bt安装包、更新包和脚本文件里面的`http://www.example.com`批量替换成当前网站的网址。 |
|||
- 在`系统基本设置`修改宝塔面板接口设置。你需要准备一个使用官方最新脚本安装并绑定账号的宝塔面板,用于获取最新插件列表及插件包。并根据界面提示安装好专用插件。 |
|||
- 在`定时任务设置`执行所显示的命令从宝塔官方获取最新的插件列表并批量下载插件包(增量更新)。当然你也可以去插件列表,一个一个点击下载。 |
|||
- 访问网站`/download`查看使用此第三方云端的一键安装脚本。 |
|||
|
|||
## 更新方法 |
|||
|
|||
- [下载最新版的Release包](https://github.com/flucont/btcloud/releases) |
|||
- 上传覆盖除data文件夹以外的全部文件 |
|||
- 后台使用批量替换工具->获取最新插件列表->修改软件版本设置里面的版本号 |
|||
|
|||
## 其他 |
|||
|
|||
- [Linux面板官方更新包修改记录](./wiki/update.md) |
|||
|
|||
- [Windows面板官方更新包修改记录](./wiki/updatewin.md) |
|||
|
|||
- [aaPanel面板官方更新包修改记录](./wiki/aapanel.md) |
|||
|
|||
- [宝塔云监控安装包修改记录](./wiki/btmonitor.md) |
|||
|
|||
- 宝塔面板官方版与此第三方云端版对比: |
|||
|
|||
| | 官方版 | 此第三方云端版 | |
|||
| ---------- | ------------------------------------------------------------ | -------------------------------------------------- | |
|||
| 版本更新 | 支持 | 支持 | |
|||
| 面板广告 | 有广告 | 无广告 | |
|||
| 是否全开源 | 没有全开源 | 全开源 | |
|||
| 资源占用 | 各种统计上报等任务,资源占用略高 | 去除了很多无用的定时任务,资源占较少 | |
|||
| 兼容性 | 由于编译的so文件有系统架构限制,兼容的系统仅限已编译的so对应的系统架构 | 由于全开源,没有已编译的so文件,因此无系统架构限制 | |
|||
|
|||
|
@ -0,0 +1,276 @@ |
|||
{extend name="admin/layout" /} |
|||
{block name="title"}插件列表{/block} |
|||
{block name="main"} |
|||
<style> |
|||
td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:340px;} |
|||
.bt-ico-ask { |
|||
border: 1px solid #fb7d00; |
|||
border-radius: 8px; |
|||
color: #fb7d00; |
|||
cursor: help; |
|||
display: inline-block; |
|||
font-family: arial; |
|||
font-size: 11px; |
|||
font-style: normal; |
|||
height: 16px; |
|||
line-height: 16px; |
|||
margin-left: 5px; |
|||
text-align: center; |
|||
width: 16px |
|||
} |
|||
.bt-ico-ask:hover { |
|||
background-color: #fb7d00; |
|||
color: #fff |
|||
} |
|||
</style> |
|||
<div class="modal fade" id="help" tabindex="-1" role="dialog"> |
|||
<div class="modal-dialog" role="document"> |
|||
<div class="modal-content"> |
|||
<div class="modal-header"> |
|||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> |
|||
<span aria-hidden="true">×</span> |
|||
</button> |
|||
<h4 class="modal-title">帮助</h4> |
|||
</div> |
|||
<div class="modal-body"> |
|||
<p>“版本与状态”一列中,红色的按钮代表本地不存在该版本插件包,需要点击下载;绿色的按钮代表已存在。</p> |
|||
<p>官方插件包本地存储路径是/data/en/plugins/package/软件标识-版本号.zip,第三方插件包路径是/data/plugins/other/other/,对于部分包含二次验证的插件可以自行修改。</p> |
|||
</div> |
|||
<div class="modal-footer"> |
|||
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="container" style="padding-top:70px;"> |
|||
<div class="col-xs-12 center-block" style="float: none;"> |
|||
|
|||
<div id="searchToolbar"> |
|||
<form onsubmit="return searchSubmit()" method="GET" class="form-inline"> |
|||
<div class="form-group"> |
|||
<label>搜索</label> |
|||
<input type="text" class="form-control" name="keyword" placeholder="应用名称"> |
|||
</div> |
|||
<div class="form-group"> |
|||
<select name="type" class="form-control"><option value="0">全部插件</option> |
|||
{foreach $typelist as $k=>$v}<option value="{$k}">{$v}</option>{/foreach} </select> |
|||
</div> |
|||
<div class="form-group"> |
|||
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i> 搜索</button> |
|||
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i> 重置</a> |
|||
<a href="javascript:refresh_plugins()" class="btn btn-success"><i class="fa fa-refresh"></i> 刷新列表</a> |
|||
<a href="javascript:download_plugins()" class="btn btn-warning" id="batch_down" style="display:none;"><i class="fa fa-download"></i> 批量下载</a> |
|||
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#help"><i class="fa fa-info-circle"></i> 帮助</button> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
|
|||
<table id="listTable"> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script> |
|||
<script src="{$cdnpublic}bootstrap-table/1.19.1/bootstrap-table.min.js"></script> |
|||
<script src="{$cdnpublic}bootstrap-table/1.19.1/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script> |
|||
<script src="/static/js/custom.js"></script> |
|||
<script> |
|||
|
|||
function download_version(name, version, status){ |
|||
if(status == true){ |
|||
var confirm = layer.confirm('是否确定重新下载'+version+'版本插件包?', { |
|||
btn: ['确定','取消'] |
|||
}, function(){ |
|||
download_plugin(name, version) |
|||
}, function(){ |
|||
layer.close(confirm) |
|||
}); |
|||
}else{ |
|||
download_plugin(name, version) |
|||
} |
|||
} |
|||
|
|||
function download_plugin(name, version){ |
|||
var ii = layer.msg('正在下载,请稍候...', {icon: 16, shade:0.1, time: 0}); |
|||
$.ajax({ |
|||
type : 'POST', |
|||
url : '/admin/download_plugin', |
|||
data: { name:name, version:version, os:'en'}, |
|||
dataType : 'json', |
|||
success : function(data) { |
|||
layer.close(ii) |
|||
if(data.code == 0){ |
|||
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();}); |
|||
}else{ |
|||
layer.alert(data.msg, {icon:2}); |
|||
} |
|||
}, |
|||
error:function(data){ |
|||
layer.close(ii) |
|||
layer.msg('服务器错误', {icon:2}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function refresh_plugins(){ |
|||
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_plugins?os=en', |
|||
dataType : 'json', |
|||
success : function(data) { |
|||
layer.close(ii) |
|||
if(data.code == 0){ |
|||
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();}); |
|||
}else{ |
|||
layer.alert(data.msg, {icon:2}); |
|||
} |
|||
}, |
|||
error:function(data){ |
|||
layer.close(ii) |
|||
layer.msg('服务器错误', {icon:2}); |
|||
} |
|||
}); |
|||
}, function(){ |
|||
layer.close(confirm) |
|||
}); |
|||
} |
|||
|
|||
function download_plugins(){ |
|||
var confirm = layer.confirm('批量下载当前分类下未下载的插件包', { |
|||
btn: ['确定','取消'] |
|||
}, function(){ |
|||
layer.close(confirm) |
|||
$.downloadCount = 0; |
|||
$.preDownloadCount = $.preDownload.length; |
|||
download_item(); |
|||
}, function(){ |
|||
layer.close(confirm) |
|||
}); |
|||
} |
|||
|
|||
function download_item(){ |
|||
if($.preDownload.length == 0){ |
|||
layer.alert('成功下载'+$.downloadCount+'个插件包!', {icon:1}, function(){layer.closeAll();searchSubmit();}); |
|||
return; |
|||
} |
|||
$.downloadCount++; |
|||
var plugin = $.preDownload[0]; |
|||
var ii = layer.msg('['+$.downloadCount+'/'+$.preDownloadCount+']正在下载'+plugin.name+'-'+plugin.version, {icon: 16, shade:0.1, time: 0}); |
|||
$.ajax({ |
|||
type : 'POST', |
|||
url : '/admin/download_plugin', |
|||
data: { name:plugin.name, version:plugin.version, os:'en'}, |
|||
dataType : 'json', |
|||
success : function(data) { |
|||
layer.close(ii) |
|||
if(data.code == 0){ |
|||
$.preDownload.shift(); |
|||
download_item(); |
|||
}else{ |
|||
layer.alert(data.msg, {icon:2}); |
|||
} |
|||
}, |
|||
error:function(data){ |
|||
layer.close(ii) |
|||
layer.msg('服务器错误', {icon:2}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function searchByType(type){ |
|||
$("input[name=keyword]").val(''); |
|||
$("select[name=type]").val(type); |
|||
searchSubmit() |
|||
} |
|||
|
|||
$(document).ready(function(){ |
|||
updateToolbar(); |
|||
const defaultPageSize = 20; |
|||
|
|||
$("#listTable").bootstrapTable({ |
|||
url: '/admin/plugins_data?os=en', |
|||
pageNumber: 1, |
|||
pageSize: 15, |
|||
sidePagination: 'client', |
|||
classes: 'table table-striped table-hover table-bottom-border', |
|||
columns: [ |
|||
{ |
|||
field: 'name', |
|||
title: '软件标识', |
|||
formatter: function(value, row, index) { |
|||
return '<b>'+value+'</b>'; |
|||
} |
|||
}, |
|||
{ |
|||
field: 'title', |
|||
title: '软件名称' |
|||
}, |
|||
{ |
|||
field: 'type', |
|||
title: '软件分类', |
|||
formatter: function(value, row, index) { |
|||
return '<a href="javascript:searchByType('+value+')" title="查看该分类下的插件">'+row.typename+'</a>'; |
|||
} |
|||
}, |
|||
{ |
|||
field: 'desc', |
|||
title: '说明', |
|||
}, |
|||
{ |
|||
field: 'price', |
|||
title: '价格', |
|||
formatter: function(value, row, index) { |
|||
return value > 0 ? '<span style="color:#fc6d26">¥'+value+'</span>' : '免费'; |
|||
} |
|||
}, |
|||
{ |
|||
field: 'author', |
|||
title: '开发商' |
|||
}, |
|||
{ |
|||
field: 'versions', |
|||
title: '版本与状态', |
|||
formatter: function(value, row, index) { |
|||
var html = ''; |
|||
if(row.type == 5 || row.name == 'mail_sys' || row.name == 'dns_manager'){ |
|||
html += '<a href="javascript:" class="btn btn-xs btn-success" disabled>无需下载</a>'; |
|||
}else{ |
|||
$.each(value, function(index,item){ |
|||
if(item.status) |
|||
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-success">'+item.version+'</a> '; |
|||
else |
|||
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-danger">'+item.version+'</a> '; |
|||
}) |
|||
} |
|||
return html |
|||
} |
|||
}, |
|||
], |
|||
onLoadSuccess: function(data){ |
|||
$.preDownload = []; |
|||
var type = $("select[name=type] option:selected").text(); |
|||
if(type != '全部插件' && type != '运行环境' && type != '第三方应用'){ |
|||
$("#batch_down").show(); |
|||
if(data.length > 0){ |
|||
$.each(data, function(index, plugin){ |
|||
if(plugin.versions.length > 0 && plugin.name!='mail_sys' && plugin.name!='dns_manager'){ |
|||
$.each(plugin.versions, function(index, version){ |
|||
if(!version.status){ |
|||
$.preDownload.push({name:plugin.name, version:version.version}) |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
}else{ |
|||
$("#batch_down").hide(); |
|||
} |
|||
} |
|||
}) |
|||
}) |
|||
</script> |
|||
{/block} |
2027
public/install/install_7.0_en.sh
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
2033
public/install/install_pro_en.sh
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1017
public/install/update_7.x_en.sh
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
After Width: 1920 | Height: 1350 | Size: 94 KiB |
@ -0,0 +1,122 @@ |
|||
# aapanel面板官方更新包修改记录 |
|||
|
|||
查询最新版本号:https://brandnew.aapanel.com/api/panel/getLatestOfficialVersion |
|||
|
|||
官方更新包下载链接:http://download.bt.cn/install/update/LinuxPanel_EN-版本号.zip |
|||
|
|||
假设搭建的宝塔第三方云端网址是 http://www.example.com |
|||
|
|||
- 将class文件夹里面所有的.so文件删除 |
|||
|
|||
- 将aapanel/PluginLoader.py复制到class文件夹 |
|||
|
|||
- 批量解密模块文件:执行 php think decrypt classdir <面板class文件夹路径> |
|||
|
|||
php think decrypt classdir <面板class_v2文件夹路径> |
|||
|
|||
- 全局搜索替换 https://wafapi2.aapanel.com => http://www.example.com(需排除task.py、ipsModel.py、js文件) |
|||
|
|||
- 全局搜索替换 https://node.aapanel.com/install/update_7.x_en.sh => http://www.example.com/install/update_7.x_en.sh |
|||
|
|||
https://node.aapanel.com/install/update_pro_en.sh => http://www.example.com/install/update_7.x_en.sh |
|||
|
|||
- 搜索并删除提交异常报告的代码 bt_error/index.php |
|||
|
|||
- class/ajax.py 文件 \#是否执行升级程序 下面的 public.get_url() 改成 public.OfficialApiBase() |
|||
|
|||
class/ajax.py 文件 __official_url = 'https://www.aapanel.com' 改成 http://www.example.com |
|||
|
|||
class/jobs.py 文件 \#尝试升级到独立环境 下面的 public.get_url() 改成 public.OfficialApiBase() |
|||
|
|||
class/system.py 文件 RepPanel和UpdatePro方法内的 public.get_url() 改成 public.OfficialApiBase() |
|||
|
|||
- class/public/common.py |
|||
|
|||
def OfficialApiBase(): 改成 return 'http://www.example.com' |
|||
|
|||
def load_soft_list 去除 if force 部分 |
|||
|
|||
plugin_list_data = PluginLoader.get_plugin_list(0) 部分改成 plugin_list_data = PluginLoader.get_plugin_list(force) |
|||
|
|||
在 def check_domain_cloud(domain): 这一行下面加上 return |
|||
|
|||
在 def count_wp(): 这一行下面加上 return |
|||
|
|||
在 def err_collect 这一行下面加上 return |
|||
|
|||
在 def get_improvement(): 这一行下面加上 return False |
|||
|
|||
在free_login_area方法内get_free_ips_area替换成get_ips_area |
|||
|
|||
在login_send_body方法内,free_login_area(login_ip=server_ip_area的server_ip_area改成login_ip |
|||
|
|||
在 def write_request_log(reques=None): 这一行下面加上 return |
|||
|
|||
- class/panelPlugin.py 文件,set_pyenv方法内,temp_file = public.readFile(filename)这行代码下面加上 |
|||
|
|||
```python |
|||
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') |
|||
``` |
|||
|
|||
def check_status(self, softInfo): 方法最后一行加上 |
|||
|
|||
```python |
|||
if 'endtime' in softInfo: |
|||
softInfo['endtime'] = time.time() + 86400 * 3650 |
|||
``` |
|||
|
|||
- class_v2/btdockerModelV2/flush_plugin.py 文件,删除clear_hosts()一行 |
|||
|
|||
- install/install_soft.sh 在. 执行之前加入以下代码 |
|||
|
|||
```shell |
|||
sed -i "s/http:\/\/download.bt.cn\/install\/public.sh/http:\/\/www.example.com\/install\/public.sh/" $name.sh |
|||
sed -i "s/https:\/\/download.bt.cn\/install\/public.sh/http:\/\/www.example.com\/install\/public.sh/" $name.sh |
|||
``` |
|||
|
|||
- install/public.sh 用官网最新版的[public.sh](http://download.bt.cn/install/public.sh)替换,并去除最下面bt_check一行 |
|||
|
|||
- 去除无用的定时任务:task.py 文件 删除以下几行 |
|||
|
|||
"update_software_list": update_software_list, |
|||
|
|||
"check_panel_msg": check_panel_msg, |
|||
|
|||
"check_panel_auth": check_panel_auth, |
|||
|
|||
"count_ssh_logs": count_ssh_logs, |
|||
|
|||
"submit_email_statistics": submit_email_statistics, |
|||
|
|||
"submit_module_call_statistics": submit_module_call_statistics, |
|||
|
|||
"mailsys_domain_restrictions": mailsys_domain_restrictions, |
|||
|
|||
- [可选]去除各种计算题:将bt.js里面的内容复制到 BTPanel/static/vite/oldjs/public_backup.js 末尾 |
|||
|
|||
- [可选]去除创建网站自动创建的垃圾文件:在class/panelSite.py,分别删除 |
|||
|
|||
htaccess = self.sitePath + '/.htaccess' |
|||
|
|||
index = self.sitePath + '/index.html' |
|||
|
|||
doc404 = self.sitePath + '/404.html' |
|||
|
|||
这3行及分别接下来的4行代码 |
|||
|
|||
- [可选]关闭未绑定域名提示页面:在class/panelSite.py,root /www/server/nginx/html改成return 400 |
|||
|
|||
- [可选]上传文件默认选中覆盖,在BTPanel/static/vite/oldjs/upload-drog.js,id="all_operation"加checked属性 |
|||
|
|||
- [可选] BTPanel/static/vite/oldjs/site.js,优化SSL证书配置页面 |
|||
|
|||
- [可选]新版vite页面去除需求反馈、各种广告、计算题等,执行 php think cleanvitejs <面板BTPanel/static/js路径> |
|||
|
|||
- 新增简体中文语言:修改BTPanel/languages/settings.json,并增加 zh/server.json、all/zh.json |
|||
|
|||
|
|||
解压安装包[panel_7_en.zip](http://download.bt.cn/install/src/panel_7_en.zip),将更新包改好的文件覆盖到里面,然后重新打包,即可更新安装包。( |
|||
|
|||
别忘了删除class文件夹里面所有的.so文件) |
|||
|
@ -0,0 +1,408 @@ |
|||
#coding: utf-8 |
|||
# +------------------------------------------------------------------- |
|||
# | 宝塔Linux面板 |
|||
# +------------------------------------------------------------------- |
|||
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. |
|||
# +------------------------------------------------------------------- |
|||
# | Author: hwliang <hwl@bt.cn> |
|||
# +------------------------------------------------------------------- |
|||
|
|||
#+-------------------------------------------------------------------- |
|||
#| 插件和模块加载器 |
|||
#+-------------------------------------------------------------------- |
|||
|
|||
import public,os,sys,json,hashlib |
|||
|
|||
def plugin_run(plugin_name,def_name,args): |
|||
''' |
|||
@name 执行插件方法 |
|||
@param plugin_name<string> 插件名称 |
|||
@param def_name<string> 方法名称 |
|||
@param args<dict_obj> 参数对像 |
|||
@return mixed |
|||
''' |
|||
if not plugin_name or not def_name: return public.returnMsg(False,'parameter incorrect: module_name and def_name cannot be empty.') |
|||
|
|||
# 获取插件目录 |
|||
plugin_path = public.get_plugin_path(plugin_name) |
|||
is_php = os.path.exists(os.path.join(plugin_path,'index.php')) |
|||
|
|||
# 检查插件目录是否合法 |
|||
if is_php: |
|||
plugin_file = os.path.join(plugin_path,'index.php') |
|||
else: |
|||
plugin_file = os.path.join(plugin_path, plugin_name + '_main.py') |
|||
if not public.path_safe_check(plugin_file): return public.returnMsg(False,'parameter incorrect: module_name and def_name cannot contains special symbols.') |
|||
|
|||
# 检查插件入口文件是否存在 |
|||
if not os.path.exists(plugin_file): return public.returnMsg(False,'plugin not found') |
|||
|
|||
# 添加插件目录到系统路径 |
|||
public.sys_path_append(plugin_path) |
|||
|
|||
if not is_php: |
|||
# 引用插件入口文件 |
|||
_name = "{}_main".format(plugin_name) |
|||
plugin_main = __import__(_name) |
|||
|
|||
# 检查类名是否符合规范 |
|||
if not hasattr(plugin_main,_name): |
|||
return public.returnMsg(False,'plugin class name is invalid') |
|||
|
|||
try: |
|||
if sys.version_info[0] == 2: |
|||
reload(plugin_main) |
|||
else: |
|||
from imp import reload |
|||
reload(plugin_main) |
|||
except: |
|||
pass |
|||
|
|||
# 实例化插件类 |
|||
plugin_obj = getattr(plugin_main,_name)() |
|||
|
|||
# 检查方法是否存在 |
|||
if not hasattr(plugin_obj,def_name): |
|||
return public.returnMsg(False,'not find method [%s] in plugin [%s]' % (def_name,plugin_name)) |
|||
|
|||
if args is not None and 'plugin_get_object' in args and args.plugin_get_object == 1: |
|||
return getattr(plugin_obj, def_name) |
|||
|
|||
# 执行方法 |
|||
return getattr(plugin_obj,def_name)(args) |
|||
else: |
|||
if args is not None and 'plugin_get_object' in args and args.plugin_get_object == 1: |
|||
return None |
|||
import panelPHP |
|||
args.s = def_name |
|||
args.name = plugin_name |
|||
return panelPHP.panelPHP(plugin_name).exec_php_script(args) |
|||
|
|||
|
|||
def get_module_list(): |
|||
''' |
|||
@name 获取模块列表 |
|||
@return list |
|||
''' |
|||
module_list = [] |
|||
class_path = public.get_class_path() |
|||
for name in os.listdir(class_path): |
|||
path = os.path.join(class_path,name) |
|||
# 过滤无效文件 |
|||
if not name or name.endswith('.py') or name[0] == '.' or not name.endswith('Model') or os.path.isfile(path):continue |
|||
module_list.append(name) |
|||
return module_list |
|||
|
|||
def module_run(module_name,def_name,args): |
|||
''' |
|||
@name 执行模块方法 |
|||
@param module_name<string> 模块名称 |
|||
@param def_name<string> 方法名称 |
|||
@param args<dict_obj> 参数对像 |
|||
@return mixed |
|||
''' |
|||
if not module_name or not def_name: return public.returnMsg(False,'parameter incorrect: module_name and def_name cannot be empty.') |
|||
model_index = args.get('model_index',None) |
|||
class_path = public.get_class_path() |
|||
panel_path = public.get_panel_path() |
|||
|
|||
module_file = None |
|||
if 'model_index' in args: |
|||
# 新模块目录 |
|||
if model_index in ['mod']: |
|||
module_file = os.path.join(panel_path,'mod','project',module_name + 'Mod.py') |
|||
elif model_index: |
|||
# 旧模块目录 |
|||
module_file = os.path.join(class_path,model_index+"Model",module_name + 'Model.py') |
|||
else: |
|||
module_file = os.path.join(class_path,"projectModel",module_name + 'Model.py') |
|||
else: |
|||
# 如果没指定模块名称,则遍历所有模块目录 |
|||
module_list = get_module_list() |
|||
for name in module_list: |
|||
module_file = os.path.join(class_path,name,module_name + 'Model.py') |
|||
if os.path.exists(module_file): break |
|||
|
|||
# 判断模块入口文件是否存在 |
|||
if not os.path.exists(module_file): |
|||
return public.returnMsg(False,'module file [%s] not exist' % module_name) |
|||
|
|||
# 判断模块路径是否合法 |
|||
if not public.path_safe_check(module_file): |
|||
return public.returnMsg(False,'parameter incorrect: module_name and def_name cannot contains special symbols.') |
|||
|
|||
def_object = public.get_script_object(module_file) |
|||
if not def_object: return public.returnMsg(False,'module [%s] not found' % module_name) |
|||
|
|||
# 模块实例化并返回方法对象 |
|||
try: |
|||
run_object = getattr(def_object.main(),def_name,None) |
|||
except: |
|||
return public.returnMsg(False,'module [%s] failed to instance class' % module_name) |
|||
if not run_object: return public.returnMsg(False,'not found method [%s] in module [%s]' % (def_name,module_name)) |
|||
|
|||
if 'module_get_object' in args and args.module_get_object == 1: |
|||
return run_object |
|||
|
|||
# 执行方法 |
|||
result = run_object(args) |
|||
return result |
|||
|
|||
|
|||
def get_plugin_list(upgrade_force = False): |
|||
''' |
|||
@name 获取插件列表 |
|||
@param upgrade_force<bool> 是否强制重新获取列表 |
|||
@return dict |
|||
''' |
|||
|
|||
api_root_url = public.OfficialApiBase() |
|||
api_url = api_root_url+ '/api/panel/getSoftListEn' |
|||
panel_path = public.get_panel_path() |
|||
data_path = os.path.join(panel_path,'data') |
|||
|
|||
if not os.path.exists(data_path): |
|||
os.makedirs(data_path,384) |
|||
|
|||
plugin_list = {} |
|||
plugin_list_file = os.path.join(data_path,'plugin_list.json') |
|||
if os.path.exists(plugin_list_file) and not upgrade_force: |
|||
plugin_list_body = public.readFile(plugin_list_file) |
|||
try: |
|||
plugin_list = json.loads(plugin_list_body) |
|||
except: |
|||
plugin_list = {} |
|||
|
|||
if not os.path.exists(plugin_list_file) or upgrade_force or not plugin_list: |
|||
try: |
|||
res = public.HttpGet(api_url) |
|||
except Exception as ex: |
|||
raise public.error_conn_cloud(str(ex)) |
|||
if not res: raise Exception(False,'failed to get soft list') |
|||
|
|||
plugin_list = json.loads(res) |
|||
if type(plugin_list)!=dict or 'list' not in plugin_list: |
|||
if type(plugin_list)==str: |
|||
raise Exception(plugin_list) |
|||
else: |
|||
raise Exception('failed to parse soft list') |
|||
content = json.dumps(plugin_list) |
|||
public.writeFile(plugin_list_file,content) |
|||
|
|||
plugin_bin_file = os.path.join(data_path,'plugin_bin.pl') |
|||
encode_content = __encode_plugin_list(content) |
|||
if encode_content: |
|||
public.writeFile(plugin_bin_file,encode_content) |
|||
|
|||
return plugin_list |
|||
|
|||
def __encode_plugin_list(content): |
|||
try: |
|||
userInfo = public.get_user_info() |
|||
if not userInfo or 'server_id' not in userInfo: return None |
|||
block_size = 51200 |
|||
uid = str(userInfo['uid']) |
|||
server_id = userInfo['server_id'] |
|||
key = server_id[10:26] + uid + server_id |
|||
key = hashlib.md5(key.encode()).hexdigest() |
|||
iv = key + server_id |
|||
iv = hashlib.md5(iv.encode()).hexdigest() |
|||
key = key[8:24] |
|||
iv = iv[8:24] |
|||
blocks = [content[i:i + block_size] for i in range(0, len(content), block_size)] |
|||
encrypted_content = '' |
|||
for block in blocks: |
|||
encrypted_content += __aes_encrypt(block, key, iv) + '\n' |
|||
return encrypted_content |
|||
except: |
|||
pass |
|||
return None |
|||
|
|||
def get_module(filename): |
|||
if not filename: return public.returnMsg(False,'parameter error: get_module(filename<str>)') |
|||
if filename[0:2] == './': return public.returnMsg(False,'filename cannot be relative path.') |
|||
if not public.path_safe_check(filename): return public.returnMsg(False,'filename cannot contains special symbols.') |
|||
if not os.path.exists(filename): return public.returnMsg(False,'file does not exist') |
|||
return __get_class_module(filename) |
|||
|
|||
def __get_class_module(filename): |
|||
_obj = sys.modules.get(filename, None) |
|||
if _obj: return _obj |
|||
_code = public.readFile(filename) |
|||
if _code.find('import') == -1: |
|||
en_arr = _code.split('\n') |
|||
de_text = '' |
|||
for data in en_arr: |
|||
data = str.strip(data) |
|||
if not data: continue |
|||
de_text += __aes_decrypt_module(data) |
|||
_code = de_text |
|||
if not _code or _code.find('import') == -1: |
|||
return public.returnMsg(False,'load failed: decode error') |
|||
_code_object = compile(_code, filename, 'exec') |
|||
from types import ModuleType |
|||
_obj = sys.modules.setdefault(filename, ModuleType(filename)) |
|||
_obj.__file__ = filename |
|||
_obj.__package__ = '' |
|||
exec(_code_object, _obj.__dict__) |
|||
return _obj |
|||
|
|||
def start_total(): |
|||
''' |
|||
@name 启动统计服务 |
|||
@return dict |
|||
''' |
|||
pass |
|||
|
|||
def get_soft_list(args): |
|||
''' |
|||
@name 获取软件列表 |
|||
@param args<dict_obj> 参数对像 |
|||
@return dict |
|||
''' |
|||
pass |
|||
|
|||
def db_encrypt(data): |
|||
''' |
|||
@name 数据库加密 |
|||
@param args<dict_obj> 参数对像 |
|||
@return dict |
|||
''' |
|||
try: |
|||
key = __get_db_sgin() |
|||
iv = __get_db_iv() |
|||
str_arr = data.split('\n') |
|||
res_str = '' |
|||
for data in str_arr: |
|||
if not data: continue |
|||
res_str += __aes_encrypt(data, key, iv) |
|||
except: |
|||
res_str = data |
|||
result = { |
|||
'status' : True, |
|||
'msg' : res_str |
|||
} |
|||
return result |
|||
|
|||
def db_decrypt(data): |
|||
''' |
|||
@name 数据库解密 |
|||
@param args<dict_obj> 参数对像 |
|||
@return dict |
|||
''' |
|||
try: |
|||
key = __get_db_sgin() |
|||
iv = __get_db_iv() |
|||
str_arr = data.split('\n') |
|||
res_str = '' |
|||
for data in str_arr: |
|||
if not data: continue |
|||
res_str += __aes_decrypt(data, key, iv) |
|||
except: |
|||
res_str = data |
|||
result = { |
|||
'status' : True, |
|||
'msg' : res_str |
|||
} |
|||
return result |
|||
|
|||
def __get_db_sgin(): |
|||
keystr = '3gP7+k_7lSNg3$+Fj!PKW+6$KYgHtw#R' |
|||
key = '' |
|||
for i in range(31): |
|||
if i & 1 == 0: |
|||
key += keystr[i] |
|||
return key |
|||
|
|||
def __get_db_iv(): |
|||
div_file = "{}/data/div.pl".format(public.get_panel_path()) |
|||
if not os.path.exists(div_file): |
|||
str = public.GetRandomString(16) |
|||
str = __aes_encrypt_module(str) |
|||
div = public.get_div(str) |
|||
public.WriteFile(div_file, div) |
|||
if os.path.exists(div_file): |
|||
div = public.ReadFile(div_file) |
|||
div = __aes_decrypt_module(div) |
|||
else: |
|||
keystr = '4jHCpBOFzL4*piTn^-4IHBhj-OL!fGlB' |
|||
div = '' |
|||
for i in range(31): |
|||
if i & 1 == 0: |
|||
div += keystr[i] |
|||
return div |
|||
|
|||
def __aes_encrypt_module(data): |
|||
key = 'Z2B87NEAS2BkxTrh' |
|||
iv = 'WwadH66EGWpeeTT6' |
|||
return __aes_encrypt(data, key, iv) |
|||
|
|||
def __aes_decrypt_module(data): |
|||
key = 'Z2B87NEAS2BkxTrh' |
|||
iv = 'WwadH66EGWpeeTT6' |
|||
return __aes_decrypt(data, key, iv) |
|||
|
|||
def __aes_decrypt(data, key, iv): |
|||
from Crypto.Cipher import AES |
|||
import base64 |
|||
encodebytes = base64.decodebytes(data.encode('utf-8')) |
|||
aes = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) |
|||
de_text = aes.decrypt(encodebytes) |
|||
unpad = lambda s: s[0:-s[-1]] |
|||
de_text = unpad(de_text) |
|||
return de_text.decode('utf-8') |
|||
|
|||
def __aes_encrypt(data, key, iv): |
|||
from Crypto.Cipher import AES |
|||
import base64 |
|||
data = (lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode('utf-8'))(data.encode('utf-8')) |
|||
aes = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8')) |
|||
encryptedbytes = aes.encrypt(data) |
|||
en_text = base64.b64encode(encryptedbytes) |
|||
return en_text.decode('utf-8') |
|||
|
|||
def plugin_end(): |
|||
''' |
|||
@name 插件到期处理 |
|||
@return dict |
|||
''' |
|||
pass |
|||
|
|||
def daemon_task(): |
|||
''' |
|||
@name 后台任务守护 |
|||
@return dict |
|||
''' |
|||
pass |
|||
|
|||
def daemon_panel(): |
|||
''' |
|||
@name 面板守护 |
|||
@return dict |
|||
''' |
|||
pass |
|||
|
|||
def flush_auth_key(): |
|||
''' |
|||
@name 刷新授权密钥 |
|||
@return dict |
|||
''' |
|||
pass |
|||
|
|||
def get_auth_state(): |
|||
''' |
|||
@name 获取授权状态 |
|||
@return 返回:0.免费版 1.专业版 2.企业版 -1.获取失败 |
|||
''' |
|||
try: |
|||
softList = get_plugin_list() |
|||
if softList['ltd'] > -1: |
|||
return 2 |
|||
elif softList['pro'] > -1: |
|||
return 1 |
|||
else: |
|||
return 0 |
|||
except: |
|||
return -1 |
|||
|
|||
|
@ -0,0 +1,83 @@ |
|||
/* |
|||
*宝塔面板去除各种计算题与延时等待 |
|||
*/ |
|||
if("undefined" != typeof bt && bt.hasOwnProperty("show_confirm")){ |
|||
bt.show_confirm = function(title, msg, fun, error) { |
|||
layer.open({ |
|||
type: 1, |
|||
title: title, |
|||
area: "350px", |
|||
closeBtn: 2, |
|||
shadeClose: true, |
|||
btn: [lan['public'].ok, lan['public'].cancel], |
|||
content: "<div class='bt-form webDelete pd20'>\ |
|||
<p>" + msg + "</p>" + (error || '') + "\ |
|||
</div>", |
|||
yes: function (index, layero) { |
|||
layer.close(index); |
|||
if (fun) fun(); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
if("undefined" != typeof bt && bt.hasOwnProperty("prompt_confirm")){ |
|||
bt.prompt_confirm = function (title, msg, callback) { |
|||
layer.open({ |
|||
type: 1, |
|||
title: title, |
|||
area: "480px", |
|||
closeBtn: 2, |
|||
btn: ['OK', 'Cancel'], |
|||
content: "<div class='bt-form promptDelete pd20'>\ |
|||
<p>" + msg + "</p>\ |
|||
</div>", |
|||
yes: function (layers, index) { |
|||
layer.close(layers) |
|||
if (callback) callback() |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
if("undefined" != typeof bt && bt.hasOwnProperty("compute_confirm")){ |
|||
bt.compute_confirm = function (config, callback) { |
|||
layer.open({ |
|||
type: 1, |
|||
title: config.title, |
|||
area: '430px', |
|||
closeBtn: 2, |
|||
shadeClose: true, |
|||
btn: [lan['public'].ok, lan['public'].cancel], |
|||
content: |
|||
'<div class="bt-form hint_confirm pd30">\ |
|||
<div class="hint_title">\ |
|||
<i class="hint-confirm-icon"></i>\ |
|||
<div class="hint_con">' + |
|||
config.msg + |
|||
'</div>\ |
|||
</div>\ |
|||
</div>', |
|||
yes: function (layers, index) { |
|||
layer.close(layers) |
|||
if (callback) callback() |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
function SafeMessage(j, h, g, f) { |
|||
if (f == undefined) f = ''; |
|||
var mess = layer.open({ |
|||
type: 1, |
|||
title: j, |
|||
area: "350px", |
|||
closeBtn: 2, |
|||
shadeClose: true, |
|||
content: "<div class='bt-form webDelete pd20 pb70'><p>" + h + "</p>" + f + "<div class='bt-form-submit-btn'><button type='button' class='btn btn-danger btn-sm bt-cancel'>"+lan.public.cancel+"</button> <button type='button' id='toSubmit' class='btn btn-success btn-sm' >"+lan.public.ok+"</button></div></div>" |
|||
}); |
|||
$(".bt-cancel").click(function(){ |
|||
layer.close(mess); |
|||
}); |
|||
$("#toSubmit").click(function() { |
|||
layer.close(mess); |
|||
g(); |
|||
}) |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue