Browse Source

update

tags/3.0
flucout 2 months ago
parent
commit
c6a9fccc1c
  1. 130
      README.md
  2. 52
      app/command/CleanViteJs.php
  3. 66
      app/command/DecryptFile.php
  4. 8
      app/common.php
  5. 17
      app/controller/Admin.php
  6. 78
      app/controller/Api.php
  7. 11
      app/lib/BtPlugins.php
  8. 49
      app/lib/Plugins.php
  9. 16
      app/lib/ThirdPlugins.php
  10. 6
      app/script/convert.sh
  11. 3
      app/view/admin/layout.html
  12. 276
      app/view/admin/pluginsen.html
  13. 124
      app/view/admin/set.html
  14. 56
      app/view/index/download.html
  15. 3
      install.sql
  16. 2
      public/install/install_6.0.sh
  17. 2027
      public/install/install_7.0_en.sh
  18. 2033
      public/install/install_pro_en.sh
  19. BIN
      public/install/src/panel6.zip
  20. BIN
      public/install/src/panel_7_en.zip
  21. BIN
      public/install/update/LinuxPanel-9.3.0.zip
  22. BIN
      public/install/update/LinuxPanel_EN-7.0.13.zip
  23. 2
      public/install/update6.sh
  24. 1017
      public/install/update_7.x_en.sh
  25. BIN
      public/static/file/en/kaixin.zip
  26. BIN
      public/static/file/kaixin.zip
  27. BIN
      public/static/images/aapanel.png
  28. 17
      route/app.php
  29. 122
      wiki/aapanel.md
  30. 408
      wiki/files/aapanel/PluginLoader.py
  31. 83
      wiki/files/aapanel/bt.js

130
README.md

@ -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文件,因此无系统架构限制 |

52
app/command/CleanViteJs.php

@ -236,6 +236,58 @@ class CleanViteJs extends Command
$file = str_replace('暂无搜索结果,<span class="text-primary cursor-pointer NpsDialog">提交需求反馈</span>', '暂无搜索结果', $file);
$flag = true;
}
if(strpos($file, 'getReceiveCoupon()')!==false){ //aapanel-优惠券
$code = $this->getExtendCode($file, 'getReceiveCoupon()');
$file = str_replace($code, '{}', $file);
$flag = true;
}
if(strpos($file, '"Site.DelSite.index_1"')!==false){ //aapanel-site
$code = $this->getExtendCode($file, '"Site.DelSite.index_10"', 3, '(', ')');
if($code){
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$file = preg_replace('@\w+\.value!==\w+\.value\+\w+\.value@', '!1', $file);
$file = preg_replace('@null==\w+\.value\|\|null==\w+\.value@', '!1', $file);
$file = str_replace('disabled:!0', 'disabled:!1', $file);
$flag = true;
}
}
if(strpos($file, '"Component.Confirm.index_4"')!==false){ //aapanel-public
$code = $this->getExtendCode($file, '"Component.Confirm.index_4"', 2, '(', ')');
if($code){
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$file = preg_replace('@\w+\.value===\w+\.value\+\w+\.value@', '!0', $file);
$flag = true;
}
$code = $this->getExtendCode($file, '"Component.Confirm.index_1"', 1, '(', ')');
if($code){
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$file = preg_replace('@\w+\.value===\w+\.value\?@', '!0?', $file);
$flag = true;
}
}
if(strpos($file, '"Component.Feedback.index_7"')!==false){ //aapanel-需求反馈
$code = $this->getExtendCode($file, '"Component.Feedback.index_7"', 2);
if($code){
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$flag = true;
}
}
if(strpos($file, '"Soft.index_16"')!==false){ //aapanel-soft
$code = $this->getExtendCode($file, '"Soft.index_16"', 2);
if($code){
$file = str_replace($code, '{}', $file);
$flag = true;
}
}
if(!$flag) return;
if(file_put_contents($filepath, $file)){

66
app/command/DecryptFile.php

@ -45,37 +45,34 @@ class DecryptFile extends Command
$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());
}
$this->decode_module_file($output, $file);
}elseif($type == 'classdir'){
$file = rtrim($file, '/');
if(!file_exists($file.'/common.py')){
if(file_exists($file.'/common.py')){
$class_v = 1;
}elseif(file_exists($file.'/common_v2.py')){
$class_v = 2;
}else{
$output->writeln('当前路径非宝塔面板class目录');
return;
}
$dirs = glob($file.'/*Model');
$dirs = glob($file.'/*Model'.($class_v == 2 ? 'V2' : ''));
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);
}
foreach($files as $filepath){
$this->decode_module_file($output, $filepath);
}
}
if($class_v == 2){
$filepath = $file.'/wp_toolkit/core.py';
if(file_exists($filepath)){
$this->decode_module_file($output, $filepath);
}
}else{
$filepath = $file.'/public/authorization.py';
if(file_exists($filepath)){
$this->decode_module_file($output, $filepath);
}
}
}elseif($type == 'all'){
@ -95,21 +92,24 @@ class DecryptFile extends Command
$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);
}
$this->decode_module_file($output, $filepath);
}
}
}
closedir($dir);
}
private function decode_module_file(Output $output, $filepath){
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);
}
}
}

8
app/common.php

@ -3,7 +3,13 @@
use think\facade\Db;
function get_data_dir($os = 'Linux'){
return app()->getRootPath().'data/'.($os == 'Windows' ? 'win/' : '');
if($os == 'en'){
return app()->getRootPath().'data/en/';
}elseif($os == 'Windows'){
return app()->getRootPath().'data/win/';
}else{
return app()->getRootPath().'data/';
}
}

17
app/controller/Admin.php

@ -151,7 +151,7 @@ class Admin extends BaseController
if($result && isset($result['username'])){
return json(['code'=>0, 'msg'=>'面板连接测试成功!']);
}else{
return json(['code'=>-1, 'msg'=>'面板连接测试成功,但未安装专用插件']);
return json(['code'=>-1, 'msg'=>'面板连接测试成功,但未安装专用插件/未登录账号']);
}
}else{
return json(['code'=>-1, 'msg'=>isset($result['msg'])?$result['msg']:'面板地址无法连接']);
@ -185,6 +185,19 @@ class Admin extends BaseController
return view();
}
public function pluginsen(){
$typelist = [];
$json_arr = Plugins::get_plugin_list('en');
if($json_arr){
foreach($json_arr['type'] as $type){
if($type['title'] == '一键部署') continue;
$typelist[$type['id']] = $type['title'];
}
}
View::assign('typelist', $typelist);
return view();
}
public function plugins_data(){
$type = input('post.type/d');
$keyword = input('post.keyword', null, 'trim');
@ -226,7 +239,7 @@ class Admin extends BaseController
'name' => $plugin['name'],
'title' => $plugin['title'],
'type' => $plugin['type'],
'typename' => $typelist[$plugin['type']],
'typename' => isset($typelist[$plugin['type']]) ? $typelist[$plugin['type']] : '未知',
'desc' => str_replace('target="_blank"','target="_blank" rel="noopener noreferrer"',$plugin['ps']),
'price' => $plugin['price'],
'author' => isset($plugin['author']) ? $plugin['author'] : '官方',

78
app/controller/Api.php

@ -36,6 +36,20 @@ class Api extends BaseController
return json($json_arr);
}
//获取插件列表(aapanel)
public function get_plugin_list_en(){
if(!$this->checklist()) return json('你的服务器被禁止使用此云端');
$record = Db::name('record')->where('ip',$this->clientip)->find();
if($record){
Db::name('record')->where('id',$record['id'])->update(['usetime'=>date("Y-m-d H:i:s")]);
}else{
Db::name('record')->insert(['ip'=>$this->clientip, 'addtime'=>date("Y-m-d H:i:s"), 'usetime'=>date("Y-m-d H:i:s")]);
}
$json_arr = Plugins::get_plugin_list('en');
if(!$json_arr) return json((object)[]);
return json($json_arr);
}
//下载插件包
public function download_plugin(){
$plugin_name = input('post.name');
@ -58,6 +72,26 @@ class Api extends BaseController
}
}
//下载插件包aapanel
public function download_plugin_en(){
$plugin_name = input('post.name');
$version = input('post.version');
if(!$plugin_name || !$version){
return '参数不能为空';
}
if(!preg_match('/^[a-zA-Z0-9_]+$/', $plugin_name) || !preg_match('/^[0-9.]+$/', $version)){
return '参数不正确';
}
if(!$this->checklist()) return '你的服务器被禁止使用此云端';
$filepath = get_data_dir('en').'plugins/package/'.$plugin_name.'-'.$version.'.zip';
if(file_exists($filepath)){
$filename = $plugin_name.'.zip';
$this->output_file($filepath, $filename);
}else{
return '云端不存在该插件包';
}
}
//下载插件主文件
public function download_plugin_main(){
$plugin_name = input('post.name');
@ -92,7 +126,10 @@ class Api extends BaseController
public function download_plugin_other(){
$fname = input('get.fname');
if(!$fname){
return json(['status'=>false, 'msg'=>'参数不能为空']);
$fname = input('get.filename');
if(!$fname){
return json(['status'=>false, 'msg'=>'参数不能为空']);
}
}
if(strpos(dirname($fname),'.')!==false)return json(['status'=>false, 'msg'=>'参数不正确']);
if(!$this->checklist()) return json(['status'=>false, 'msg'=>'你的服务器被禁止使用此云端']);
@ -139,6 +176,11 @@ class Api extends BaseController
return $version;
}
public function get_version_en(){
$version = config_get('new_version_en');
return $version;
}
public function get_panel_version(){
$version = config_get('new_version');
$file = app()->getRootPath().'public/install/update/LinuxPanel-'.$version.'.zip';
@ -205,6 +247,28 @@ class Api extends BaseController
return json($data);
}
//检测更新(aapanel)
public function check_update_en(){
$version = config_get('new_version_en');
$down_url = request()->root(true).'/install/update/LinuxPanel_EN-'.$version.'.zip';
$data = [
'force' => false,
'version' => $version,
'downUrl' => $down_url,
'updateMsg' => config_get('update_msg_en'),
'uptime' => config_get('update_date_en'),
'is_beta' => 0,
'btb' => '',
'beta' => [
'version' => $version,
'downUrl' => $down_url,
'updateMsg' => config_get('update_msg_en'),
'uptime' => config_get('update_date_en'),
]
];
return json($data);
}
//宝塔云监控获取最新版本
public function btm_latest_version(){
$data = [
@ -410,6 +474,18 @@ class Api extends BaseController
return json(['page'=>"<div><span class='Pcurrent'>1</span><span class='Pnumber'>1/0</span><span class='Pline'>从1-1000条</span><span class='Pcount'>共计0条数据</span></div>", 'data'=>[]]);
}
public function nps_check(){
return json(['err_no'=>0, 'success'=>true, 'res'=>true, 'nonce'=>time()]);
}
public function nps_questions(){
return json(['err_no'=>0, 'success'=>true, 'res'=>[], 'nonce'=>time()]);
}
public function nps_submit(){
return json(['err_no'=>0, 'success'=>true, 'res'=>'Success', 'nonce'=>time()]);
}
//获取所有蜘蛛IP列表
public function btwaf_getspiders(){
try{

11
app/lib/BtPlugins.php

@ -15,7 +15,10 @@ class BtPlugins
public function __construct($os){
$this->os = $os;
if($os == 'Windows'){
if($os == 'en'){
$bt_url = config_get('enbt_url');
$bt_key = config_get('enbt_key');
}elseif($os == 'Windows'){
$bt_url = config_get('wbt_url');
$bt_key = config_get('wbt_key');
}else{
@ -157,9 +160,9 @@ class BtPlugins
$de_text = '';
foreach($data_arr as $data){
$data = trim($data);
if(!empty($data) && strlen($data)!=24){
if(!empty($data)){
$tmp = openssl_decrypt($data, 'aes-128-cbc', $key, 0, $iv);
if($tmp) $de_text .= $tmp;
if($tmp !== false) $de_text .= $tmp;
}
}
if(!empty($de_text) && strpos($de_text, 'import ')!==false){
@ -202,6 +205,8 @@ class BtPlugins
$data = str_replace('\'http://www.bt.cn/api/Auth', 'public.GetConfigValue(\'home\')+\'/api/Auth', $data);
$data = str_replace('\'https://www.bt.cn/api/Auth', 'public.GetConfigValue(\'home\')+\'/api/Auth', $data);
$data = str_replace('\'https://brandnew.aapanel.com/api/panel/getSoftList', 'public.OfficialApiBase()+\'/api/panel/getSoftList', $data);
file_put_contents($main_filepath, $data);
}

49
app/lib/Plugins.php

@ -17,7 +17,13 @@ class Plugins
}
private static function is_third($os){
$type = $os == 'Windows' ? config_get('wbt_type') : config_get('bt_type');
if($os == 'en'){
$type = config_get('enbt_type');
}elseif($os == 'Windows'){
$type = config_get('wbt_type');
}else{
$type = config_get('bt_type');
}
return $type == 1;
}
@ -31,23 +37,44 @@ class Plugins
//保存插件列表
private static function save_plugin_list($data, $os){
$data['ip'] = '127.0.0.1';
$data['serverid'] = '';
$data['beta'] = 0;
$data['uid'] = 1;
$data['skey'] = '';
$list = [];
foreach($data['list'] as $plugin){
if($os == 'en'){
$data['serverId'] = '';
$data['aln'] = self::get_aln();
$data['pro'] = 0;
$data['pro_authorization_sn'] = '0';
if(!empty($data['authorization_map'])){
foreach($data['authorization_map'] as $code => &$plugin){
if($code != '0' && isset($plugin['end_time'])) $plugin['end_time'] = 0;
}
}
if(isset($data['expansions']['mail'])){
$data['expansions']['mail']['total'] = 2000000;
$data['expansions']['mail']['available'] = 2000000;
}
}else{
$data['serverid'] = '';
$data['aln'] = self::get_aln();
$data['beta'] = 0;
$data['uid'] = 1;
$data['skey'] = '';
$data['ltd'] = strtotime('+10 year');
}
foreach($data['list'] as &$plugin){
if(isset($plugin['endtime'])) $plugin['endtime'] = 0;
$list[] = $plugin;
}
$data['list'] = $list;
$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('保存插件列表失败,文件无写入权限');
}
}
//多账号数量
private static function get_aln($count = '9999'){
$key = 'FB8upo8XMgP5by54';
$iv = 'lOrrq3lNEURZNdK7';
return openssl_encrypt($count, 'aes-128-cbc', $key, 0, $iv);
}
//获取插件列表
public static function get_plugin_list($os = 'Linux'){
$json_file = get_data_dir($os).'config/plugin_list.json';
@ -106,7 +133,7 @@ class Plugins
$data = trim($data);
if(!empty($data)){
$tmp = openssl_decrypt($data, 'aes-128-cbc', $key, 0, $iv);
if($tmp) $de_text .= $tmp;
if($tmp !== false) $de_text .= $tmp;
}
}
if(!empty($de_text) && strpos($de_text, 'import ')!==false){

16
app/lib/ThirdPlugins.php

@ -13,7 +13,13 @@ class ThirdPlugins
public function __construct($os)
{
$this->os = $os;
$url = $os == 'Windows' ? config_get('wbt_surl') : config_get('bt_surl');
if($os == 'en'){
$url = config_get('enbt_surl');
}elseif($os == 'Windows'){
$url = config_get('wbt_surl');
}else{
$url = config_get('bt_surl');
}
if(!$url) throw new Exception('请先配置好第三方云端首页URL');
$this->url = $url;
}
@ -21,7 +27,13 @@ class ThirdPlugins
//获取插件列表
public function get_plugin_list()
{
$url = $this->os == 'Windows' ? $this->url . 'api/wpanel/get_soft_list' : $this->url . 'api/panel/get_soft_list';
if($this->os == 'en'){
$url = $this->url . 'api/panel/get_plugin_list_en';
}elseif($this->os == 'Windows'){
$url = $this->url . 'api/wpanel/get_plugin_list';
}else{
$url = $this->url . 'api/panel/get_plugin_list';
}
$res = $this->curl($url);
$result = json_decode($res, true);
if($result && isset($result['list']) && isset($result['type'])){

6
app/script/convert.sh

@ -2,6 +2,7 @@
Linux_Version="9.3.0"
Windows_Version="8.2.1"
Aapanel_Version="7.0.13"
Btm_Version="2.3.0"
FILES=(
@ -17,6 +18,11 @@ public/win/panel/data/setup.py
public/install/src/bt-monitor-${Btm_Version}.zip
public/install/install_btmonitor.sh
public/install/update_btmonitor.sh
public/install/src/panel_7_en.zip
public/install/update/LinuxPanel_EN-${Aapanel_Version}.zip
public/install/install_7.0_en.sh
public/install/install_pro_en.sh
public/install/update_7.x_en.sh
)
DIR=$1

3
app/view/admin/layout.html

@ -34,11 +34,12 @@
<li class="{:checkIfActive('index')}">
<a href="/admin"><i class="fa fa-home"></i> 后台首页</a>
</li>
<li class="{:checkIfActive('plugins,pluginswin,deplist')}">
<li class="{:checkIfActive('plugins,pluginswin,pluginsen,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('pluginsen')}"><a href="/admin/pluginsen">aaPanel面板</a></li>
<li class="{:checkIfActive('deplist')}"><a href="/admin/deplist">一键部署列表</a></li>
</ul>
</li>

276
app/view/admin/pluginsen.html

@ -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">&times;</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>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i> 重置</a>&nbsp;
<a href="javascript:refresh_plugins()" class="btn btn-success"><i class="fa fa-refresh"></i> 刷新列表</a>&nbsp;
<a href="javascript:download_plugins()" class="btn btn-warning" id="batch_down" style="display:none;"><i class="fa fa-download"></i> 批量下载</a>&nbsp;
<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>&nbsp;';
else
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-danger">'+item.version+'</a>&nbsp;';
})
}
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}

124
app/view/admin/set.html

@ -49,8 +49,6 @@
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">Windows面板版本设置</h3></div>
<div class="panel-body">
@ -76,6 +74,8 @@
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">云监控版本设置</h3></div>
<div class="panel-body">
@ -99,6 +99,31 @@
</form>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">aaPanel面板版本设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>宝塔Linux面板最新版本号:</label>
<input type="text" name="new_version_en" value="{:config_get('new_version_en')}" class="form-control"/>
<font color="green">用于一键更新脚本获取最新版本号,以及检测更新接口。并确保已在/public/install/update/放置对应版本更新包</font>
</div>
<div class="form-group">
<label>宝塔Linux面板更新日志:</label>
<textarea class="form-control" name="update_msg_en" rows="5" placeholder="支持HTML代码">{:config_get('update_msg_en')}</textarea>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group">
<label>宝塔Linux面板更新日期:</label>
<input type="date" name="update_date_en" value="{:config_get('update_date_en')}" class="form-control"/>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
{elseif $mod=='api'}
<div class="col-sm-12 col-md-6 center-block">
@ -175,6 +200,43 @@
</div>
</div>
</div>
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">aaPanel面板接口设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>对接方式:</label><br/>
<select class="form-control" name="enbt_type" default="{:config_get('enbt_type')}"><option value="0">对接aaPanel面板接口</option><option value="1">对接其他第三方云端</option></select>
</div><hr/>
<div id="enbt_type_0" style="{if config_get('enbt_type')==1}display:none;{/if}">
<p>以下aaPanel面板请使用官方最新脚本安装并绑定账号,用于获取插件列表及插件包</p>
<p><a href="/static/file/en/kaixin.zip">下载专用插件(aaPanel)</a>,在面板【软件商店】->【第三方应用】,点击【导入插件】,导入该专用插件。</p>
<div class="form-group">
<label>aaPanel面板URL:</label><br/>
<input type="text" name="enbt_url" value="{:config_get('enbt_url')}" class="form-control"/>
<font color="green">填写规则如:<u>http://192.168.1.1:8888</u> ,不要带其他后缀</font>
</div>
<div class="form-group">
<label>aaPanel面板接口密钥:</label>
<input type="text" name="enbt_key" value="{:config_get('enbt_key')}" class="form-control"/>
</div>
</div>
<div id="enbt_type_1" style="{if !config_get('enbt_type')}display:none;{/if}">
<div class="form-group">
<label>第三方云端首页URL:</label><br/>
<input type="text" name="enbt_surl" value="{:config_get('enbt_surl')}" class="form-control"/>
<font color="green">填写规则如:<u>http://www.example.com/</u> ,必须以/结尾</font>
</div>
</div>
<div class="form-group text-center">
<button type="button" class="btn btn-info btn-block" id="testbturl3">测试连接</button>
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
<script>
$("select[name='bt_type']").change(function(){
if($(this).val() == 1){
@ -194,6 +256,15 @@ $("select[name='wbt_type']").change(function(){
$("#wbt_type_1").hide();
}
});
$("select[name='enbt_type']").change(function(){
if($(this).val() == 1){
$("#enbt_type_0").hide();
$("#enbt_type_1").show();
}else{
$("#enbt_type_0").show();
$("#enbt_type_1").hide();
}
});
</script>
{elseif $mod=='task'}
<div class="col-sm-12 col-md-6 center-block">
@ -222,6 +293,10 @@ $("select[name='wbt_type']").change(function(){
<label>Windows面板批量下载插件范围:</label><br/>
<select class="form-control" name="updateall_type_win" default="{:config_get('updateall_type_win')}"><option value="0">仅免费插件</option><option value="1">免费插件+专业版插件</option><option value="2">免费插件+专业版插件+企业版插件</option></select><font color="green">(批量下载不包含所有第三方插件,第三方插件需要去手动下载。)</font>
</div>
<div class="form-group">
<label>aaPanel面板批量下载插件范围:</label><br/>
<select class="form-control" name="updateall_type_en" default="{:config_get('updateall_type_en')}"><option value="0">仅免费插件</option><option value="1">免费插件+专业版插件</option><option value="2">免费插件+专业版插件+企业版插件</option></select><font color="green">(批量下载不包含所有第三方插件,第三方插件需要去手动下载。)</font>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
@ -376,6 +451,51 @@ $(document).ready(function(){
}
});
})
$("#testbturl3").click(function(){
var enbt_type = $("select[name=enbt_type]").val();
if(enbt_type == '1'){
var enbt_surl = $("input[name=enbt_surl]").val();
if(enbt_surl == ''){
layer.alert('第三方云端URL不能为空');return;
}
if(enbt_surl.indexOf('http://')==-1 && enbt_surl.indexOf('https://')==-1){
layer.alert('第三方云端URL不正确');return;
}
var postdata = {bt_type:enbt_type, bt_surl:enbt_surl};
}else{
var enbt_url = $("input[name=enbt_url]").val();
var enbt_key = $("input[name=enbt_key]").val();
if(enbt_url == ''){
layer.alert('宝塔面板URL不能为空');return;
}
if(enbt_url.indexOf('http://')==-1 && enbt_url.indexOf('https://')==-1){
layer.alert('宝塔面板URL不正确');return;
}
if(enbt_key == ''){
layer.alert('宝塔面板接口密钥不能为空');return;
}
var postdata = {bt_type:enbt_type, bt_url:enbt_url, bt_key:enbt_key};
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/testbturl',
data : postdata,
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.msg(data.msg, {icon: 1, time:1000})
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
})
})
function saveSetting(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});

56
app/view/index/download.html

@ -43,7 +43,7 @@
</div>
</div>
</div>
<div class="install-box windows">
{if config_get('new_version_win')}<div class="install-box windows">
<div class="img">
<img src="/static/images/prd_2_03.png">
</div>
@ -60,10 +60,10 @@
<a class="btn" href="javascript:;" id="goInstallWindows">查看安装方法</a>
</div>
</div>
</div>
</div>{/if}
</div>
{if config_get('new_version_btm')}<div class="disflex flex_lrcenter mt_30 install-list">
<div class="install-box monitor">
{if config_get('new_version_btm') || config_get('new_version_en')}<div class="disflex flex_lrcenter mt_30 install-list">
{if config_get('new_version_win')}<div class="install-box monitor">
<div class="img">
<img src="/static/images/bt_monitor.png" style="height: 96px;"/>
</div>
@ -76,7 +76,21 @@
<a class="btn" href="javascript:;" id="goInstallMonitor">查看安装脚本</a>
</div>
</div>
</div>
</div>{/if}
{if config_get('new_version_en')}<div class="install-box monitor">
<div class="img">
<img src="/static/images/aapanel.png" style="height: 96px;"/>
</div>
<div class="cont">
<div class="top">
<div class="title">aaPanel {:config_get('new_version_en')}</div>
<div class="desc">宝塔面板国际版</div>
</div>
<div class="bottom">
<a class="btn" href="javascript:;" id="goInstallAaPanel">查看安装脚本</a>
</div>
</div>
</div>{/if}
</div>{/if}
</div>
</div>
@ -121,6 +135,7 @@
</div>
</div>
</div>
{if config_get('new_version_win')}
<div class="d4" id="instal-windows" style="background-color: #edf6ef;">
<div class="wrap">
<div class="wrap-title">
@ -140,7 +155,7 @@
<p>注意:仅支持Windows Server 2008 R2/2012/2016/2019/2022,64位系统(中文简体),且未安装其它环境</p>
</div>
</div>
</div>
</div>{/if}
{if config_get('new_version_btm')}
<div class="d4" id="instal-monitor">
<div class="wrap">
@ -171,6 +186,31 @@
</div>
</div>
</div>{/if}
{if config_get('new_version_en')}
<div class="d4" id="instal-aapanel" style="background-color: #edf6ef;">
<div class="wrap">
<div class="wrap-title">
<div class="text">aaPanel {:config_get('new_version_en')} install script</div>
</div>
<div class="desc">
<p>It is recommended that you use Ubuntu22.04 to install aaPanel</p>
</div>
<div class="install-code">
<span class="osname">Instarll script</span>
<div class="code-cont">
<div class="command" title="Copy successfully, please paste it to the server installation">URL={$siteurl}/script/install_pro_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO $URL ;else wget -O install_pro_en.sh $URL;fi;bash install_pro_en.sh</div>
<span class="ico-copy" title="Copy successfully, please paste it to the server installation" data-clipboard-text="URL={$siteurl}/script/install_pro_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO $URL ;else wget -O install_pro_en.sh $URL;fi;bash install_pro_en.sh">复制</span>
</div>
</div>
<div class="install-code">
<span class="osname">Upgrade script</span>
<div class="code-cont">
<div class="command" title="Copy successfully, please paste it to the server installation">curl {$siteurl}/install/update_7.x_en.sh|bash</div>
<span class="ico-copy" title="Copy successfully, please paste it to the server installation" data-clipboard-text="curl {$siteurl}/install/update_7.x_en.sh|bash">复制</span>
</div>
</div>
</div>
</div>{/if}
<div class="animate-bg"></div>
</div>
@ -236,6 +276,10 @@
scrollTop('#instal-monitor');
});
$('#goInstallAaPanel').click(function () {
scrollTop('#instal-aapanel');
});
function GetRequest() {
var url = location.search;
//获取url中"?"符后的字串

3
install.sql

@ -18,6 +18,9 @@ INSERT INTO `cloud_config` (`key`, `value`) VALUES
('new_version_win', '8.2.1'),
('update_msg_win', '暂无更新日志'),
('update_date_win', '2024-12-06'),
('new_version_en', '7.0.13'),
('update_msg_en', '暂无更新日志'),
('update_date_en', '2024-11-17'),
('new_version_btm', '2.3.0'),
('update_msg_btm', '暂无更新日志'),
('update_date_btm', '2024-04-24'),

2
public/install/install_6.0.sh

@ -931,7 +931,7 @@ Install_Bt(){
echo "True" > /www/server/panel/data/not_workorder.pl
fi
if [ ! -f /www/server/panel/data/userInfo.json ]; then
echo "{\"uid\":1,\"username\":\"Administrator\",\"address\":\"127.0.0.1\",\"serverid\":\"1\",\"access_key\":\"test\",\"secret_key\":\"123456\",\"ukey\":\"123456\",\"state\":1}" > /www/server/panel/data/userInfo.json
echo "{\"uid\":1,\"username\":\"Administrator\",\"address\":\"127.0.0.1\",\"access_key\":\"test\",\"secret_key\":\"123456\",\"ukey\":\"123456\",\"state\":1}" > /www/server/panel/data/userInfo.json
fi
if [ ! -f /www/server/panel/data/panel_nps.pl ]; then
echo "" > /www/server/panel/data/panel_nps.pl

2027
public/install/install_7.0_en.sh
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

BIN
public/install/src/panel6.zip

BIN
public/install/src/panel_7_en.zip

BIN
public/install/update/LinuxPanel-9.3.0.zip

BIN
public/install/update/LinuxPanel_EN-7.0.13.zip

2
public/install/update6.sh

@ -254,7 +254,7 @@ if [ ! -f /www/server/panel/data/not_workorder.pl ]; then
echo "True" > /www/server/panel/data/not_workorder.pl
fi
if [ ! -f /www/server/panel/data/userInfo.json ]; then
echo "{\"uid\":1,\"username\":\"Administrator\",\"address\":\"127.0.0.1\",\"serverid\":\"1\",\"access_key\":\"test\",\"secret_key\":\"123456\",\"ukey\":\"123456\",\"state\":1}" > /www/server/panel/data/userInfo.json
echo "{\"uid\":1,\"username\":\"Administrator\",\"address\":\"127.0.0.1\",\"access_key\":\"test\",\"secret_key\":\"123456\",\"ukey\":\"123456\",\"state\":1}" > /www/server/panel/data/userInfo.json
fi
if [ ! -f /www/server/panel/data/panel_nps.pl ]; then
echo "" > /www/server/panel/data/panel_nps.pl

1017
public/install/update_7.x_en.sh
File diff suppressed because it is too large
View File

BIN
public/static/file/en/kaixin.zip

BIN
public/static/file/kaixin.zip

BIN
public/static/images/aapanel.png

After

Width: 1920  |  Height: 1350  |  Size: 94 KiB

17
route/app.php

@ -127,6 +127,22 @@ Route::group('api', function () {
Route::post('/Cert_cloud_deploy/get_cert_list', 'api/return_success');
Route::post('/Cert_cloud_deploy/del_cert', 'api/return_success');
Route::any('/panel/getSoftList', 'api/get_plugin_list_en');
Route::any('/panel/getSoftListEn', 'api/get_plugin_list_en');
Route::post('/panel/download_plugin', 'api/download_plugin_en');
Route::get('/plugin/download', 'api/download_plugin_other');
Route::get('/common/getClientIP', 'api/get_ip_address');
Route::post('/panel/checkDomain', 'api/check_domain');
Route::get('/panel/getBetaVersionLogs', 'api/get_beta_logs');
Route::any('/panel/updateLinuxEn', 'api/check_update_en');
Route::post('/user/verifyToken', 'api/return_success');
Route::post('/panel/nps/check', 'api/nps_check');
Route::post('/panel/nps/questions', 'api/nps_questions');
Route::post('/panel/nps/submit', 'api/nps_submit');
Route::post('/panel/submit_feature_invoked_bulk', 'api/return_success');
Route::post('/panel/submit_expand_pack_used', 'api/return_success');
Route::get('/panel/getLatestOfficialVersion', 'api/get_version_en');
Route::miss('api/return_error');
});
@ -141,6 +157,7 @@ Route::group('admin', function () {
Route::post('/testbturl', 'admin/testbturl');
Route::get('/plugins', 'admin/plugins');
Route::get('/pluginswin', 'admin/pluginswin');
Route::get('/pluginsen', 'admin/pluginsen');
Route::post('/plugins_data', 'admin/plugins_data');
Route::post('/download_plugin', 'admin/download_plugin');
Route::get('/refresh_plugins', 'admin/refresh_plugins');

122
wiki/aapanel.md

@ -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文件)

408
wiki/files/aapanel/PluginLoader.py

@ -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

83
wiki/files/aapanel/bt.js

@ -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();
})
}
Loading…
Cancel
Save