Browse Source

update

tags/1.8.9
flucont 1 year ago
commit
aa293b66a9
  1. 18
      .env.example
  2. 4
      .gitignore
  3. 21
      LICENSE
  4. 56
      README.md
  5. 1
      app/.htaccess
  6. 22
      app/AppService.php
  7. 115
      app/BaseController.php
  8. 58
      app/ExceptionHandle.php
  9. 8
      app/Request.php
  10. 115
      app/command/DecryptFile.php
  11. 118
      app/command/UpdateAll.php
  12. 187
      app/common.php
  13. 388
      app/controller/Admin.php
  14. 387
      app/controller/Api.php
  15. 24
      app/controller/Index.php
  16. 83
      app/controller/Install.php
  17. 17
      app/event.php
  18. 230
      app/lib/Btapi.php
  19. 347
      app/lib/Plugins.php
  20. 12
      app/middleware.php
  21. 26
      app/middleware/AuthAdmin.php
  22. 19
      app/middleware/CheckAdmin.php
  23. 39
      app/middleware/LoadConfig.php
  24. 24
      app/middleware/RefererCheck.php
  25. 9
      app/provider.php
  26. 80
      app/script/convert.sh
  27. 9
      app/service.php
  28. 49
      app/view/admin/deplist.html
  29. 60
      app/view/admin/index.html
  30. 73
      app/view/admin/layout.html
  31. 209
      app/view/admin/list.html
  32. 74
      app/view/admin/log.html
  33. 100
      app/view/admin/login.html
  34. 214
      app/view/admin/plugins.html
  35. 214
      app/view/admin/pluginswin.html
  36. 67
      app/view/admin/record.html
  37. 335
      app/view/admin/set.html
  38. 60
      app/view/dispatch_jump.html
  39. 261
      app/view/index/download.html
  40. 268
      app/view/install/index.html
  41. 41
      composer.json
  42. 32
      config/app.php
  43. 40
      config/cache.php
  44. 39
      config/captcha.php
  45. 11
      config/console.php
  46. 20
      config/cookie.php
  47. 63
      config/database.php
  48. 24
      config/filesystem.php
  49. 27
      config/lang.php
  50. 45
      config/log.php
  51. 8
      config/middleware.php
  52. 45
      config/route.php
  53. 19
      config/session.php
  54. 10
      config/trace.php
  55. 25
      config/view.php
  56. 1
      data/config/deployment_list.json
  57. 1
      data/config/plugin_list.json
  58. 0
      data/plugins/folder/.gitkeep
  59. 0
      data/plugins/main/.gitkeep
  60. 0
      data/plugins/other/other/.gitkeep
  61. 0
      data/plugins/package/.gitkeep
  62. 1
      data/win/config/deployment_list.json
  63. 1
      data/win/config/plugin_list.json
  64. 0
      data/win/plugins/folder/.gitkeep
  65. 0
      data/win/plugins/main/.gitkeep
  66. 0
      data/win/plugins/package/.gitkeep
  67. 66
      install.sql
  68. 8
      public/.htaccess
  69. 24
      public/index.php
  70. 1024
      public/install/install_6.0.sh
  71. 880
      public/install/install_btmonitor.sh
  72. 150
      public/install/public.sh
  73. BIN
      public/install/src/bt-monitor-2.2.5.zip
  74. BIN
      public/install/src/panel6.zip
  75. BIN
      public/install/update/LinuxPanel-8.0.1.zip
  76. 127
      public/install/update6.sh
  77. 446
      public/install/update_btmonitor.sh
  78. 367
      public/install/update_panel.sh
  79. 2
      public/robots.txt
  80. 19
      public/router.php
  81. 6
      public/static/css/bootstrap-table.css
  82. 716
      public/static/css/download.css
  83. 1057
      public/static/css/sanren.css
  84. 1491
      public/static/css/style.css
  85. BIN
      public/static/file/kaixin.zip
  86. BIN
      public/static/file/win/kaixin.zip
  87. BIN
      public/static/images/account.png
  88. BIN
      public/static/images/addc.png
  89. BIN
      public/static/images/bt.png
  90. BIN
      public/static/images/bt_monitor.png
  91. BIN
      public/static/images/bto_copy.png
  92. BIN
      public/static/images/close.png
  93. BIN
      public/static/images/downico1_01.png
  94. BIN
      public/static/images/downico2_01.png
  95. BIN
      public/static/images/eye.png
  96. BIN
      public/static/images/eye_close.png
  97. BIN
      public/static/images/footbg_02.jpg
  98. BIN
      public/static/images/hot_03.png
  99. BIN
      public/static/images/i1abg_03.png
  100. BIN
      public/static/images/i1aico_03.png

18
.env.example

@ -0,0 +1,18 @@
APP_DEBUG = false
[APP]
DEFAULT_TIMEZONE = Asia/Shanghai
[DATABASE]
TYPE = mysql
HOSTNAME = {dbhost}
DATABASE = {dbname}
USERNAME = {dbuser}
PASSWORD = {dbpwd}
HOSTPORT = {dbport}
CHARSET = utf8mb4
PREFIX = {dbprefix}
DEBUG = false
[LANG]
default_lang = zh-cn

4
.gitignore

@ -0,0 +1,4 @@
/.idea
/.vscode
/vendor
*.log

21
LICENSE

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Flucont
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

56
README.md

@ -0,0 +1,56 @@
# 宝塔面板第三方云端
这是一个用php开发的宝塔面板第三方云端站点程序。
你可以使用此程序搭建属于自己的宝塔面板第三方云端,实现最新版宝塔面板私有化部署,不与宝塔官方接口通信,满足隐私安全合规需求。同时还可以去除面板强制绑定账号,DIY面板功能等。
网站后台管理可一键同步宝塔官方的插件列表与增量更新插件包,还有云端使用记录、IP黑白名单、操作日志、定时任务等功能。
本项目自带的宝塔安装包和更新包是7.9.8最新版,已修改适配此第三方云端,并且全开源,无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`查看使用此第三方云端的一键安装脚本。
## 其他
- [Linux面板官方更新包修改记录](./wiki/update.md)
- [Windows面板官方更新包修改记录](./wiki/updatewin.md)
- 宝塔面板官方版与此第三方云端版对比:
| | 官方版 | 此第三方云端版 |
| ---------- | ------------------------------------------------------------ | -------------------------------------------------- |
| 版本更新 | 支持 | 支持 |
| 面板广告 | 有广告 | 无广告 |
| 是否全开源 | 没有全开源 | 全开源 |
| 资源占用 | 各种统计上报等任务,资源占用略高 | 去除了很多无用的定时任务,资源占较少 |
| 兼容性 | 由于编译的so文件有系统架构限制,兼容的系统仅限已编译的so对应的系统架构 | 由于全开源,没有已编译的so文件,因此无系统架构限制 |

1
app/.htaccess

@ -0,0 +1 @@
deny from all

22
app/AppService.php

@ -0,0 +1,22 @@
<?php
declare (strict_types = 1);
namespace app;
use think\Service;
/**
* 应用服务类
*/
class AppService extends Service
{
public function register()
{
// 服务注册
}
public function boot()
{
// 服务启动
}
}

115
app/BaseController.php

@ -0,0 +1,115 @@
<?php
declare (strict_types = 1);
namespace app;
use think\App;
use think\exception\ValidateException;
use think\Validate;
use think\facade\View;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* Request实例
* @var \think\Request
*/
protected $request;
/**
* 应用实例
* @var \think\App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [];
protected $clientip;
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $this->app->request;
// 控制器初始化
$this->initialize();
}
// 初始化
protected function initialize()
{
$this->clientip = real_ip();
}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
[$validate, $scene] = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();
if (!empty($scene)) {
$v->scene($scene);
}
}
$v->message($message);
// 是否批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
return $v->failException(true)->check($data);
}
protected function alert($code, $msg = '', $url = null, $wait = 3)
{
if ($url) {
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : (string)$this->app->route->buildUrl($url);
}
if(empty($msg)) $msg = '未知错误';
View::assign([
'code' => $code,
'msg' => $msg,
'url' => $url,
'wait' => $wait,
]);
return View::fetch(app()->getAppPath().'view/dispatch_jump.html');
}
}

58
app/ExceptionHandle.php

@ -0,0 +1,58 @@
<?php
namespace app;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Response;
use Throwable;
/**
* 应用异常处理类
*/
class ExceptionHandle extends Handle
{
/**
* 不需要记录信息(日志)的异常类列表
* @var array
*/
protected $ignoreReport = [
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
DataNotFoundException::class,
ValidateException::class,
];
/**
* 记录异常信息(包括日志或者其它方式记录)
*
* @access public
* @param Throwable $exception
* @return void
*/
public function report(Throwable $exception): void
{
// 使用内置的方式记录异常日志
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @access public
* @param \think\Request $request
* @param Throwable $e
* @return Response
*/
public function render($request, Throwable $e): Response
{
// 添加自定义异常处理机制
// 其他错误交给系统处理
return parent::render($request, $e);
}
}

8
app/Request.php

@ -0,0 +1,8 @@
<?php
namespace app;
// 应用请求对象类
class Request extends \think\Request
{
}

115
app/command/DecryptFile.php

@ -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);
}
}

118
app/command/UpdateAll.php

@ -0,0 +1,118 @@
<?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 UpdateAll extends Command
{
protected function configure()
{
$this->setName('updateall')
->setDescription('the updateall command');
}
protected function execute(Input $input, Output $output)
{
$res = Db::name('config')->cache('configs',0)->column('value','key');
Config::set($res, 'sys');
if(config_get('bt_url')){
$this->process_plugins($input, $output, 'Linux');
}
if(config_get('wbt_url')){
$this->process_plugins($input, $output, 'Windows');
}
config_set('runtime', date('Y-m-d H:i:s'));
}
private function process_plugins(Input $input, Output $output, $os){
//刷新插件列表
if(!$this->refresh_plugin_list($input, $output, $os)){
return;
}
$count = 0;
$type = intval(config_get($os=='Windows'?'updateall_type_win':'updateall_type'));
$json_arr = Plugins::get_plugin_list($os);
//循环下载缺少的插件
foreach($json_arr['list'] as $plugin){
if($type == 0 && ($plugin['type']==8 || $plugin['type']==12) || $type == 1 && $plugin['type']==12 || $plugin['type']==10 || $plugin['type']==5) continue;
foreach($plugin['versions'] as $version){
$ver = $version['m_version'].'.'.$version['version'];
if(isset($version['download'])){
if(!file_exists(get_data_dir().'plugins/other/'.$version['download'])){
if(!$this->download_plugin($input, $output, $plugin['name'], $ver, $os)){
sleep(1);
$this->download_plugin($input, $output, $plugin['name'], $ver, $os);
}
sleep(1);
$count++;
}
}else{
if(!file_exists(get_data_dir($os).'plugins/package/'.$plugin['name'].'-'.$ver.'.zip')){
if(!$this->download_plugin($input, $output, $plugin['name'], $ver, $os)){
sleep(1);
$this->download_plugin($input, $output, $plugin['name'], $ver, $os);
}
sleep(1);
$count++;
}
}
}
}
$output->writeln($os.'本次成功下载'.$count.'个插件');
}
private function refresh_plugin_list(Input $input, Output $output, $os){
try{
Plugins::refresh_plugin_list($os);
Db::name('log')->insert(['uid' => 1, 'action' => '刷新插件列表', 'data' => '刷新'.$os.'插件列表成功', 'addtime' => date("Y-m-d H:i:s")]);
$output->writeln('刷新'.$os.'插件列表成功');
return true;
}catch(\Exception $e){
$output->writeln($e->getMessage());
errorlog($e->getMessage());
return false;
}
}
private function download_plugin(Input $input, Output $output, $plugin_name, $version, $os){
$fullname = $plugin_name.'-'.$version;
try{
Plugins::download_plugin($plugin_name, $version, $os);
Db::name('log')->insert(['uid' => 1, 'action' => '下载插件', 'data' => $fullname.' os:'.$os, 'addtime' => date("Y-m-d H:i:s")]);
$output->writeln('下载'.$os.'插件: '.$fullname.' 成功');
return true;
}catch(\Exception $e){
$output->writeln($fullname.' '.$e->getMessage());
errorlog($fullname.' '.$e->getMessage());
return false;
}
}
private function download_plugin_image(Input $input, Output $output, $fname){
try{
Plugins::download_plugin_other($fname);
$output->writeln('下载图片: '.$fname.' 成功');
return true;
}catch(\Exception $e){
$output->writeln($fname.' '.$e->getMessage());
errorlog($fname.' '.$e->getMessage());
return false;
}
}
}

187
app/common.php

@ -0,0 +1,187 @@
<?php
// 应用公共文件
use think\facade\Db;
function get_data_dir($os = 'Linux'){
return app()->getRootPath().'data/'.($os == 'Windows' ? 'win/' : '');
}
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
$ckey_length = 4;
$key = md5($key);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if(((int)substr($result, 0, 10) == 0 || (int)substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc.str_replace('=', '', base64_encode($result));
}
}
function random($length, $numeric = 0) {
$seed = base_convert(md5(microtime().$_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35);
$seed = $numeric ? (str_replace('0', '', $seed).'012340567890') : ($seed.'zZ'.strtoupper($seed));
$hash = '';
$max = strlen($seed) - 1;
for($i = 0; $i < $length; $i++) {
$hash .= $seed[mt_rand(0, $max)];
}
return $hash;
}
function get_curl($url, $post=0, $referer=0, $cookie=0, $header=0, $ua=0, $nobody=0, $addheader=0)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$httpheader[] = "Accept: */*";
$httpheader[] = "Accept-Encoding: gzip,deflate,sdch";
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
$httpheader[] = "Connection: close";
if($addheader){
$httpheader = array_merge($httpheader, $addheader);
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
if ($post) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
if ($header) {
curl_setopt($ch, CURLOPT_HEADER, true);
}
if ($cookie) {
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
}
if($referer){
curl_setopt($ch, CURLOPT_REFERER, $referer);
}
if ($ua) {
curl_setopt($ch, CURLOPT_USERAGENT, $ua);
}
else {
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36");
}
if ($nobody) {
curl_setopt($ch, CURLOPT_NOBODY, 1);
}
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
function jsonp_decode($jsonp, $assoc = false)
{
$jsonp = trim($jsonp);
if(isset($jsonp[0]) && $jsonp[0] !== '[' && $jsonp[0] !== '{') {
$begin = strpos($jsonp, '(');
if(false !== $begin)
{
$end = strrpos($jsonp, ')');
if(false !== $end)
{
$jsonp = substr($jsonp, $begin + 1, $end - $begin - 1);
}
}
}
return json_decode($jsonp, $assoc);
}
function config_get($key, $default = null)
{
$value = config('sys.'.$key);
return $value!==null ? $value : $default;
}
function config_set($key, $value)
{
$res = Db::name('config')->replace()->insert(['key'=>$key, 'value'=>$value]);
return $res!==false;
}
function real_ip($type=0){
$ip = $_SERVER['REMOTE_ADDR'];
if($type<=0 && isset($_SERVER['HTTP_X_FORWARDED_FOR']) && preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
foreach ($matches[0] AS $xip) {
if (filter_var($xip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $xip;
break;
}
}
} elseif ($type<=0 && isset($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif ($type<=1 && isset($_SERVER['HTTP_CF_CONNECTING_IP']) && filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
} elseif ($type<=1 && isset($_SERVER['HTTP_X_REAL_IP']) && filter_var($_SERVER['HTTP_X_REAL_IP'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
return $ip;
}
function getSubstr($str, $leftStr, $rightStr)
{
$left = strpos($str, $leftStr);
$start = $left+strlen($leftStr);
$right = strpos($str, $rightStr, $start);
if($left < 0) return '';
if($right>0){
return substr($str, $start, $right-$start);
}else{
return substr($str, $start);
}
}
function checkRefererHost(){
if(!request()->header('referer'))return false;
$url_arr = parse_url(request()->header('referer'));
$http_host = request()->header('host');
if(strpos($http_host,':'))$http_host = substr($http_host, 0, strpos($http_host, ':'));
return $url_arr['host'] === $http_host;
}
function checkIfActive($string) {
$array=explode(',',$string);
$action = request()->action();
if (in_array($action,$array)){
return 'active';
}else
return null;
}
function errorlog($msg){
$handle = fopen(app()->getRootPath()."record.txt", 'a');
fwrite($handle, date('Y-m-d H:i:s')."\t".$msg."\r\n");
fclose($handle);
}

388
app/controller/Admin.php

@ -0,0 +1,388 @@
<?php
namespace app\controller;
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;
class Admin extends BaseController
{
public function verifycode()
{
return captcha();
}
public function login(){
if(request()->islogin){
return redirect('/admin');
}
if(request()->isAjax()){
$username = input('post.username',null,'trim');
$password = input('post.password',null,'trim');
$code = input('post.code',null,'trim');
if(empty($username) || empty($password)){
return json(['code'=>-1, 'msg'=>'用户名或密码不能为空']);
}
if(!captcha_check($code)){
return json(['code'=>-1, 'msg'=>'验证码错误']);
}
if($username == config_get('admin_username') && $password == config_get('admin_password')){
Db::name('log')->insert(['uid' => 0, 'action' => '登录后台', 'data' => 'IP:'.$this->clientip, 'addtime' => date("Y-m-d H:i:s")]);
$session = md5($username.config_get('admin_password'));
$expiretime = time()+2562000;
$token = authcode("{$username}\t{$session}\t{$expiretime}", 'ENCODE', config_get('syskey'));
cookie('admin_token', $token, ['expire' => $expiretime, 'httponly' => true]);
config_set('admin_lastlogin', date('Y-m-d H:i:s'));
return json(['code'=>0]);
}else{
return json(['code'=>-1, 'msg'=>'用户名或密码错误']);
}
}
return view();
}
public function logout()
{
cookie('admin_token', null);
return redirect('/admin/login');
}
public function index()
{
$stat = ['total'=>0, 'free'=>0, 'pro'=>0, 'ltd'=>0, 'third'=>0];
$json_arr = Plugins::get_plugin_list();
if($json_arr){
foreach($json_arr['list'] as $plugin){
$stat['total']++;
if($plugin['type']==10) $stat['third']++;
elseif($plugin['type']==12) $stat['ltd']++;
elseif($plugin['type']==8) $stat['pro']++;
elseif($plugin['type']==5 || $plugin['type']==6 || $plugin['type']==7) $stat['free']++;
}
}
$stat['runtime'] = Db::name('config')->where('key','runtime')->value('value') ?? '<font color="red">未运行</font>';
$stat['record_total'] = Db::name('record')->count();
$stat['record_isuse'] = Db::name('record')->whereTime('usetime', '>=', strtotime('-7 days'))->count();
View::assign('stat', $stat);
$tmp = 'version()';
$mysqlVersion = Db::query("select version()")[0][$tmp];
$info = [
'framework_version' => app()::VERSION,
'php_version' => PHP_VERSION,
'mysql_version' => $mysqlVersion,
'software' => $_SERVER['SERVER_SOFTWARE'],
'os' => php_uname(),
'date' => date("Y-m-d H:i:s"),
];
View::assign('info', $info);
return view();
}
public function set(){
if(request()->isAjax()){
$params = Request::param();
foreach ($params as $key => $value) {
config_set($key, $value);
}
cache('configs', NULL);
return json(['code'=>0]);
}
$mod = input('param.mod', 'sys');
View::assign('mod', $mod);
View::assign('conf', config('sys'));
$runtime = Db::name('config')->where('key','runtime')->value('value') ?? '<font color="red">未运行</font>';
View::assign('runtime', $runtime);
return view();
}
public function setaccount(){
$params = Request::param();
if(isset($params['username']))$params['username']=trim($params['username']);
if(isset($params['oldpwd']))$params['oldpwd']=trim($params['oldpwd']);
if(isset($params['newpwd']))$params['newpwd']=trim($params['newpwd']);
if(isset($params['newpwd2']))$params['newpwd2']=trim($params['newpwd2']);
if(empty($params['username'])) return json(['code'=>-1, 'msg'=>'用户名不能为空']);
config_set('admin_username', $params['username']);
if(!empty($params['oldpwd']) && !empty($params['newpwd']) && !empty($params['newpwd2'])){
if(config_get('admin_password') != $params['oldpwd']){
return json(['code'=>-1, 'msg'=>'旧密码不正确']);
}
if($params['newpwd'] != $params['newpwd2']){
return json(['code'=>-1, 'msg'=>'两次新密码输入不一致']);
}
config_set('admin_password', $params['newpwd']);
}
cache('configs', NULL);
cookie('admin_token', null);
return json(['code'=>0]);
}
public function testbturl(){
$bt_url = input('post.bt_url');
$bt_key = input('post.bt_key');
if(!$bt_url || !$bt_key)return json(['code'=>-1, 'msg'=>'参数不能为空']);
$btapi = new Btapi($bt_url, $bt_key);
$result = $btapi->get_config();
if($result && isset($result['status']) && ($result['status']==1 || isset($result['sites_path']))){
$result = $btapi->get_user_info();
if($result && isset($result['username'])){
return json(['code'=>0, 'msg'=>'面板连接测试成功!']);
}else{
return json(['code'=>-1, 'msg'=>'面板连接测试成功,但未安装专用插件']);
}
}else{
return json(['code'=>-1, 'msg'=>isset($result['msg'])?$result['msg']:'面板地址无法连接']);
}
}
public function plugins(){
$typelist = [];
$json_arr = Plugins::get_plugin_list();
if($json_arr){
foreach($json_arr['type'] as $type){
$typelist[$type['id']] = $type['title'];
}
}
View::assign('typelist', $typelist);
return view();
}
public function pluginswin(){
$typelist = [];
$json_arr = Plugins::get_plugin_list('Windows');
if($json_arr){
foreach($json_arr['type'] as $type){
$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');
$os = input('get.os');
if(!$os) $os = 'Linux';
$json_arr = Plugins::get_plugin_list($os);
if(!$json_arr) return json([]);
$typelist = [];
foreach($json_arr['type'] as $row){
$typelist[$row['id']] = $row['title'];
}
$list = [];
foreach($json_arr['list'] as $plugin){
if($type > 0 && $plugin['type']!=$type) continue;
if(!empty($keyword) && $keyword != $plugin['name'] && stripos($plugin['title'], $keyword)===false) continue;
$versions = [];
foreach($plugin['versions'] as $version){
$ver = $version['m_version'].'.'.$version['version'];
if(isset($version['download'])){
$status = false;
if(file_exists(get_data_dir().'plugins/other/'.$version['download'])){
$status = true;
}
$versions[] = ['status'=>$status, 'type'=>1, 'version'=>$ver, 'download'=>$version['download'], 'md5'=>$version['md5']];
}else{
$status = false;
if(file_exists(get_data_dir($os).'plugins/package/'.$plugin['name'].'-'.$ver.'.zip')){
$status = true;
}
$versions[] = ['status'=>$status, 'type'=>0, 'version'=>$ver];
}
}
if($plugin['name'] == 'obs') $plugin['ps'] = substr($plugin['ps'],0,strpos($plugin['ps'],'<a '));
$list[] = [
'id' => $plugin['id'],
'name' => $plugin['name'],
'title' => $plugin['title'],
'type' => $plugin['type'],
'typename' => $typelist[$plugin['type']],
'desc' => str_replace('target="_blank"','target="_blank" rel="noopener noreferrer"',$plugin['ps']),
'price' => $plugin['price'],
'author' => isset($plugin['author']) ? $plugin['author'] : '官方',
'versions' => $versions
];
}
return json($list);
}
public function download_plugin(){
$name = input('post.name', null, 'trim');
$version = input('post.version', null, 'trim');
$os = input('post.os');
if(!$os) $os = 'Linux';
if(!$name || !$version) return json(['code'=>-1, 'msg'=>'参数不能为空']);
try{
Plugins::download_plugin($name, $version, $os);
Db::name('log')->insert(['uid' => 0, 'action' => '下载插件', 'data' => $name.'-'.$version.' os:'.$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 refresh_plugins(){
$os = input('get.os');
if(!$os) $os = 'Linux';
try{
Plugins::refresh_plugin_list($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 record(){
return view();
}
public function record_data(){
$ip = input('post.ip', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
$select = Db::name('record');
if(!empty($ip)){
$select->where('ip', $ip);
}
$total = $select->count();
$rows = $select->order('id','desc')->limit($offset, $limit)->select();
return json(['total'=>$total, 'rows'=>$rows]);
}
public function log(){
return view();
}
public function log_data(){
$action = input('post.action', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
$select = Db::name('log');
if(!empty($action)){
$select->where('action', $action);
}
$total = $select->count();
$rows = $select->order('id','desc')->limit($offset, $limit)->select();
return json(['total'=>$total, 'rows'=>$rows]);
}
public function list(){
$type = input('param.type', 'black');
View::assign('type', $type);
View::assign('typename', $type=='white'?'白名单':'黑名单');
return view();
}
public function list_data(){
$type = input('param.type', 'black');
$ip = input('post.ip', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
$tablename = $type == 'black' ? 'black' : 'white';
$select = Db::name($tablename);
if(!empty($ip)){
$select->where('ip', $ip);
}
$total = $select->count();
$rows = $select->order('id','desc')->limit($offset, $limit)->select();
return json(['total'=>$total, 'rows'=>$rows]);
}
public function list_op(){
$type = input('param.type', 'black');
$tablename = $type == 'black' ? 'black' : 'white';
$act = input('post.act', null);
if($act == 'get'){
$id = input('post.id/d');
if(!$id) return json(['code'=>-1, 'msg'=>'no id']);
$data = Db::name($tablename)->where('id', $id)->find();
return json(['code'=>0, 'data'=>$data]);
}elseif($act == 'add'){
$ip = input('post.ip', null, 'trim');
if(!$ip) return json(['code'=>-1, 'msg'=>'IP不能为空']);
if(Db::name($tablename)->where('ip', $ip)->find()){
return json(['code'=>-1, 'msg'=>'该IP已存在']);
}
Db::name($tablename)->insert([
'ip' => $ip,
'enable' => 1,
'addtime' => date("Y-m-d H:i:s")
]);
return json(['code'=>0, 'msg'=>'succ']);
}elseif($act == 'edit'){
$id = input('post.id/d');
$ip = input('post.ip', null, 'trim');
if(!$id || !$ip) return json(['code'=>-1, 'msg'=>'IP不能为空']);
if(Db::name($tablename)->where('ip', $ip)->where('id', '<>', $id)->find()){
return json(['code'=>-1, 'msg'=>'该IP已存在']);
}
Db::name($tablename)->where('id', $id)->update([
'ip' => $ip
]);
return json(['code'=>0, 'msg'=>'succ']);
}elseif($act == 'enable'){
$id = input('post.id/d');
$enable = input('post.enable/d');
if(!$id) return json(['code'=>-1, 'msg'=>'no id']);
Db::name($tablename)->where('id', $id)->update([
'enable' => $enable
]);
return json(['code'=>0, 'msg'=>'succ']);
}elseif($act == 'del'){
$id = input('post.id/d');
if(!$id) return json(['code'=>-1, 'msg'=>'no id']);
Db::name($tablename)->where('id', $id)->delete();
return json(['code'=>0, 'msg'=>'succ']);
}
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']);
}
}

387
app/controller/Api.php

@ -0,0 +1,387 @@
<?php
namespace app\controller;
use think\facade\Db;
use app\BaseController;
use app\lib\Plugins;
class Api extends BaseController
{
//获取插件列表
public function get_plugin_list(){
if(!$this->checklist()) return '';
$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();
if(!$json_arr) return json((object)[]);
return json($json_arr);
}
//获取插件列表(win)
public function get_plugin_list_win(){
if(!$this->checklist()) return '';
$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_file = get_data_dir('Windows').'config/plugin_list.json';
if(file_exists($json_file)){
$data = file_get_contents($json_file);
$json_arr = json_decode($data, true);
if($json_arr){
return json($json_arr);
}
}
return json((object)[]);
}
//下载插件包
public function download_plugin(){
$plugin_name = input('post.name');
$version = input('post.version');
$os = input('post.os');
if(!$plugin_name || !$version){
return '参数不能为空';
}
if(!in_array($os,['Windows','Linux'])) $os = 'Linux';
if(!preg_match('/^[a-zA-Z0-9_]+$/', $plugin_name) || !preg_match('/^[0-9.]+$/', $version)){
return '参数不正确';
}
if(!$this->checklist()) '你的服务器被禁止使用此云端';
$filepath = get_data_dir($os).'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');
$version = input('post.version');
$os = input('post.os');
if(!$plugin_name || !$version){
return '参数不能为空';
}
if(!in_array($os,['Windows','Linux'])) $os = 'Linux';
if(!preg_match('/^[a-zA-Z0-9_]+$/', $plugin_name) || !preg_match('/^[0-9.]+$/', $version)){
return '参数不正确';
}
if(!$this->checklist()) '你的服务器被禁止使用此云端';
$filepath = get_data_dir($os).'plugins/main/'.$plugin_name.'-'.$version.'.dat';
if(file_exists($filepath)){
$filename = $plugin_name.'_main.py';
$this->output_file($filepath, $filename);
}else{
$filepath = get_data_dir($os).'plugins/folder/'.$plugin_name.'-'.$version.'/'.$plugin_name.'/'.$plugin_name.'_main.py';
if(file_exists($filepath)){
$filename = $plugin_name.'_main.py';
$this->output_file($filepath, $filename);
}else{
return '云端不存在该插件主文件';
}
}
}
//下载插件其他文件
public function download_plugin_other(){
$fname = input('get.fname');
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'=>'你的服务器被禁止使用此云端']);
$filepath = get_data_dir().'plugins/other/'.$fname;
if(file_exists($filepath)){
$filename = basename($fname);
$this->output_file($filepath, $filename);
}else{
return json(['status'=>false, 'msg'=>'云端不存在该插件文件']);
}
}
public function get_update_logs(){
$type = input('get.type');
if($type == 'Windows'){
$version = config_get('new_version_win');
$data = [
[
'title' => 'Linux面板'.$version,
'body' => config_get('update_msg_win'),
'addtime' => config_get('update_date_win')
]
];
}else{
$version = config_get('new_version');
$data = [
[
'title' => 'Linux面板'.$version,
'body' => config_get('update_msg'),
'addtime' => config_get('update_date')
]
];
}
return jsonp($data);
}
public function get_version(){
$version = config_get('new_version');
return $version;
}
public function get_version_win(){
$version = config_get('new_version_win');
return $version;
}
//安装统计
public function setup_count(){
return 'ok';
}
//检测更新
public function check_update(){
$version = config_get('new_version');
$down_url = request()->root(true).'/install/update/LinuxPanel-'.$version.'.zip';
$data = [
'force' => false,
'version' => $version,
'downUrl' => $down_url,
'updateMsg' => config_get('update_msg'),
'uptime' => config_get('update_date'),
'is_beta' => 0,
'adviser' => -1,
'btb' => '',
'beta' => [
'version' => $version,
'downUrl' => $down_url,
'updateMsg' => config_get('update_msg'),
'uptime' => config_get('update_date'),
]
];
return json($data);
}
//检测更新(win)
public function check_update_win(){
$version = config_get('new_version_win');
$down_url = request()->root(true).'/win/panel/panel_'.$version.'.zip';
$data = [
'force' => false,
'version' => $version,
'downUrl' => $down_url,
'updateMsg' => config_get('update_msg_win'),
'uptime' => config_get('update_date_win'),
'is_beta' => 0,
'py_version' => '3.8.6',
'adviser' => -1,
'is_rec' => -1,
'btb' => '',
'beta' => [
'py_version' => '3.8.6',
'version' => $version,
'downUrl' => $down_url,
'updateMsg' => config_get('update_msg_win'),
'uptime' => config_get('update_date_win'),
]
];
return json($data);
}
//宝塔云监控获取最新版本
public function btm_latest_version(){
$data = [
'version' => config_get('new_version_btm'),
'description' => config_get('update_msg_btm'),
'create_time' => config_get('update_date_btm')
];
return json($data);
}
//宝塔云监控更新日志
public function btm_update_history(){
$data = [
[
'version' => config_get('new_version_btm'),
'description' => config_get('update_msg_btm'),
'create_time' => config_get('update_date_btm')
]
];
return json($data);
}
//获取内测版更新日志
public function get_beta_logs(){
return json(['beta_ps'=>'当前暂无内测版', 'list'=>[]]);
}
//检查用户绑定是否正确
public function check_auth_key(){
return '1';
}
//从云端验证域名是否可访问
public function check_domain(){
$domain = input('post.domain',null,'trim');
$ssl = input('post.ssl/d');
if(!$domain) return json(['status'=>false, 'msg'=>'域名不能为空']);
if(!strpos($domain,'.')) return json(['status'=>false, 'msg'=>'域名格式不正确']);
$domain = str_replace('*.','',$domain);
$ip = gethostbyname($domain);
if(!$ip || $ip == $domain){
return json(['status'=>false, 'msg'=>'无法访问']);
}else{
return json(['status'=>true, 'msg'=>'访问正常']);
}
}
//同步时间
public function get_time(){
return time();
}
//同步时间
public function get_win_date(){
return date("Y-m-d H:i:s");
}
//查询是否专业版(废弃)
public function is_pro(){
return json(['endtime'=>true, 'code'=>1]);
}
//获取产品推荐信息
public function get_plugin_remarks(){
return json(['list'=>[], 'pro_list'=>[], 'kfqq'=>'', 'kf'=>'', 'qun'=>'']);
}
//获取指定插件评分
public function get_plugin_socre(){
return json(['total'=>0, 'split'=>[0,0,0,0,0],'page'=>"<div><span class='Pcurrent'>1</span><span class='Pcount'>共计0条数据</span></div>",'data'=>[]]);
}
//提交插件评分
public function plugin_score(){
return json(['status'=>true, 'msg'=>'您的评分已成功提交,感谢您的支持!']);
}
//获取IP地址
public function get_ip_address(){
return $this->clientip;
}
//绑定账号
public function get_auth_token(){
if(!$_POST['data']) return json(['status'=>false, 'msg'=>'参数不能为空']);
$reqData = hex2bin($_POST['data']);
parse_str($reqData, $arr);
$serverid = $arr['serverid'];
$userinfo = ['uid'=>1, 'username'=>'Administrator', 'address'=>'127.0.0.1', 'serverid'=>$serverid, 'access_key'=>random(32), 'secret_key'=>random(48), 'ukey'=>md5(time()), 'state'=>1];
$data = bin2hex(urlencode(json_encode($userinfo)));
return json(['status'=>true, 'msg'=>'登录成功!', 'data'=>$data]);
}
//绑定账号新
public function authorization_login(){
if(!$_POST['data']) return json(['status'=>false, 'msg'=>'参数不能为空']);
$reqData = hex2bin($_POST['data']);
parse_str($reqData, $arr);
$serverid = $arr['serverid'];
$userinfo = ['uid'=>1, 'username'=>'Administrator', 'ip'=>'127.0.0.1', 'server_id'=>$serverid, 'access_key'=>random(32), 'secret_key'=>random(48)];
$data = bin2hex(urlencode(json_encode($userinfo)));
return json(['status'=>true, 'msg'=>'登录成功!', 'data'=>$data]);
}
//刷新授权信息
public function authorization_info(){
if(!$_POST['data']) return json(['status'=>false, 'msg'=>'参数不能为空']);
$reqData = hex2bin($_POST['data']);
parse_str($reqData, $arr);
$id = isset($arr['id'])&&$arr['id']>0?$arr['id']:1;
$userinfo = ['id'=>$id, 'product'=>$arr['product'], 'status'=>2, 'clients'=>9999, 'durations'=>0, 'end_time'=>strtotime('+10 year')];
$data = bin2hex(urlencode(json_encode($userinfo)));
return json(['status'=>true, 'data'=>$data]);
}
//一键部署列表
public function get_deplist(){
$os = input('post.os');
$json_arr = Plugins::get_deplist($os);
if(!$json_arr) return json([]);
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)[]]);
}
public function return_error(){
return json(['status'=>false, 'msg'=>'不支持当前操作']);
}
public function return_empty(){
return '';
}
public function return_empty_array(){
return json([]);
}
public function return_page_data(){
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'=>[]]);
}
//检查黑白名单
private function checklist(){
if(config_get('whitelist') == 1){
if(Db::name('white')->where('ip', $this->clientip)->where('enable', 1)->find()){
return true;
}
return false;
}else{
if(Db::name('black')->where('ip', $this->clientip)->where('enable', 1)->find()){
return false;
}
return true;
}
}
//下载大文件
private function output_file($filepath, $filename){
$filesize = filesize($filepath);
$filemd5 = md5_file($filepath);
ob_clean();
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename={$filename}.zip");
header("Content-Length: {$filesize}");
header("File-size: {$filesize}");
header("Content-md5: {$filemd5}");
$read_buffer = 1024 * 100;
$handle = fopen($filepath, 'rb');
$sum_buffer = 0;
while(!feof($handle) && $sum_buffer<$filesize) {
echo fread($handle, min($read_buffer, ($filesize - $sum_buffer) + 1));
$sum_buffer += $read_buffer;
flush();
}
fclose($handle);
exit;
}
}

24
app/controller/Index.php

@ -0,0 +1,24 @@
<?php
namespace app\controller;
use app\BaseController;
use think\facade\View;
class Index extends BaseController
{
public function index()
{
return 'Server is ok';
}
public function download()
{
if(config_get('download_page') == '0' && !request()->islogin){
return redirect('/admin/login');
}
View::assign('siteurl', request()->root(true));
return view();
}
}

83
app/controller/Install.php

@ -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();
}
}

17
app/event.php

@ -0,0 +1,17 @@
<?php
// 事件定义文件
return [
'bind' => [
],
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
],
'subscribe' => [
],
];

230
app/lib/Btapi.php

@ -0,0 +1,230 @@
<?php
namespace app\lib;
use Exception;
class Btapi
{
private $BT_KEY; //接口密钥
private $BT_PANEL; //面板地址
public function __construct($bt_panel, $bt_key){
$this->BT_PANEL = $bt_panel;
$this->BT_KEY = $bt_key;
}
//获取面板配置信息
public function get_config(){
$url = $this->BT_PANEL.'/config?action=get_config';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//获取已登录用户信息
public function get_user_info(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_user_info';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//从云端获取插件列表
public function get_plugin_list(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_plugin_list';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载插件包,返回文件路径
public function get_plugin_filename($plugin_name, $version){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=download_plugin';
$p_data = $this->GetKeyData();
$p_data['plugin_name'] = $plugin_name;
$p_data['version'] = $version;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载插件主程序文件,返回文件路径
public function get_plugin_main_filename($plugin_name, $version){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=download_plugin_main';
$p_data = $this->GetKeyData();
$p_data['plugin_name'] = $plugin_name;
$p_data['version'] = $version;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//解密插件主程序py代码,返回文件路径
public function get_decode_plugin_main($plugin_name, $version){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=decode_plugin_main';
$p_data = $this->GetKeyData();
$p_data['plugin_name'] = $plugin_name;
$p_data['version'] = $version;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载插件其他文件,返回文件路径
public function get_plugin_other_filename($fname){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=download_plugin_other';
$p_data = $this->GetKeyData();
$p_data['fname'] = $fname;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载文件
public function download($filename, $localpath){
$url = $this->BT_PANEL.'/download';
$p_data = $this->GetKeyData();
$p_data['filename'] = $filename;
$result = $this->curl_download($url.'?'.http_build_query($p_data), $localpath);
return $result;
}
//获取文件base64
public function get_file($filename){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_file';
$p_data = $this->GetKeyData();
$p_data['filename'] = $filename;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//购买第三方插件
public function create_plugin_other_order($pid){
$url = $this->BT_PANEL.'/auth?action=create_plugin_other_order';
$p_data = $this->GetKeyData();
$p_data['pid'] = $pid;
$p_data['cycle'] = '999';
$p_data['type'] = '0';
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//获取一键部署列表
public function get_deplist(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_deplist';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
private function GetKeyData(){
$now_time = time();
$p_data = array(
'request_token' => md5($now_time.''.md5($this->BT_KEY)),
'request_time' => $now_time
);
return $p_data;
}
private function curl($url, $data = null, $timeout = 60)
{
//定义cookie保存位置
$cookie_file=app()->getRuntimePath().md5($this->BT_PANEL).'.cookie';
if(!file_exists($cookie_file)){
touch($cookie_file);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
if($data){
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
private function curl_download($url, $localpath, $timeout = 300)
{
//定义cookie保存位置
$cookie_file=app()->getRuntimePath().md5($this->BT_PANEL).'.cookie';
if(!file_exists($cookie_file)){
touch($cookie_file);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
$fp = fopen($localpath, 'w+');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_exec($ch);
if (curl_errno($ch)) {
$message = curl_error($ch);
curl_close($ch);
fclose($fp);
throw new Exception('下载文件失败:'.$message);
}
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpcode>299){
curl_close($ch);
fclose($fp);
throw new Exception('下载文件失败:HTTPCODE-'.$httpcode);
}
curl_close($ch);
fclose($fp);
return true;
}
}

347
app/lib/Plugins.php

@ -0,0 +1,347 @@
<?php
namespace app\lib;
use Exception;
use ZipArchive;
class Plugins
{
private static function get_btapi($os){
if($os == 'Windows'){
$bt_url = config_get('wbt_url');
$bt_key = config_get('wbt_key');
}else{
$bt_url = config_get('bt_url');
$bt_key = config_get('bt_key');
}
if(!$bt_url || !$bt_key) throw new Exception('请先配置好宝塔面板接口信息');
$btapi = new Btapi($bt_url, $bt_key);
return $btapi;
}
//刷新插件列表
public static function refresh_plugin_list($os = 'Linux'){
$btapi = self::get_btapi($os);
$result = $btapi->get_plugin_list();
if($result && isset($result['list']) && isset($result['type'])){
if(empty($result['list']) || empty($result['type'])){
throw new Exception('获取插件列表失败:插件列表为空');
}
self::save_plugin_list($result, $os);
}else{
throw new Exception('获取插件列表失败:'.(isset($result['msg'])?$result['msg']:'面板连接失败'));
}
}
//保存插件列表
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(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('保存插件列表失败,文件无写入权限');
}
}
//获取插件列表
public static function get_plugin_list($os = 'Linux'){
$json_file = get_data_dir($os).'config/plugin_list.json';
if(file_exists($json_file)){
$data = file_get_contents($json_file);
$json_arr = json_decode($data, true);
if($json_arr){
return $json_arr;
}
}
return false;
}
//获取一个插件信息
public static function get_plugin_info($name, $os = 'Linux'){
$json_arr = self::get_plugin_list($os);
if(!$json_arr) return null;
foreach($json_arr['list'] as $plugin){
if($plugin['name'] == $name){
return $plugin;
}
}
return null;
}
//下载插件(自动判断是否第三方)
public static function download_plugin($plugin_name, $version, $os = 'Linux'){
$plugin_info = Plugins::get_plugin_info($plugin_name, $os);
if(!$plugin_info) throw new Exception('未找到该插件信息');
if($plugin_info['type'] == 10 && isset($plugin_info['versions'][0]['download'])){
if($plugin_info['price'] == 0){
$btapi = self::get_btapi($os);
$btapi->create_plugin_other_order($plugin_info['id']);
}
$fname = $plugin_info['versions'][0]['download'];
$filemd5 = $plugin_info['versions'][0]['md5'];
Plugins::download_plugin_other($fname, $filemd5, $os);
if(isset($plugin_info['min_image']) && strpos($plugin_info['min_image'], 'fname=')){
$fname = substr($plugin_info['min_image'], strpos($plugin_info['min_image'], '?fname=')+7);
Plugins::download_plugin_other($fname, null, $os);
}
}else{
Plugins::download_plugin_package($plugin_name, $version, $os);
}
}
//下载插件包
public static function download_plugin_package($plugin_name, $version, $os = 'Linux'){
$filepath = get_data_dir($os).'plugins/package/'.$plugin_name.'-'.$version.'.zip';
$btapi = self::get_btapi($os);
$result = $btapi->get_plugin_filename($plugin_name, $version);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
self::download_file($btapi, $filename, $filepath);
if(file_exists($filepath)){
$zip = new ZipArchive;
if ($zip->open($filepath) === true)
{
$zip->extractTo(get_data_dir($os).'plugins/folder/'.$plugin_name.'-'.$version);
$zip->close();
$main_filepath = get_data_dir($os).'plugins/folder/'.$plugin_name.'-'.$version.'/'.$plugin_name.'/'.$plugin_name.'_main.py';
if(file_exists($main_filepath) && filesize($main_filepath)>10){
if(!strpos(file_get_contents($main_filepath), 'import ')){ //加密py文件,需要解密
self::decode_plugin_main($plugin_name, $version, $main_filepath, $os);
self::noauth_plugin_main($main_filepath);
$zip->open($filepath, ZipArchive::CREATE);
$zip->addFile($main_filepath, $plugin_name.'/'.$plugin_name.'_main.py');
$zip->close();
}
}
}else{
throw new Exception('插件包解压缩失败');
}
return true;
}else{
throw new Exception('下载插件包失败,本地文件不存在');
}
}else{
throw new Exception('下载插件包失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('下载插件包失败,接口返回错误');
}
}
//下载插件主程序文件
public static function download_plugin_main($plugin_name, $version, $os = 'Linux'){
$filepath = get_data_dir($os).'plugins/main/'.$plugin_name.'-'.$version.'.dat';
$btapi = self::get_btapi($os);
$result = $btapi->get_plugin_main_filename($plugin_name, $version);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
self::download_file($btapi, $filename, $filepath);
if(file_exists($filepath)){
return true;
}else{
throw new Exception('下载插件主程序文件失败,本地文件不存在');
}
}else{
throw new Exception('下载插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('下载插件主程序文件失败,接口返回错误');
}
}
//解密并下载插件主程序文件
public static function decode_plugin_main($plugin_name, $version, $main_filepath, $os = 'Linux'){
if(self::decode_plugin_main_local($main_filepath, $os)) return true;
$btapi = self::get_btapi($os);
$result = $btapi->get_decode_plugin_main($plugin_name, $version);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
self::download_file($btapi, $filename, $main_filepath);
return true;
}else{
throw new Exception('解密插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('解密插件主程序文件失败,接口返回错误');
}
}
//本地解密插件主程序文件
public static function decode_plugin_main_local($main_filepath, $os = 'Linux'){
$btapi = self::get_btapi($os);
$userinfo = $btapi->get_user_info();
if(isset($userinfo['uid'])){
$src = file_get_contents($main_filepath);
if($src===false)throw new Exception('文件打开失败');
if(!$src || strpos($src, 'import ')!==false)return true;
$uid = $userinfo['uid'];
$serverid = $userinfo['serverid'];
$key = md5(substr($serverid, 10, 16).$uid.$serverid);
$iv = md5($key.$serverid);
$key = substr($key, 8, 16);
$iv = substr($iv, 8, 16);
$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;
}
return false;
}else{
throw new Exception('解密插件主程序文件失败,获取用户信息失败');
}
}
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);
if(!$data) return false;
$data = str_replace('\'http://www.bt.cn/api/panel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list_test', $data);
$data = str_replace('\'https://www.bt.cn/api/panel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list_test', $data);
$data = str_replace('\'http://www.bt.cn/api/panel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list', $data);
$data = str_replace('\'https://www.bt.cn/api/panel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list', $data);
$data = str_replace('\'http://www.bt.cn/api/panel/notpro', 'public.GetConfigValue(\'home\')+\'/api/panel/notpro', $data);
$data = str_replace('\'https://www.bt.cn/api/panel/notpro', 'public.GetConfigValue(\'home\')+\'/api/panel/notpro', $data);
$data = str_replace('\'http://www.bt.cn/api/wpanel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list_test', $data);
$data = str_replace('\'https://www.bt.cn/api/wpanel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list_test', $data);
$data = str_replace('\'http://www.bt.cn/api/wpanel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list', $data);
$data = str_replace('\'https://www.bt.cn/api/wpanel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list', $data);
$data = str_replace('\'http://www.bt.cn/api/wpanel/notpro', 'public.GetConfigValue(\'home\')+\'/api/wpanel/notpro', $data);
$data = str_replace('\'https://www.bt.cn/api/wpanel/notpro', 'public.GetConfigValue(\'home\')+\'/api/wpanel/notpro', $data);
file_put_contents($main_filepath, $data);
}
//下载插件其他文件
public static function download_plugin_other($fname, $filemd5 = null, $os = 'Linux'){
$filepath = get_data_dir().'plugins/other/'.$fname;
@mkdir(dirname($filepath), 0777, true);
$btapi = self::get_btapi($os);
$result = $btapi->get_plugin_other_filename($fname);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
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($msg);
}
return true;
}else{
throw new Exception('下载插件文件失败,本地文件不存在');
}
}else{
throw new Exception('下载插件文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('下载插件文件失败,接口返回错误');
}
}
//下载文件
private static function download_file($btapi, $filename, $filepath){
try{
$btapi->download($filename, $filepath);
}catch(Exception $e){
@unlink($filepath);
//宝塔bug小文件下载失败,改用base64下载
$result = $btapi->get_file($filename);
if($result && isset($result['status']) && $result['status']==true){
$filedata = base64_decode($result['data']);
if(strlen($filedata) < 4096 && substr($filedata,0,1)=='{' && substr($filedata,-1,1)=='}'){
$arr = json_decode($filedata, true);
if($arr){
throw new Exception('获取文件失败:'.($arr['msg']?$arr['msg']:'未知错误'));
}
}
if(!$filedata){
throw new Exception('获取文件失败:文件内容为空');
}
file_put_contents($filepath, $filedata);
}elseif($result){
throw new Exception('获取文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}else{
throw new Exception('获取文件失败:未知错误');
}
}
}
//刷新一键部署列表
public static function refresh_deplist($os = 'Linux'){
$btapi = self::get_btapi($os);
$result = $btapi->get_deplist();
if($result && isset($result['list']) && isset($result['type'])){
if(empty($result['list']) || empty($result['type'])){
throw new Exception('获取一键部署列表失败:一键部署列表为空');
}
$json_file = get_data_dir($os).'config/deployment_list.json';
if(!file_put_contents($json_file, json_encode($result))){
throw new Exception('保存一键部署列表失败,文件无写入权限');
}
}else{
throw new Exception('获取一键部署列表失败:'.(isset($result['msg'])?$result['msg']:'面板连接失败'));
}
}
//获取一键部署列表
public static function get_deplist($os = 'Linux'){
$json_file = get_data_dir($os).'config/deployment_list.json';
if(file_exists($json_file)){
$data = file_get_contents($json_file);
$json_arr = json_decode($data, true);
if($json_arr){
return $json_arr;
}
}
return false;
}
}

12
app/middleware.php

@ -0,0 +1,12 @@
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
// \think\middleware\LoadLangPack::class,
// Session初始化
// \think\middleware\SessionInit::class,
\app\middleware\LoadConfig::class,
\app\middleware\AuthAdmin::class
];

26
app/middleware/AuthAdmin.php

@ -0,0 +1,26 @@
<?php
declare (strict_types=1);
namespace app\middleware;
class AuthAdmin
{
public function handle($request, \Closure $next)
{
$islogin = false;
$cookie = cookie('admin_token');
if($cookie){
$token=authcode($cookie, 'DECODE', config_get('syskey'));
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;
return $next($request);
}
}

19
app/middleware/CheckAdmin.php

@ -0,0 +1,19 @@
<?php
declare (strict_types=1);
namespace app\middleware;
class CheckAdmin
{
public function handle($request, \Closure $next)
{
if (!request()->islogin) {
if ($request->isAjax() || !$request->isGet()) {
return json(['code'=>-1, 'msg'=>'未登录'])->code(401);
}
return redirect((string)url('/admin/login'));
}
return $next($request);
}
}

39
app/middleware/LoadConfig.php

@ -0,0 +1,39 @@
<?php
declare (strict_types = 1);
namespace app\middleware;
use think\facade\Db;
use think\facade\Config;
class LoadConfig
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
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');
return $next($request)->header([
'Cache-Control' => 'no-store, no-cache, must-revalidate',
'Pragma' => 'no-cache',
]);
}
}

24
app/middleware/RefererCheck.php

@ -0,0 +1,24 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use think\facade\View;
class RefererCheck
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
if(!checkRefererHost()){
return response('Access Denied', 403);
}
return $next($request);
}
}

9
app/provider.php

@ -0,0 +1,9 @@
<?php
use app\ExceptionHandle;
use app\Request;
// 容器Provider定义文件
return [
'think\Request' => Request::class,
'think\exception\Handle' => ExceptionHandle::class,
];

80
app/script/convert.sh

@ -0,0 +1,80 @@
#!/bin/bash
Linux_Version="8.0.1"
Windows_Version="7.9.0"
Btm_Version="2.2.5"
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
public/install/src/bt-monitor-${Btm_Version}.zip
public/install/install_btmonitor.sh
public/install/update_btmonitor.sh
)
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 "=========================="

9
app/service.php

@ -0,0 +1,9 @@
<?php
use app\AppService;
// 系统服务定义文件
// 服务在完成全局初始化之后执行
return [
AppService::class,
];

49
app/view/admin/deplist.html

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

60
app/view/admin/index.html

@ -0,0 +1,60 @@
{extend name="admin/layout" /}
{block name="title"}宝塔第三方云端管理中心{/block}
{block name="main"}
<style>
.table>tbody>tr>td{white-space: normal;}
.query-title {
background-color:#f5fafe;
word-break: keep-all;
}
.query-result{
word-break: break-all;
}
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-sm-10 col-md-8 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">后台管理首页</h3></div>
<div class="list-group">
<div class="list-group-item"><span class="glyphicon glyphicon-stats"></span> <b>宝塔插件统计:</b>共有 {$stat.total} 个,其中免费插件 {$stat.free} 个,专业版插件 {$stat.pro} 个,企业版插件 {$stat.ltd} 个,第三方插件 {$stat.third} 个</div>
<div class="list-group-item"><span class="glyphicon glyphicon-tint"></span> <b>使用记录统计:</b>历史总共数量:{$stat.record_total},正在使用数量:{$stat.record_isuse}</div>
<div class="list-group-item"><span class="glyphicon glyphicon-time"></span> <b>任务运行情况:</b>上次运行时间:{$stat.runtime|raw}&nbsp;&nbsp;<a href="/admin/set/mod/task" class="btn btn-xs btn-info">查看详情</a></div>
<div class="list-group-item"><span class="glyphicon glyphicon-cog"></span> <b>常用功能入口:</b><a href="/admin/plugins" class="btn btn-xs btn-default">插件列表</a>&nbsp;<a href="/admin/record" class="btn btn-xs btn-default">使用记录</a>&nbsp;<a href="/admin/list" class="btn btn-xs btn-default">黑白名单</a>&nbsp;<a href="/download" class="btn btn-xs btn-default" target="_blank">安装脚本</a></div>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">服务器信息</h3>
</div>
<table class="table table-bordered">
<tbody>
<tr>
<td class="query-title">框架版本</td>
<td class="query-result">{$info.framework_version}</td>
</tr>
<tr>
<td class="query-title">PHP版本</td>
<td class="query-result">{$info.php_version}</td>
</tr>
<tr>
<td class="query-title">MySQL版本</td>
<td class="query-result">{$info.mysql_version}</td>
</tr>
<tr>
<td class="query-title">WEB软件</td>
<td class="query-result">{$info.software}</td>
</tr>
<tr>
<td class="query-title">操作系统</td>
<td class="query-result">{$info.os}</td>
</tr>
<tr>
<td class="query-title">服务器时间</td>
<td class="query-result">{$info.date}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{/block}

73
app/view/admin/layout.html

@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8" />
<meta name="renderer" content="webkit">
<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" />
<link href="/static/css/bootstrap-table.css" rel="stylesheet" />
<script src="//cdn.staticfile.org/modernizr/2.8.3/modernizr.min.js"></script>
<script src="//cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<script src="//cdn.staticfile.org/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<!--[if lt IE 9]>
<script src="//cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="//cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<nav class="navbar navbar-fixed-top navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="sr-only">导航按钮</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="./">宝塔第三方云端管理中心</a>
</div><!-- /.navbar-header -->
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li class="{:checkIfActive('index')}">
<a href="/admin"><i class="fa fa-home"></i> 后台首页</a>
</li>
<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')}">
<a href="/admin/record"><i class="fa fa-list"></i> 使用记录</a>
</li>
<li class="{:checkIfActive('list')}">
<a href="/admin/list"><i class="fa fa-globe"></i> 黑白名单</a>
</li>
<li class="{:checkIfActive('log')}">
<a href="/admin/log"><i class="fa fa-calendar"></i> 操作日志</a>
</li>
<li class="{:checkIfActive('set')}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-cog"></i> 系统设置<b
class="caret"></b></a>
<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>
<li>
<a href="/admin/logout" onclick="return confirm('确定退出登录吗?')"><i class="fa fa-power-off"></i> 退出登录</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container -->
</nav><!-- /.navbar -->
{block name="main"}主内容{/block}
</body>
</html>

209
app/view/admin/list.html

@ -0,0 +1,209 @@
{extend name="admin/layout" /}
{block name="title"}黑白名单{/block}
{block name="main"}
<style>
.alert{margin-bottom: 5px;}
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-md-10 center-block" style="float: none;">
<ul class="nav nav-tabs">
<li class="{if $type=='black'}active{/if}"><a href="/admin/list">黑名单列表</a></li><li class="{if $type=='white'}active{/if}"><a href="/admin/list/type/white">白名单列表</a></li>
</ul>
{if $type=='black' && config_get('whitelist')=='1'}
<div class="alert alert-warning">提示:当前为白名单模式,黑名单列表里面的记录不会生效。</div>
{/if}
{if $type=='white' && config_get('whitelist')=='0'}
<div class="alert alert-warning">提示:当前未开启白名单模式,白名单列表里面的记录不会生效。</div>
{/if}
{if $type=='black'}
<div class="alert alert-info">添加到黑名单列表中的服务器IP将无法使用此云端</div>
{/if}
{if $type=='white'}
<div class="alert alert-info">只有添加到白名单列表中的服务器IP才可以使用此云端</div>
{/if}
<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="ip" placeholder="服务器IP">
</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:add_item()" class="btn btn-success"><i class="fa fa-plus"></i>添加</a>&nbsp;
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/bootstrap-table.min.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
function setEnable(id,enable) {
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'enable', id:id, enable:enable},
dataType : 'json',
success : function(data) {
if(data.code == 0){
searchSubmit();
}else{
layer.msg(data.msg, {icon:2, time:1500});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
}
function add_item(){
layer.open({
area: ['360px'],
title: '添加IP{$typename}',
content: '<div class="form-group"><input type="text" class="form-control" name="item_input" placeholder="请输入服务器IP" value=""></div>',
yes: function(){
var ip = $("input[name='item_input']").val();
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'add', ip:ip},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('添加成功', {icon:1, time:800});
searchSubmit();
}else{
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
},
shadeClose: true
});
}
function edit_item(id){
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'get', id:id},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.open({
area: ['360px'],
title: '编辑IP{$typename}',
content: '<div class="form-group"><input type="text" class="form-control" name="item_input" placeholder="请输入服务器IP" value="'+data.data.ip+'"></div>',
yes: function(){
var ip = $("input[name='item_input']").val();
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'edit', id:id, ip:ip},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('修改成功', {icon:1, time:800});
searchSubmit();
}else{
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
},
shadeClose: true
});
}else{
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
}
function del_item(id) {
if(confirm('是否确定删除此记录?')){
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'del', id:id},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('删除成功!', {icon:1, time:800});
searchSubmit();
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
}
}
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 15;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/admin/list_data/type/{$type}',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'id',
title: 'ID',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'ip',
title: '服务器IP'
},
{
field: 'enable',
title: '是否生效',
formatter: function(value, row, index) {
return value?'<a href="javascript:setEnable('+row.id+',0)"><font color=green><i class="fa fa-check-circle"></i>已生效</font></a>':'<a href="javascript:setEnable('+row.id+',1)"><font color=red><i class="fa fa-times-circle"></i>未生效</font></a>';
}
},
{
field: 'addtime',
title: '添加时间'
},
{
field: '',
title: '操作',
formatter: function(value, row, index) {
return '<a href="javascript:edit_item('+row.id+')" class="btn btn-xs btn-info">编辑</a>&nbsp;<a href="javascript:del_item('+row.id+')" class="btn btn-xs btn-danger">删除</a>';
},
},
],
})
})
</script>
{/block}

74
app/view/admin/log.html

@ -0,0 +1,74 @@
{extend name="admin/layout" /}
{block name="title"}操作日志{/block}
{block name="main"}
<style>
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-md-10 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="action" placeholder="操作类型">
</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;
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/bootstrap-table.min.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 20;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/admin/log_data',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'id',
title: 'ID',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'uid',
title: '操作人',
formatter: function(value, row, index) {
return value==1?'<font color="green">定时任务</font>':'<font color="blue">管理员</font>';
}
},
{
field: 'action',
title: '操作类型'
},
{
field: 'data',
title: '操作详情',
},
{
field: 'addtime',
title: '操作时间'
},
],
})
})
</script>
{/block}

100
app/view/admin/login.html

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8"/>
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>管理员登录</title>
<link href="//cdn.staticfile.org/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="//cdn.staticfile.org/modernizr/2.8.3/modernizr.min.js"></script>
<script src="//cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<!--[if lt IE 9]>
<script src="//cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="//cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<nav class="navbar navbar-fixed-top navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">导航按钮</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="./">宝塔第三方云端管理中心</a>
</div><!-- /.navbar-header -->
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li class="active">
<a href="#"><span class="glyphicon glyphicon-user"></span> 登录</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container -->
</nav><!-- /.navbar -->
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-sm-10 col-md-8 col-lg-6 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 class="form-horizontal" role="form" onsubmit="return submitlogin()">
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
<input type="text" name="user" value="" class="form-control input-lg" placeholder="用户名" required="required"/>
</div><br/>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
<input type="password" name="pass" class="form-control input-lg" placeholder="密码" required="required"/>
</div><br/>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-adjust"></span></span>
<input type="text" class="form-control input-lg" name="code" placeholder="输入验证码" autocomplete="off" required>
<span class="input-group-addon" style="padding: 0">
<img src="/admin/verifycode" height="45" id="verifycode" onclick="this.src='/admin/verifycode?r='+Math.random();" title="点击更换验证码">
</span>
</div><br/>
<div class="form-group">
<div class="col-xs-12"><input type="submit" value="立即登录" class="btn btn-primary btn-block btn-lg"/></div>
</div>
</form>
</div>
</div>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script>
function submitlogin(){
var user = $("input[name='user']").val();
var pass = $("input[name='pass']").val();
var code = $("input[name='code']").val();
if(user=='' || pass==''){layer.alert('用户名或密码不能为空!');return false;}
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';
}else{
if(data.msg.indexOf('验证码')==-1){
$("#verifycode").attr('src', '/admin/verifycode?r='+Math.random())
}
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
</script>
</body>
</html>

214
app/view/admin/plugins.html

@ -0,0 +1,214 @@
{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/plugins/package/软件标识-版本号.zip,第三方插件包路径是/data/plugins/other/other/,对于部分包含二次验证的插件可以自行修改。</p>
<p>点击【重新获取】按钮会从宝塔官方获取最新插件列表,但是插件包需要手动点击下载。如果需要批量下载插件包,可查看<a href="/admin/set/mod/task">定时任务设置</a></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;
<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="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/bootstrap-table.min.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/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},
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',
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 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',
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){
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
}
},
],
})
})
</script>
{/block}

214
app/view/admin/pluginswin.html

@ -0,0 +1,214 @@
{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/win/plugins/package/软件标识-版本号.zip,第三方插件包路径是/data/plugins/other/other/,对于部分包含二次验证的插件可以自行修改。</p>
<p>点击【重新获取】按钮会从宝塔官方获取最新插件列表,但是插件包需要手动点击下载。如果需要批量下载插件包,可查看<a href="/admin/set/mod/task">定时任务设置</a></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;
<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="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/bootstrap-table.min.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/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:'Windows'},
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=Windows',
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 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=Windows',
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){
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
}
},
],
})
})
</script>
{/block}

67
app/view/admin/record.html

@ -0,0 +1,67 @@
{extend name="admin/layout" /}
{block name="title"}使用记录{/block}
{block name="main"}
<style>
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-md-10 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="ip" placeholder="服务器IP">
</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;
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/bootstrap-table.min.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 15;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/admin/record_data',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'id',
title: 'ID',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'ip',
title: '服务器IP'
},
{
field: 'addtime',
title: '首次安装时间',
},
{
field: 'usetime',
title: '最后使用时间'
},
],
})
})
</script>
{/block}

335
app/view/admin/set.html

@ -0,0 +1,335 @@
{extend name="admin/layout" /}
{block name="title"}系统设置{/block}
{block name="main"}
<div class="container" style="padding-top:70px;">
{if $mod=='sys'}
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">系统基本设置</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="whitelist" default="{:config_get('whitelist')}"><option value="0">关闭</option><option value="1">开启</option></select>
<font color="green">开启白名单模式后,只有在<a href="/admin/list/type/white" target="_blank">白名单列表</a>中的服务器IP才能使用此云端</font>
</div>
<div class="form-group">
<label>安装脚本展示页面开关:</label>
<select class="form-control" name="download_page" default="{:config_get('download_page')}"><option value="0">关闭</option><option value="1">开启</option></select>
<font color="green">页面地址:<a href="/download" target="_blank">/download</a>,开启后可以公开访问,否则只能管理员访问</font>
</div>
<div class="form-group">
<label>宝塔Linux面板最新版本号:</label>
<input type="text" name="new_version" value="{:config_get('new_version')}" 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" rows="5" placeholder="支持HTML代码">{:config_get('update_msg')}</textarea>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group">
<label>宝塔Linux面板更新日期:</label>
<input type="date" name="update_date" value="{:config_get('update_date')}" class="form-control"/>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group">
<label>宝塔Windows面板最新版本号:</label>
<input type="text" name="new_version_win" value="{:config_get('new_version_win')}" class="form-control"/>
<font color="green">用于一键更新脚本获取最新版本号,以及检测更新接口。并确保已在/public/win/panel/放置对应版本更新包</font>
</div>
<div class="form-group">
<label>宝塔Windows面板更新日志:</label>
<textarea class="form-control" name="update_msg_win" rows="5" placeholder="支持HTML代码">{:config_get('update_msg_win')}</textarea>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group">
<label>宝塔Windows面板更新日期:</label>
<input type="date" name="update_date_win" value="{:config_get('update_date_win')}" class="form-control"/>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group">
<label>宝塔云监控最新版本号:</label>
<input type="text" name="new_version_btm" value="{:config_get('new_version_btm')}" class="form-control"/>
<font color="green">用于一键更新脚本获取最新版本号,以及检测更新接口。并确保已在/public/install/src/放置对应版本更新包</font>
</div>
<div class="form-group">
<label>宝塔云监控更新日志:</label>
<textarea class="form-control" name="update_msg_btm" rows="3" placeholder="支持HTML代码">{:config_get('update_msg_btm')}</textarea>
</div>
<div class="form-group">
<label>宝塔云监控更新日期:</label>
<input type="date" name="update_date_btm" value="{:config_get('update_date_btm')}" class="form-control"/>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">宝塔Linux面板接口设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<p>以下宝塔面板请使用官方最新脚本安装并绑定账号,用于获取最新插件列表及插件包</p>
<p><a href="/static/file/kaixin.zip">下载专用插件(Linux)</a>,在面板【软件商店】->【第三方应用】,点击【导入插件】,导入该专用插件。</p>
<div class="form-group">
<label>宝塔面板URL:</label><br/>
<input type="text" name="bt_url" value="{:config_get('bt_url')}" class="form-control"/>
<font color="green">填写规则如:<u>http://192.168.1.1:8888</u> ,不要带其他后缀</font>
</div>
<div class="form-group">
<label>宝塔面板接口密钥:</label>
<input type="text" name="bt_key" value="{:config_get('bt_key')}" class="form-control"/>
</div>
<div class="form-group text-center">
<button type="button" class="btn btn-info btn-block" id="testbturl">测试连接</button>
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">宝塔Windows面板接口设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<p>以下宝塔面板请使用官方最新脚本安装并绑定账号,用于获取最新插件列表及插件包</p>
<p><a href="/static/file/win/kaixin.zip">下载专用插件(Win)</a>,在面板【软件商店】->【第三方应用】,点击【导入插件】,导入该专用插件。</p>
<div class="form-group">
<label>宝塔面板URL:</label><br/>
<input type="text" name="wbt_url" value="{:config_get('wbt_url')}" class="form-control"/>
<font color="green">填写规则如:<u>http://192.168.1.1:8888</u> ,不要带其他后缀</font>
</div>
<div class="form-group">
<label>宝塔面板接口密钥:</label>
<input type="text" name="wbt_key" value="{:config_get('wbt_key')}" class="form-control"/>
</div>
<div class="form-group text-center">
<button type="button" class="btn btn-info btn-block" id="testbturl2">测试连接</button>
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
{elseif $mod=='task'}
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">定时任务说明</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="alert alert-info">使用以下命令可以从宝塔官方获取最新的插件列表并批量下载插件包(增量更新)。<br/>你也可以将此命令添加到crontab以使此云端的插件保持最新,建议1天执行1次。</div>
<div class="alert alert-danger">使用命令执行之后,可能会导致 /data 目录下文件权限不对,后台插件列表下载插件覆盖会报错,需要手动循环设置 /data 目录权限。</div>
<div class="alert alert-warning">上次运行时间:{$runtime|raw}</div>
<div class="list-group-item">php {:app()->getRootPath()}think updateall</div><br/>
</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">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>Linux面板批量下载插件范围:</label><br/>
<select class="form-control" name="updateall_type" default="{:config_get('updateall_type')}"><option value="0">仅免费插件</option><option value="1">免费插件+专业版插件</option><option value="2">免费插件+专业版插件+企业版插件</option></select><font color="green">(批量下载不包含所有第三方插件,第三方插件需要去手动下载。)</font>
</div>
<div class="form-group">
<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 text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
{elseif $mod=='account'}
<div class="col-xs-12 col-sm-8 col-lg-6 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="form-group">
<label>用户名:</label><br/>
<input type="text" name="username" value="{:config_get('admin_username')}" class="form-control" required/>
</div>
<div class="form-group">
<label>旧密码:</label>
<input type="password" name="oldpwd" value="" class="form-control" placeholder="请输入当前的管理员密码"/>
</div>
<div class="form-group">
<label>新密码:</label>
<input type="password" name="newpwd" value="" class="form-control" placeholder="不修改请留空"/>
</div>
<div class="form-group">
<label>重输密码:</label>
<input type="password" name="newpwd2" value="" class="form-control" placeholder="不修改请留空"/>
</div>
<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>
{/if}
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script>
$(document).ready(function(){
var items = $("select[default]");
for (i = 0; i < items.length; i++) {
$(items[i]).val($(items[i]).attr("default")||0);
}
$("#testbturl").click(function(){
var bt_url = $("input[name=bt_url]").val();
var bt_key = $("input[name=bt_key]").val();
if(bt_url == ''){
layer.alert('宝塔面板URL不能为空');return;
}
if(bt_url.indexOf('http://')==-1 && bt_url.indexOf('https://')==-1){
layer.alert('宝塔面板URL不正确');return;
}
if(bt_key == ''){
layer.alert('宝塔面板接口密钥不能为空');return;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/testbturl',
data : {bt_url:bt_url, bt_key:bt_key},
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('服务器错误');
}
});
})
$("#testbturl2").click(function(){
var wbt_url = $("input[name=wbt_url]").val();
var wbt_key = $("input[name=wbt_key]").val();
if(wbt_url == ''){
layer.alert('宝塔面板URL不能为空');return;
}
if(wbt_url.indexOf('http://')==-1 && wbt_url.indexOf('https://')==-1){
layer.alert('宝塔面板URL不正确');return;
}
if(wbt_key == ''){
layer.alert('宝塔面板接口密钥不能为空');return;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/testbturl',
data : {bt_url:wbt_url, bt_key:wbt_key},
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']});
$.ajax({
type : 'POST',
url : '/admin/set',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('设置保存成功!', {
icon: 1,
closeBtn: false
}, function(){
window.location.reload()
});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
function saveAccount(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/setaccount',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('管理账号保存成功!请重新登录。', {
icon: 1,
closeBtn: false
}, function(){
window.location.reload()
});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
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}

60
app/view/dispatch_jump.html

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>温馨提示</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<meta name="renderer" content="webkit"/>
<style type="text/css">
*{box-sizing:border-box;margin:0;padding:0;font-family:Lantinghei SC,Open Sans,Arial,Hiragino Sans GB,Microsoft YaHei,"微软雅黑",STHeiti,WenQuanYi Micro Hei,SimSun,sans-serif;-webkit-font-smoothing:antialiased}
body{padding:70px 0;background:#edf1f4;font-weight:400;font-size:1pc;-webkit-text-size-adjust:none;color:#333}
a{outline:0;color:#3498db;text-decoration:none;cursor:pointer}
.system-message{margin:20px 5%;padding:40px 20px;background:#fff;box-shadow:1px 1px 1px hsla(0,0%,39%,.1);text-align:center}
.system-message h1{margin:0;margin-bottom:9pt;color:#444;font-weight:400;font-size:40px}
.system-message .jump,.system-message .image{margin:20px 0;padding:0;padding:10px 0;font-weight:400}
.system-message .jump{font-size:14px}
.system-message .jump a{color:#333}
.system-message p{font-size:9pt;line-height:20px}
.system-message .btn{display:inline-block;margin-right:10px;width:138px;height:2pc;border:1px solid #44a0e8;border-radius:30px;color:#44a0e8;text-align:center;font-size:1pc;line-height:2pc;margin-bottom:5px;}
.success .btn{border-color:#69bf4e;color:#69bf4e}
.error .btn{border-color:#ff8992;color:#ff8992}
.info .btn{border-color:#3498db;color:#3498db}
.copyright p{width:100%;color:#919191;text-align:center;font-size:10px}
.system-message .btn-grey{border-color:#bbb;color:#bbb}
.clearfix:after{clear:both;display:block;visibility:hidden;height:0;content:"."}
@media (max-width:768px){body {padding:20px 0;}}
@media (max-width:480px){.system-message h1{font-size:30px;}}
</style>
</head>
<body>
<div class="system-message {$code}">
<div class="image">
<img src="/static/images/{$code}.svg" alt="" width="150" />
</div>
<h1>{$msg}</h1>
{if $url}
<p class="jump">
页面将在 <span id="wait">{$wait}</span> 秒后自动跳转
</p>
{/if}
<p class="clearfix">
<a href="javascript:history.go(-1);" class="btn btn-grey">返回上一页</a>
{if $url}
<a href="{$url}" class="btn btn-primary">立即跳转</a>
{/if}
</p>
</div>
<script type="text/javascript">
(function () {
var wait = document.getElementById('wait');
var interval = setInterval(function () {
var time = --wait.innerHTML;
if (time <= 0) {
location.href = "{$url}";
clearInterval(interval);
}
}, 1000);
})();
</script>
</body>
</html>

261
app/view/index/download.html

@ -0,0 +1,261 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="renderer" content="webkit" />
<meta name="force-rendering" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>宝塔面板安装脚本</title>
<link rel="stylesheet" type="text/css" href="/static/css/sanren.css" />
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<link rel="stylesheet" type="text/css" href="/static/css/download.css" />
</head>
<body>
<div class="down-main">
<div class="d1">
<div class="wrap">
<div class="i1t textcenter">
<h1 class="disflex flex_center flex_lrcenter textcenter">宝塔面板安装脚本<img class="ml10" src="/static/images/i1ico_03.png"></h1>
<div class="text20 mt_25 wap_mt15 textcenter cl8">
<p>2分钟装好面板,一键管理服务器</p>
<p>集成LAMP/LNMP环境安装,网站、FTP、数据库、文件管理、软件安装等功能</p>
</div>
<div class="disflex flex_lrcenter mt_50 install-list">
<div class="install-box linux">
<div class="img">
<img src="/static/images/prd_1_03.png">
</div>
<div class="cont">
<div class="top">
<div class="title">Linux面板 {:config_get('new_version')}</div>
<div class="desc">
支持Centos、Ubuntu、Deepin、Debian、Fedora等Linux系统。
<a class="link" href="https://demo.bt.cn/login" target="_blank" style="margin-left: 5px; font-weight: 700" rel="noreferrer">查看演示</a>
</div>
<div class="mark">
<span>2分钟装好</span>
<span>阿里云推荐</span>
<span>腾讯云推荐</span>
</div>
</div>
<div class="bottom">
<a class="btn" href="javascript:;" id="goInstallLinux">查看安装脚本</a>
</div>
</div>
</div>
<div class="install-box windows">
<div class="img">
<img src="/static/images/prd_2_03.png">
</div>
<div class="cont">
<div class="top">
<div class="title">Windows面板 {:config_get('new_version_win')}</div>
<div class="desc">支持Windows Server 2008 R2/2012/2016/2019/2022,64位系统</div>
<div class="mark">
<span>操作简单</span>
<span>使用方便</span>
</div>
</div>
<div class="bottom">
<a class="btn" href="javascript:;" id="goInstallWindows">查看安装方法</a>
</div>
</div>
</div>
</div>
{if config_get('new_version_btm')}<div class="disflex flex_lrcenter mt_30 install-list">
<div class="install-box monitor">
<div class="img">
<img src="/static/images/bt_monitor.png" />
</div>
<div class="cont">
<div class="top">
<div class="title">堡塔云监控</div>
<div class="desc">多服务监控和异常告警通知</div>
</div>
<div class="bottom">
<a class="btn" href="javascript:;" id="goInstallMonitor">查看安装脚本</a>
</div>
</div>
</div>
</div>{/if}
</div>
</div>
</div>
<div class="d2" id="instal-linux">
<div class="wrap">
<div class="wrap-title linux-title">
<div class="text">Linux面板{:config_get('new_version')}安装脚本</div>
</div>
<div class="desc">
使用 SSH 连接工具,如
<a class="link" href="https://www.putty.org/" target="_blank" rel="noreferrer">PUTTY</a>
连接到您的 Linux 服务器后,
<a class="link" href="https://www.bt.cn/bbs/thread-50002-1-1.html" target="_blank" rel="noreferrer">挂载磁盘</a>
,根据系统执行相应命令开始安装:
</div>
<div class="install-code">
<span class="osname">Centos安装脚本</span>
<div class="code-cont">
<div class="command" title="点击复制Centos安装脚本">yum install -y wget && wget -O install.sh {$siteurl}/install/install_6.0.sh && sh install.sh</div>
<span class="ico-copy" title="点击复制Centos安装脚本" data-clipboard-text="yum install -y wget && wget -O install.sh {$siteurl}/install/install_6.0.sh && sh install.sh">复制</span>
</div>
</div>
<div class="install-code">
<span class="osname">Ubuntu/Debian安装脚本</span>
<div class="code-cont">
<div class="command" title="点击复制Ubuntu/Debian安装脚本">wget -O install.sh {$siteurl}/install/install_6.0.sh && bash install.sh</div>
<span class="ico-copy" title="点击复制Ubuntu/Debian安装脚本" data-clipboard-text="wget -O install.sh {$siteurl}/install/install_6.0.sh && bash install.sh">复制</span>
</div>
</div>
<div class="install-code">
<span class="osname">一键更新脚本</span>
<div class="code-cont">
<div class="command" title="点击复制一键更新脚本">curl {$siteurl}/install/update6.sh|bash</div>
<span class="ico-copy" title="点击复制一键更新脚本" data-clipboard-text="curl {$siteurl}/install/update6.sh|bash">复制</span>
</div>
</div>
<div class="tips" style="color: orangered; font-weight: 700">
<p>注意:必须为没装过其它环境如Apache/Nginx/php/MySQL的新系统,推荐使用centos 7.X的系统安装宝塔面板</p>
<p style="text-indent: 3em">推荐使用Chrome、火狐、edge浏览器,国产浏览器请使用极速模式访问面板登录地址</p>
<p style="text-indent: 3em">如果使用过官方版或其他第三方云端的版本,使用一键更新脚本即可切换到此云端</p>
</div>
</div>
</div>
<div class="d4" id="instal-windows" style="background-color: #edf6ef;">
<div class="wrap">
<div class="wrap-title">
<div class="text">Windows面板{:config_get('new_version_win')}安装方法</div>
</div>
<div class="desc">
<p>1、使用<a class="link" href="https://download.bt.cn/win/panel/BtSoft.zip" target="_blank" rel="noreferrer">官方安装程序</a>进行安装,安装完先不要进入面板。</p>
<p>2、在命令提示符(cmd)输入以下一键更新命令,然后重启面板。</p>
</div>
<div class="install-code">
<div class="code-cont">
<div class="command" title="点击复制一键更新命令">wget {$siteurl}/win/panel/data/setup.py -O C:/update.py && &quot;C:\Program Files\python\python.exe&quot; C:/update.py update_panel {:config_get('new_version_win')}</div>
<span class="ico-copy" title="点击复制一键更新命令" data-clipboard-text="wget {$siteurl}/win/panel/data/setup.py -O C:/update.py && &quot;C:\Program Files\python\python.exe&quot; C:/update.py update_panel {:config_get('new_version_win')}">复制</span>
</div>
</div>
<div class="tips" style="color: orangered; font-weight: 700">
<p>注意:仅支持Windows Server 2008 R2/2012/2016/2019/2022,64位系统(中文简体),且未安装其它环境</p>
</div>
</div>
</div>
{if config_get('new_version_btm')}
<div class="d4" id="instal-monitor">
<div class="wrap">
<div class="wrap-title">
<div class="text" style="margin-right: 12px;">堡塔云监控{:config_get('new_version_btm')}安装脚本</div>
</div>
<div class="desc">
使用 SSH 连接工具,如
<a class="link" href="https://www.putty.org/" target="_blank" rel="noreferrer">PUTTY</a>
连接到您的 Linux 服务器后,根据系统执行相应命令开始安装:
</div>
<div class="install-code">
<span class="osname">堡塔云监控安装脚本</span>
<div class="code-cont">
<div class="command" title="点击复制安装脚本">curl -sS {$siteurl}/install/install_btmonitor.sh -o /tmp/install_btmonitor.sh && bash /tmp/install_btmonitor.sh</div>
<span class="ico-copy" title="点击复制安装脚本" data-clipboard-text="curl -sS {$siteurl}/install/install_btmonitor.sh -o /tmp/install_btmonitor.sh && bash /tmp/install_btmonitor.sh">复制</span>
</div>
</div>
<div class="install-code">
<span class="osname">堡塔云监控更新脚本</span>
<div class="code-cont">
<div class="command" title="点击复制更新脚本">curl {$siteurl}/install/update_btmonitor.sh|bash</div>
<span class="ico-copy" title="点击复制更新脚本" data-clipboard-text="curl {$siteurl}/install/update_btmonitor.sh|bash">复制</span>
</div>
</div>
<div class="tips" style="color: orangered; font-weight: 700">
<p>注意:推荐使用Chrome、火狐、edge浏览器,国产浏览器(极速模式)</p>
</div>
</div>
</div>{/if}
<div class="animate-bg"></div>
</div>
<div class="foot">
<div class="wrap">
<div class="fb textcenter">
<div class="fb1 textcenter">
<a href="http://www.bt.cn/new/agreement_open.html" target="_blank" rel="noreferrer">《开源许可协议》</a>
<i></i>
<a href="http://www.bt.cn/new/agreement_user.html" target="_blank" rel="noreferrer">《用户协议》</a>
<i></i>
<a href="http://www.bt.cn/new/agreement_privacy.html" target="_blank" rel="noreferrer">《隐私声明》</a>
</div>
<div class="fb2 mt_15">
<p>
Copyright © {:date('Y')} 宝塔面板安装脚本
</p>
</div>
</div>
</div>
</div>
<script src="//cdn.staticfile.org/jquery/3.6.0/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" src="//cdn.staticfile.org/clipboard.js/1.7.1/clipboard.min.js"></script>
<script type="text/javascript" src="/static/js/dx.js"></script>
<script>
$(function () {
var userId = '';
// 复制安装命令
var clipboard = new Clipboard('.ico-copy', {
text: function (element) {
return $(element).prev().text();
},
});
clipboard
.on('success', function (e) {
layer.msg(e.trigger.title + '成功', { icon: 1 });
})
.on('error', function (e) {
layer.msg('复制失败,请手动选中文本Ctrl+c复制内容', { icon: 2 });
});
$('.install-code .command').click(function () {
$(this).next('.ico-copy').click();
});
$('#goInstallLinux').click(function () {
scrollTop('#instal-linux');
});
$('#goInstallWindows').click(function () {
scrollTop('#instal-windows');
});
$('#goInstallCloud').click(function () {
scrollTop('#instal-cloud');
});
$('#goInstallMonitor').click(function () {
scrollTop('#instal-monitor');
});
function GetRequest() {
var url = location.search;
//获取url中"?"符后的字串
var theRequest = new Object();
if (url.indexOf('?') != -1) {
var str = url.substr(1);
}
return str;
}
if (GetRequest() == 'bt') {
scrollTop('#instal-linux');
}
// 滚动到指定位置
function scrollTop(el) {
var headHeight = 0;
$('html, body').animate({ scrollTop: $(el).offset().top - headHeight }, { duration: 200, easing: 'swing' });
}
});
</script>
</body>
</html>

268
app/view/install/index.html

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

41
composer.json

@ -0,0 +1,41 @@
{
"name": "btpanel/cloud",
"description": "BTPanel third cloud site",
"type": "project",
"keywords": [
"btpanel",
"baota",
"aapanel"
],
"homepage": "https://www.bt.cn/",
"license": "Apache-2.0",
"authors": [],
"require": {
"php": ">=7.2.5",
"topthink/framework": "^6.0.0",
"topthink/think-orm": "^2.0",
"topthink/think-view": "^1.0",
"topthink/think-captcha": "^3.0"
},
"require-dev": {
"symfony/var-dumper": "^4.2",
"topthink/think-trace":"^1.0"
},
"autoload": {
"psr-4": {
"app\\": "app"
},
"psr-0": {
"": "extend/"
}
},
"config": {
"preferred-install": "dist"
},
"scripts": {
"post-autoload-dump": [
"@php think service:discover",
"@php think vendor:publish"
]
}
}

32
config/app.php

@ -0,0 +1,32 @@
<?php
// +----------------------------------------------------------------------
// | 应用设置
// +----------------------------------------------------------------------
return [
// 应用地址
'app_host' => env('app.host', ''),
// 应用的命名空间
'app_namespace' => '',
// 是否启用路由
'with_route' => true,
// 默认应用
'default_app' => 'index',
// 默认时区
'default_timezone' => 'Asia/Shanghai',
// 应用映射(自动多应用模式有效)
'app_map' => [],
// 域名绑定(自动多应用模式有效)
'domain_bind' => [],
// 禁止URL访问的应用列表(自动多应用模式有效)
'deny_app_list' => [],
// 异常页面的模板文件
'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl',
// 错误显示信息,非调试模式有效
'error_message' => '页面错误!请稍后再试~',
// 显示错误信息
'show_error_msg' => true,
];

40
config/cache.php

@ -0,0 +1,40 @@
<?php
// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------
return [
// 默认缓存驱动
'default' => env('cache.driver', 'file'),
// 缓存连接方式配置
'stores' => [
'file' => [
// 驱动方式
'type' => 'File',
// 缓存保存目录
'path' => '',
// 缓存前缀
'prefix' => '',
// 缓存有效期 0表示永久缓存
'expire' => 0,
// 缓存标签前缀
'tag_prefix' => 'tag:',
// 序列化机制 例如 ['serialize', 'unserialize']
'serialize' => [],
],
'redis' => [
// 驱动方式
'type' => 'Redis',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0,
// 缓存有效期 0表示永久缓存
'expire' => 3600,
'prefix' => '',
],
// 更多的缓存连接
],
];

39
config/captcha.php

@ -0,0 +1,39 @@
<?php
// +----------------------------------------------------------------------
// | Captcha配置文件
// +----------------------------------------------------------------------
return [
//验证码位数
'length' => 4,
// 验证码字符集合
'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY',
// 验证码过期时间
'expire' => 1800,
// 是否使用中文验证码
'useZh' => false,
// 是否使用算术验证码
'math' => false,
// 是否使用背景图
'useImgBg' => false,
//验证码字符大小
'fontSize' => 25,
// 是否使用混淆曲线
'useCurve' => true,
//是否添加杂点
'useNoise' => true,
// 验证码字体 不设置则随机
'fontttf' => '',
//背景颜色
'bg' => [243, 251, 254],
// 验证码图片高度
'imageH' => 0,
// 验证码图片宽度
'imageW' => 0,
// 添加额外的验证码设置
// verify => [
// 'length'=>4,
// ...
//],
];

11
config/console.php

@ -0,0 +1,11 @@
<?php
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------
return [
// 指令定义
'commands' => [
'updateall' => 'app\command\UpdateAll',
'decrypt' => 'app\command\DecryptFile',
],
];

20
config/cookie.php

@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | Cookie设置
// +----------------------------------------------------------------------
return [
// cookie 保存时间
'expire' => 0,
// cookie 保存路径
'path' => '/',
// cookie 有效域名
'domain' => '',
// cookie 启用安全传输
'secure' => false,
// httponly设置
'httponly' => false,
// 是否使用 setcookie
'setcookie' => true,
// samesite 设置,支持 'strict' 'lax'
'samesite' => '',
];

63
config/database.php

@ -0,0 +1,63 @@
<?php
return [
// 默认使用的数据库连接配置
'default' => env('database.driver', 'mysql'),
// 自定义时间查询规则
'time_query_rule' => [],
// 自动写入时间戳字段
// true为自动识别类型 false关闭
// 字符串则明确指定时间字段类型 支持 int timestamp datetime date
'auto_timestamp' => true,
// 时间字段取出后的默认时间格式
'datetime_format' => 'Y-m-d H:i:s',
// 时间字段配置 配置格式:create_time,update_time
'datetime_field' => '',
// 数据库连接配置信息
'connections' => [
'mysql' => [
// 数据库类型
'type' => env('database.type', 'mysql'),
// 服务器地址
'hostname' => env('database.hostname', '127.0.0.1'),
// 数据库名
'database' => env('database.database', ''),
// 用户名
'username' => env('database.username', 'root'),
// 密码
'password' => env('database.password', ''),
// 端口
'hostport' => env('database.hostport', '3306'),
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => env('database.charset', 'utf8mb4'),
// 数据库表前缀
'prefix' => env('database.prefix', ''),
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 是否严格检查字段是否存在
'fields_strict' => true,
// 是否需要断线重连
'break_reconnect' => false,
// 监听SQL
'trigger_sql' => env('app_debug', true),
// 开启字段缓存
'fields_cache' => false,
],
// 更多的数据库配置信息
],
];

24
config/filesystem.php

@ -0,0 +1,24 @@
<?php
return [
// 默认磁盘
'default' => env('filesystem.driver', 'local'),
// 磁盘列表
'disks' => [
'local' => [
'type' => 'local',
'root' => app()->getRuntimePath() . 'storage',
],
'public' => [
// 磁盘类型
'type' => 'local',
// 磁盘路径
'root' => app()->getRootPath() . 'public/storage',
// 磁盘路径对应的外部URL路径
'url' => '/storage',
// 可见性
'visibility' => 'public',
],
// 更多的磁盘配置信息
],
];

27
config/lang.php

@ -0,0 +1,27 @@
<?php
// +----------------------------------------------------------------------
// | 多语言设置
// +----------------------------------------------------------------------
return [
// 默认语言
'default_lang' => env('lang.default_lang', 'zh-cn'),
// 允许的语言列表
'allow_lang_list' => [],
// 多语言自动侦测变量名
'detect_var' => 'lang',
// 是否使用Cookie记录
'use_cookie' => true,
// 多语言cookie变量
'cookie_var' => 'think_lang',
// 多语言header变量
'header_var' => 'think-lang',
// 扩展语言包
'extend_list' => [],
// Accept-Language转义为对应语言包名称
'accept_language' => [
'zh-hans-cn' => 'zh-cn',
],
// 是否支持语言分组
'allow_group' => false,
];

45
config/log.php

@ -0,0 +1,45 @@
<?php
// +----------------------------------------------------------------------
// | 日志设置
// +----------------------------------------------------------------------
return [
// 默认日志记录通道
'default' => env('log.channel', 'file'),
// 日志记录级别
'level' => [],
// 日志类型记录的通道 ['error'=>'email',...]
'type_channel' => [],
// 关闭全局日志写入
'close' => false,
// 全局日志处理 支持闭包
'processor' => null,
// 日志通道列表
'channels' => [
'file' => [
// 日志记录方式
'type' => 'File',
// 日志保存目录
'path' => '',
// 单文件日志写入
'single' => false,
// 独立日志级别
'apart_level' => [],
// 最大日志文件数量
'max_files' => 0,
// 使用JSON格式记录
'json' => false,
// 日志处理
'processor' => null,
// 关闭通道日志写入
'close' => false,
// 日志输出格式化
'format' => '[%s][%s] %s',
// 是否实时写入
'realtime_write' => false,
],
// 其它日志通道配置
],
];

8
config/middleware.php

@ -0,0 +1,8 @@
<?php
// 中间件配置
return [
// 别名或分组
'alias' => [],
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
'priority' => [],
];

45
config/route.php

@ -0,0 +1,45 @@
<?php
// +----------------------------------------------------------------------
// | 路由设置
// +----------------------------------------------------------------------
return [
// pathinfo分隔符
'pathinfo_depr' => '/',
// URL伪静态后缀
'url_html_suffix' => '',
// URL普通方式参数 用于自动生成
'url_common_param' => true,
// 是否开启路由延迟解析
'url_lazy_route' => false,
// 是否强制使用路由
'url_route_must' => true,
// 合并路由规则
'route_rule_merge' => false,
// 路由是否完全匹配
'route_complete_match' => false,
// 访问控制器层名称
'controller_layer' => 'controller',
// 空控制器名
'empty_controller' => 'Error',
// 是否使用控制器后缀
'controller_suffix' => false,
// 默认的路由变量规则
'default_route_pattern' => '[\w\.]+',
// 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
'request_cache_key' => false,
// 请求缓存有效期
'request_cache_expire' => null,
// 全局请求缓存排除规则
'request_cache_except' => [],
// 默认控制器名
'default_controller' => 'Index',
// 默认操作名
'default_action' => 'index',
// 操作方法后缀
'action_suffix' => '',
// 默认JSONP格式返回的处理方法
'default_jsonp_handler' => 'jsonpReturn',
// 默认JSONP处理方法
'var_jsonp_handler' => 'callback',
];

19
config/session.php

@ -0,0 +1,19 @@
<?php
// +----------------------------------------------------------------------
// | 会话设置
// +----------------------------------------------------------------------
return [
// session name
'name' => 'PHPSESSID',
// SESSION_ID的提交变量,解决flash上传跨域
'var_session_id' => '',
// 驱动方式 支持file cache
'type' => 'file',
// 存储连接标识 当type使用cache的时候有效
'store' => null,
// 过期时间
'expire' => 1440,
// 前缀
'prefix' => '',
];

10
config/trace.php

@ -0,0 +1,10 @@
<?php
// +----------------------------------------------------------------------
// | Trace设置 开启调试模式后有效
// +----------------------------------------------------------------------
return [
// 内置Html和Console两种方式 支持扩展
'type' => 'Html',
// 读取的日志通道名
'channel' => '',
];

25
config/view.php

@ -0,0 +1,25 @@
<?php
// +----------------------------------------------------------------------
// | 模板设置
// +----------------------------------------------------------------------
return [
// 模板引擎类型使用Think
'type' => 'Think',
// 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
'auto_rule' => 1,
// 模板目录名
'view_dir_name' => 'view',
// 模板后缀
'view_suffix' => 'html',
// 模板文件名分隔符
'view_depr' => DIRECTORY_SEPARATOR,
// 模板引擎普通标签开始标记
'tpl_begin' => '{',
// 模板引擎普通标签结束标记
'tpl_end' => '}',
// 标签库标签开始标记
'taglib_begin' => '{',
// 标签库标签结束标记
'taglib_end' => '}',
];

1
data/config/deployment_list.json
File diff suppressed because it is too large
View File

1
data/config/plugin_list.json
File diff suppressed because it is too large
View File

0
data/plugins/folder/.gitkeep

0
data/plugins/main/.gitkeep

0
data/plugins/other/other/.gitkeep

0
data/plugins/package/.gitkeep

1
data/win/config/deployment_list.json
File diff suppressed because it is too large
View File

1
data/win/config/plugin_list.json
File diff suppressed because it is too large
View File

0
data/win/plugins/folder/.gitkeep

0
data/win/plugins/main/.gitkeep

0
data/win/plugins/package/.gitkeep

66
install.sql

@ -0,0 +1,66 @@
DROP TABLE IF EXISTS `cloud_config`;
CREATE TABLE `cloud_config` (
`key` varchar(32) NOT NULL,
`value` varchar(255) DEFAULT NULL,
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `cloud_config` (`key`, `value`) VALUES
('admin_username', 'admin'),
('admin_password', '123456'),
('bt_url', ''),
('bt_key', ''),
('whitelist', '0'),
('download_page', '1'),
('new_version', '8.0.1'),
('update_msg', '暂无更新日志'),
('update_date', '2023-07-20'),
('new_version_win', '7.9.0'),
('update_msg_win', '暂无更新日志'),
('update_date_win', '2023-07-20'),
('new_version_btm', '2.2.5'),
('update_msg_btm', '暂无更新日志'),
('update_date_btm', '2023-07-06'),
('updateall_type', '0'),
('syskey', 'UqP94LtI8eWAIgCP');
DROP TABLE IF EXISTS `cloud_black`;
CREATE TABLE `cloud_black` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip` varchar(20) NOT NULL,
`enable` tinyint(1) NOT NULL DEFAULT '1',
`addtime` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ip`(`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `cloud_white`;
CREATE TABLE `cloud_white` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip` varchar(20) NOT NULL,
`enable` tinyint(1) NOT NULL DEFAULT '1',
`addtime` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ip`(`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `cloud_record`;
CREATE TABLE `cloud_record` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip` varchar(20) NOT NULL,
`addtime` datetime NOT NULL,
`usetime` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ip`(`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `cloud_log`;
CREATE TABLE `cloud_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uid` tinyint(4) NOT NULL DEFAULT '1',
`action` varchar(40) NOT NULL,
`data` varchar(150) DEFAULT NULL,
`addtime` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

8
public/.htaccess

@ -0,0 +1,8 @@
<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>

24
public/index.php

@ -0,0 +1,24 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <[email protected]>
// +----------------------------------------------------------------------
// [ 应用入口文件 ]
namespace think;
require __DIR__ . '/../vendor/autoload.php';
// 执行HTTP应用并响应
$http = (new App())->http;
$response = $http->run();
$response->send();
$http->end($response);

1024
public/install/install_6.0.sh
File diff suppressed because it is too large
View File

880
public/install/install_btmonitor.sh

@ -0,0 +1,880 @@
#!/bin/bash
#########################
# 广东堡塔安全技术有限公司
# author: 赤井秀一
# mail: [email protected]
#########################
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
LANG=en_US.UTF-8
Btapi_Url='http://www.example.com'
is64bit=$(getconf LONG_BIT)
if [ "${is64bit}" != '64' ];then
echo -e "\033[31m 抱歉, 堡塔云监控系统不支持32位系统, 请使用64位系统! \033[0m"
exit 1
fi
S390X_CHECK=$(uname -a|grep s390x)
if [ "${S390X_CHECK}" ];then
echo -e "\033[31m 抱歉, 堡塔云监控系统不支持s390x架构进行安装,请使用x86_64服务器架构 \033[0m"
exit 1
fi
is_aarch64=$(uname -a|grep aarch64)
if [ "${is_aarch64}" != "" ];then
echo -e "\033[31m 抱歉, 堡塔云监控系统暂不支持aarch64架构进行安装,请使用x86_64服务器架构 \033[0m"
exit 1
fi
Command_Exists() {
command -v "$@" >/dev/null 2>&1
}
GetSysInfo(){
if [ -s "/etc/redhat-release" ];then
SYS_VERSION=$(cat /etc/redhat-release)
elif [ -s "/etc/issue" ]; then
SYS_VERSION=$(cat /etc/issue)
fi
SYS_INFO=$(uname -a)
SYS_BIT=$(getconf LONG_BIT)
MEM_TOTAL=$(free -m|grep Mem|awk '{print $2}')
CPU_INFO=$(getconf _NPROCESSORS_ONLN)
echo -e ${SYS_VERSION}
echo -e Bit:${SYS_BIT} Mem:${MEM_TOTAL}M Core:${CPU_INFO}
echo -e ${SYS_INFO}
echo -e "请截图以上报错信息发帖至论坛www.bt.cn/bbs求助"
}
Red_Error(){
echo '=================================================';
printf '\033[1;31;40m%b\033[0m\n' "$@";
GetSysInfo
exit 1;
}
monitor_path="/www/server/bt-monitor"
run_bin="/www/server/bt-monitor/BT-MONITOR"
if [ ! -d "/www/server" ];then
mkdir -p /www/server
fi
old_dir="/www/server/old_btmonitor"
cd ~
setup_path="/www"
python_bin=$setup_path/server/bt-monitor/pyenv/bin/python
cpu_cpunt=$(cat /proc/cpuinfo|grep processor|wc -l)
get_node_url(){
if [ ! -f /bin/curl ];then
if [ "${PM}" = "yum" ]; then
yum install curl -y
elif [ "${PM}" = "apt-get" ]; then
apt-get install curl -y
fi
fi
if [ -f "/www/node.pl" ];then
download_Url=$(cat /www/node.pl)
echo "Download node: $download_Url";
echo '---------------------------------------------';
return
fi
echo '---------------------------------------------';
echo "Selected download node...";
# nodes=(http://dg2.bt.cn http://dg1.bt.cn http://125.90.93.52:5880 http://36.133.1.8:5880 http://123.129.198.197 http://38.34.185.130 http://116.213.43.206:5880 http://128.1.164.196);
#nodes=(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://116.213.43.206:5880 http://128.1.164.196);
nodes=(https://dg2.bt.cn https://dg1.bt.cn https://download.bt.cn);
tmp_file1=/dev/shm/net_test1.pl
tmp_file2=/dev/shm/net_test2.pl
[ -f "${tmp_file1}" ] && rm -f ${tmp_file1}
[ -f "${tmp_file2}" ] && rm -f ${tmp_file2}
touch $tmp_file1
touch $tmp_file2
for node in ${nodes[@]};
do
NODE_CHECK=$(curl --connect-timeout 3 -m 3 2>/dev/null -w "%{http_code} %{time_total}" ${node}/net_test|xargs)
RES=$(echo ${NODE_CHECK}|awk '{print $1}')
NODE_STATUS=$(echo ${NODE_CHECK}|awk '{print $2}')
TIME_TOTAL=$(echo ${NODE_CHECK}|awk '{print $3 * 1000 - 500 }'|cut -d '.' -f 1)
if [ "${NODE_STATUS}" == "200" ];then
if [ $TIME_TOTAL -lt 100 ];then
if [ $RES -ge 1500 ];then
echo "$RES $node" >> $tmp_file1
fi
else
if [ $RES -ge 1500 ];then
echo "$TIME_TOTAL $node" >> $tmp_file2
fi
fi
i=$(($i+1))
if [ $TIME_TOTAL -lt 100 ];then
if [ $RES -ge 3000 ];then
break;
fi
fi
fi
done
NODE_URL=$(cat $tmp_file1|sort -r -g -t " " -k 1|head -n 1|awk '{print $2}')
if [ -z "$NODE_URL" ];then
NODE_URL=$(cat $tmp_file2|sort -g -t " " -k 1|head -n 1|awk '{print $2}')
if [ -z "$NODE_URL" ];then
NODE_URL='https://download.bt.cn';
fi
fi
rm -f $tmp_file1
rm -f $tmp_file2
download_Url=$NODE_URL
echo "Download node: $download_Url";
echo '---------------------------------------------';
}
Get_Versions(){
redhat_version_file="/etc/redhat-release"
deb_version_file="/etc/issue"
if [ -f $redhat_version_file ];then
os_type='el'
is_aliyunos=$(cat $redhat_version_file|grep Aliyun)
if [ "$is_aliyunos" != "" ];then
return
fi
os_version=$(cat $redhat_version_file|grep CentOS|grep -Eo '([0-9]+\.)+[0-9]+'|grep -Eo '^[0-9]')
if [ "${os_version}" = "5" ];then
os_version=""
fi
if [ -z "${os_version}" ];then
os_version=$(cat /etc/redhat-release |grep Stream|grep -oE 8)
fi
else
os_type='ubuntu'
os_version=$(cat $deb_version_file|grep Ubuntu|grep -Eo '([0-9]+\.)+[0-9]+'|grep -Eo '^[0-9]+')
if [ "${os_version}" = "" ];then
os_type='debian'
os_version=$(cat $deb_version_file|grep Debian|grep -Eo '([0-9]+\.)+[0-9]+'|grep -Eo '[0-9]+')
if [ "${os_version}" = "" ];then
os_version=$(cat $deb_version_file|grep Debian|grep -Eo '[0-9]+')
fi
if [ "${os_version}" = "8" ];then
os_version=""
fi
if [ "${is64bit}" = '32' ];then
os_version=""
fi
else
if [ "$os_version" = "14" ];then
os_version=""
fi
if [ "$os_version" = "12" ];then
os_version=""
fi
if [ "$os_version" = "19" ];then
os_version=""
fi
if [ "$os_version" = "21" ];then
os_version=""
fi
if [ "$os_version" = "20" ];then
os_version2004=$(cat /etc/issue|grep 20.04)
if [ -z "${os_version2004}" ];then
os_version=""
fi
fi
fi
fi
}
Install_Python_Lib(){
curl -Ss --connect-timeout 3 -m 60 $download_Url/install/pip_select.sh|bash
pyenv_path="/www/server/bt-monitor"
if [ -f $pyenv_path/pyenv/bin/python ];then
is_ssl=$($python_bin -c "import ssl" 2>&1|grep cannot)
$pyenv_path/pyenv/bin/python3.7 -V
if [ $? -eq 0 ] && [ -z "${is_ssl}" ];then
chmod -R 700 $pyenv_path/pyenv/bin
is_package=$($python_bin -m psutil 2>&1|grep package)
if [ "$is_package" = "" ];then
wget -O $pyenv_path/pyenv/pip.txt $download_Url/install/pyenv/pip.txt -t 5 -T 10
$pyenv_path/pyenv/bin/pip install -U pip
$pyenv_path/pyenv/bin/pip install -U setuptools
$pyenv_path/pyenv/bin/pip install -r $pyenv_path/pyenv/pip.txt
$pyenv_path/pyenv/bin/pip install -U flask==2.2.0
$pyenv_path/pyenv/bin/pip install flask_sock
$pyenv_path/pyenv/bin/pip install cachelib
$pyenv_path/pyenv/bin/pip install py7zr
$pyenv_path/pyenv/bin/pip install backports.lzma
$pyenv_path/pyenv/bin/pip install pandas
$pyenv_path/pyenv/bin/pip install msgpack
fi
source $pyenv_path/pyenv/bin/activate
chmod -R 700 $pyenv_path/pyenv/bin
return
else
rm -rf $pyenv_path/pyenv
fi
fi
py_version="3.7.9"
if [ ! -d "$pyenv_path" ]; then
mkdir -p $pyenv_path
fi
echo "True" > /www/disk.pl
if [ ! -w /www/disk.pl ];then
Red_Error "ERROR: Install python env fielded." "ERROR: /www目录无法写入,请检查目录/用户/磁盘权限!"
fi
os_type='el'
os_version='7'
is_export_openssl=0
Get_Versions
echo "OS: $os_type - $os_version"
is_aarch64=$(uname -a|grep aarch64)
if [ "$is_aarch64" != "" ];then
is64bit="aarch64"
fi
if [ -f "/www/server/bt-monitor/pymake.pl" ];then
os_version=""
rm -f /www/server/bt-monitor/pymake.pl
fi
if [[ $os_type =~ "debian" ]] || [[ $os_type =~ "ubuntu" ]]; then
isbtm="-btm"
fi
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}${isbtm}.tar.gz -t 5 -T 10
tmp_size=$(du -b $pyenv_file|awk '{print $1}')
if [ $tmp_size -lt 703460 ];then
rm -f $pyenv_file
echo "ERROR: Download python env fielded."
else
echo "Install python env..."
tar zxvf $pyenv_file -C $pyenv_path/ > /dev/null
chmod -R 700 $pyenv_path/pyenv/bin
rm -rf $pyenv_path/pyenv/bin/python
ln -sf $pyenv_path/pyenv/bin/python3.7 $pyenv_path/pyenv/bin/python
$pyenv_path/pyenv/bin/python -m pip install --upgrade --force-reinstall pip
$pyenv_path/pyenv/bin/pip install -U flask==2.2.0
$pyenv_path/pyenv/bin/pip install flask_sock
$pyenv_path/pyenv/bin/pip install cachelib
$pyenv_path/pyenv/bin/pip install py7zr
$pyenv_path/pyenv/bin/pip install backports.lzma
$pyenv_path/pyenv/bin/pip install pandas
$pyenv_path/pyenv/bin/pip install msgpack
if [ ! -f $pyenv_path/pyenv/bin/python ];then
rm -f $pyenv_file
Red_Error "ERROR: Install python env fielded." "ERROR: 下载堡塔云监控主控端运行环境失败,请尝试重新安装!"
fi
$pyenv_path/pyenv/bin/python3.7 -V
if [ $? -eq 0 ];then
rm -f $pyenv_file
ln -sf $pyenv_path/pyenv/bin/pip3.7 /usr/bin/btmpip
ln -sf $pyenv_path/pyenv/bin/python3.7 /usr/bin/btmpython
source $pyenv_path/pyenv/bin/activate
return
else
rm -f $pyenv_file
rm -rf $pyenv_path/pyenv
fi
fi
fi
cd /www
python_src='/www/python_src.tar.xz'
python_src_path="/www/Python-${py_version}"
wget -O $python_src $download_Url/src/Python-${py_version}.tar.xz -t 5 -T 10
tmp_size=$(du -b $python_src|awk '{print $1}')
if [ $tmp_size -lt 10703460 ];then
rm -f $python_src
Red_Error "ERROR: Download python source code fielded." "ERROR: 下载堡塔云监控主控端运行环境失败,请尝试重新安装!"
fi
tar xvf $python_src
rm -f $python_src
cd $python_src_path
./configure --prefix=$pyenv_path/pyenv
make -j$cpu_cpunt
make install
if [ ! -f $pyenv_path/pyenv/bin/python3.7 ];then
rm -rf $python_src_path
Red_Error "ERROR: Make python env fielded." "ERROR: 编译堡塔云监控主控端运行环境失败!"
fi
cd ~
rm -rf $python_src_path
wget -O $pyenv_path/pyenv/bin/activate $download_Url/install/pyenv/activate.panel -t 5 -T 10
wget -O $pyenv_path/pyenv/pip.txt $download_Url/install/pyenv/pip-3.7.8.txt -t 5 -T 10
ln -sf $pyenv_path/pyenv/bin/pip3.7 $pyenv_path/pyenv/bin/pip
ln -sf $pyenv_path/pyenv/bin/python3.7 $pyenv_path/pyenv/bin/python
ln -sf $pyenv_path/pyenv/bin/pip3.7 /usr/bin/btmpip
ln -sf $pyenv_path/pyenv/bin/python3.7 /usr/bin/btmpython
chmod -R 700 $pyenv_path/pyenv/bin
$pyenv_path/pyenv/bin/pip install -U pip
$pyenv_path/pyenv/bin/pip install -U setuptools
$pyenv_path/pyenv/bin/pip install -U wheel==0.34.2
$pyenv_path/pyenv/bin/pip install -r $pyenv_path/pyenv/pip.txt
$pyenv_path/pyenv/bin/pip install -U flask==2.2.0
$pyenv_path/pyenv/bin/pip install flask_sock
$pyenv_path/pyenv/bin/pip install cachelib
$pyenv_path/pyenv/bin/pip install py7zr
$pyenv_path/pyenv/bin/pip install backports.lzma
$pyenv_path/pyenv/bin/pip install pandas
$pyenv_path/pyenv/bin/pip install msgpack
source $pyenv_path/pyenv/bin/activate
is_gevent=$($python_bin -m gevent 2>&1|grep -oE package)
is_psutil=$($python_bin -m psutil 2>&1|grep -oE package)
if [ "${is_gevent}" != "${is_psutil}" ];then
Red_Error "ERROR: psutil/gevent install failed!"
fi
}
Install_Monitor(){
ulimit -n 1000001
tee -a /etc/security/limits.conf << EOF
* hard nofile 1000001
* soft nofile 1000001
root hard nofile 1000001
root soft nofile 1000001
EOF
sysctl -p
panelPort="806"
if [ ! -d "/etc/init.d" ];then
mkdir -p /etc/init.d
fi
if [ -f "/etc/init.d/btm" ]; then
/etc/init.d/btm stop
sleep 1
fi
if [ -f "/www/server/bt-monitor/sqlite-server.sh" ]; then
chmod +x /www/server/bt-monitor/sqlite-server.sh
/www/server/bt-monitor/sqlite-server.sh stop
sleep 1
fi
version="2.1.7"
file_name="bt-monitor"
agent_src="bt-monitor.zip"
cd ~
version=`curl -sf ${Btapi_Url}/bt_monitor/latest_version |awk -F '\"version\"' '{print $2}'|awk -F ':' '{print $2}'|awk -F '"' '{print $2}'`
if [ -z $version ]; then
version="2.0.6"
fi
if [ "$re_install" == "1" ]; then
new_dir="/www/server/new_btmonitor"
if [ ! -d "$new_dir" ];then
mkdir -p $new_dir
fi
wget -O $agent_src ${Btapi_Url}/install/src/$file_name-$version.zip -t 5 -T 10
unzip -o $agent_src -d $new_dir/ > /dev/null
if [ ! -f $new_dir/BT-MONITOR ];then
ls -lh $agent_src
Red_Error "ERROR: Failed to download, please try install again!" "ERROR: 下载堡塔云监控主控端失败,请尝试重新安装!"
fi
rm -rf $new_dir/config
rm -rf $new_dir/data
rm -rf $new_dir/ssl
\cp -r $new_dir/* $monitor_path/
rm -rf $new_dir
else
wget -O $agent_src ${Btapi_Url}/install/src/$file_name-$version.zip -t 5 -T 10
if [ ! -d "$monitor_path" ]; then
mkdir -p $monitor_path
fi
unzip -o $agent_src -d $monitor_path/ > /dev/null
if [ ! -f $run_bin ];then
ls -lh $agent_src
Red_Error "ERROR: Failed to download, please try install again!" "ERROR: 下载堡塔云监控主控端失败,请尝试重新安装!"
fi
fi
rm -rf $agent_src
chmod +x $monitor_path/BT-MONITOR
chmod +x $monitor_path/tools.py
wget -O /etc/init.d/btm ${download_Url}/init/btmonitor.init -t 5 -T 10
# \cp -r $monitor_path/init.sh /etc/init.d/btm
chmod +x /etc/init.d/btm
ln -sf /etc/init.d/btm /usr/bin/btm
if [ ! -f $monitor_path/data/user.json ]; then
echo "{\"uid\":1,\"username\":\"Administrator\",\"ip\":\"127.0.0.1\",\"server_id\":\"1\",\"access_key\":\"test\",\"secret_key\":\"123456\"}" > $monitor_path/data/user.json
fi
if [ -f $monitor_path/core/include/c_loader/PluginLoader.so ]; then
rm -f $monitor_path/core/include/c_loader/PluginLoader.so
fi
}
Start_Monitor(){
/etc/init.d/btm start
if [ "$?" != "0" ]; then
#echo "堡塔云监控主控端启动失败!"
tail $monitor_path/logs/error.log
Red_Error "堡塔云监控主控端启动失败!"
fi
echo "正在初始化云监控主控端..."
if [ "$re_install" == "1" ] || [ "$re_install" == "2" ]; then
user_pass=`$setup_path/server/bt-monitor/tools.py reset_pwd`
password=`echo $user_pass |awk '{print $3}'`
else
user_pass=`$monitor_path/tools.py create_admin`
password=`echo $user_pass |awk -F " " '{print $5}'`
for ((i=1; i<=5; i++));do
if [ -z "$password" ]; then
sleep 7
user_pass=`$monitor_path/tools.py create_admin`
password=`echo $user_pass |awk -F " " '{print $5}'`
else
i=5
fi
done
fi
if [[ "$password" == "" ]];then
Red_Error "ERROR: 初始化云监控主控端失败,请尝试重新安装!"
fi
c_path=$(cat /www/server/bt-monitor/config/config.json |awk -F '\"admin_path\"' '{print $2}'|awk -F ":" '{print $2}'|awk -F '"' '{print $2}')
adminpath=$(echo $c_path|awk -F ',' '{print $1}')
if [ -d "/usr/bin/btmonitoragent" ];then
rm -rf /usr/bin/btmonitoragent
fi
date_f=`date '+%Y%m%d_%H%M%S'`
md5_pl=`echo $date_f | md5sum | head -c 32`
token_pl=`cat $monitor_path/config/token.pl 2>&1`
if [ "$token_pl" == ' ' ] || [ ! -f $monitor_path/config/token.pl ]; then
echo "$md5_pl" > $monitor_path/config/token.pl
fi
echo "正在给本机安装云监控被控端,请等待..."
sleep 15
curl -sSO ${download_Url}/install/btmonitoragent.sh && bash btmonitoragent.sh https://127.0.0.1:806 $md5_pl
target_dir="/usr/local/btmonitoragent"
if [ ! -f "$target_dir/BT-MonitorAgent" ];then
tail -n 10 ${monitor_path}/logs/error.log
echo ""
ps aux|grep -v grep|grep ${monitor_path}
netstat -tulnp|grep ${panelPort}
/etc/init.d/btm restart
if [ "$?" -eq 0 ]; then
echo -e "\033[31m安装云监控被控端失败,正在尝试重新安装!\033[0m"
sleep 15
curl -sSO ${download_Url}/install/btmonitoragent.sh && bash btmonitoragent.sh https://127.0.0.1:806 $md5_pl
if [ ! -f "$target_dir/BT-MonitorAgent" ];then
Red_Error "ERROR: 安装云监控被控端失败,请尝试重新安装!"
fi
else
Red_Error "ERROR: 安装云监控被控端失败,请尝试重新安装!"
fi
fi
/etc/init.d/btm restart > /dev/null 2>&1
}
Set_Firewall(){
sshPort=$(cat /etc/ssh/sshd_config | grep 'Port '|awk '{print $2}')
if [ "${PM}" = "apt-get" ]; then
apt-get install -y ufw
if [ -f "/usr/sbin/ufw" ];then
ufw allow 22/tcp
ufw allow ${panelPort}/tcp
ufw allow ${sshPort}/tcp
ufw_status=`ufw status`
echo y|ufw enable
ufw default deny
ufw reload
fi
else
if [ -f "/etc/init.d/iptables" ];then
iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ${panelPort} -j ACCEPT
iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ${sshPort} -j ACCEPT
iptables -A INPUT -p icmp --icmp-type any -j ACCEPT
iptables -A INPUT -s localhost -d localhost -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -P INPUT DROP
service iptables save
sed -i "s#IPTABLES_MODULES=\"\"#IPTABLES_MODULES=\"ip_conntrack_netbios_ns ip_conntrack_ftp ip_nat_ftp\"#" /etc/sysconfig/iptables-config
iptables_status=$(service iptables status | grep 'not running')
if [ "${iptables_status}" == '' ];then
service iptables restart
fi
else
AliyunCheck=$(cat /etc/redhat-release|grep "Aliyun Linux")
[ "${AliyunCheck}" ] && return
yum install firewalld -y
[ "${Centos8Check}" ] && yum reinstall python3-six -y
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --set-default-zone=public > /dev/null 2>&1
firewall-cmd --permanent --zone=public --add-port=22/tcp > /dev/null 2>&1
firewall-cmd --permanent --zone=public --add-port=${panelPort}/tcp > /dev/null 2>&1
firewall-cmd --permanent --zone=public --add-port=${sshPort}/tcp > /dev/null 2>&1
firewall-cmd --reload
fi
fi
}
Service_Add(){
if [ $Command_Exists systemctl ]; then
wget -O /usr/lib/systemd/system/btm.service ${download_Url}/init/systemd/btmonitor.service -t 5 -T 10
systemctl daemon-reload
systemctl enable btm
else
if [ "${PM}" == "yum" ] || [ "${PM}" == "dnf" ]; then
chkconfig --add btm
chkconfig --level 2345 btm on
elif [ "${PM}" == "apt-get" ]; then
update-rc.d btm defaults
fi
fi
}
Service_Del(){
if [ $Command_Exists systemctl ]; then
rm -rf /usr/lib/systemd/system/btm.service
systemctl disable btm
else
if [ "${PM}" == "yum" ] || [ "${PM}" == "dnf" ]; then
chkconfig --del btm
chkconfig --level 2345 btm off
elif [ "${PM}" == "apt-get" ]; then
update-rc.d btm remove
fi
fi
}
Get_Ip_Address(){
getIpAddress=""
getIpAddress=$(curl -sS --connect-timeout 10 -m 60 https://www.bt.cn/Api/getIpAddress)
if [ -z "${getIpAddress}" ] || [ "${getIpAddress}" = "0.0.0.0" ]; then
isHosts=$(cat /etc/hosts|grep 'www.bt.cn')
if [ -z "${isHosts}" ];then
echo "" >> /etc/hosts
echo "116.213.43.206 www.bt.cn" >> /etc/hosts
getIpAddress=$(curl -sS --connect-timeout 10 -m 60 https://www.bt.cn/Api/getIpAddress)
if [ -z "${getIpAddress}" ];then
sed -i "/bt.cn/d" /etc/hosts
fi
fi
fi
ipv4Check=$($python_bin -c "import re; print(re.match('^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$','${getIpAddress}'))")
if [ "${ipv4Check}" == "None" ];then
ipv6Address=$(echo ${getIpAddress}|tr -d "[]")
ipv6Check=$($python_bin -c "import re; print(re.match('^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$','${ipv6Address}'))")
if [ "${ipv6Check}" == "None" ]; then
getIpAddress="SERVER_IP"
else
echo "True" > ${setup_path}/server/bt-monitor/data/ipv6.pl
sleep 1
/etc/init.d/btm restart
fi
fi
if [ "${getIpAddress}" != "SERVER_IP" ];then
echo "${getIpAddress}" > ${setup_path}/server/bt-monitor/data/iplist.txt
fi
LOCAL_IP=$(ip addr | grep -E -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | grep -E -v "^127\.|^255\.|^0\." | head -n 1)
}
System_Check(){
if [ -f "$monitor_path/BT-MONITOR" ] || [ -f "$monitor_path/tools.py" ] || [ -f "/etc/init.d/btm" ];then
Install_Check
elif [ -d "$old_dir" ];then
Rev_Install_Check
fi
}
Install_Check(){
echo -e "----------------------------------------------------"
echo -e "检测到已存在堡塔云监控系统,请按照选项选择安装方式!"
echo -e "1) 覆盖安装:保存原有监控配置及数据并安装堡塔云监控"
echo -e "\033[33m2) 全新安装:清空原有监控配置及数据并安装堡塔云监控\033[0m"
echo -e "----------------------------------------------------"
read -p "请输入对应选项[1|2]进行安装或输入任意内容退出安装: " yes;
if [ "$yes" == "1" ]; then
re_install="1"
echo "即将卸载并重装本机的堡塔云监控被控端..."
Uninstall_agent
elif [ "$yes" == "2" ]; then
Backup_Monitor
echo "即将卸载并重装本机的堡塔云监控被控端..."
Uninstall_agent
else
echo -e "------------"
echo "取消安装"
exit;
fi
}
Rev_Install_Check(){
echo -e "----------------------------------------------------"
echo -e "\033[33m检测到上一次卸载云监控时保留的旧数据,请按照选项选择安装方式!\033[0m"
echo -e "1) 还原以前的备份并安装堡塔云监控系统!"
echo -e "2) 不使用原有备份,全新安装堡塔云监控系统!"
echo -e "----------------------------------------------------"
read -p "请输入对应选项[1|2]进行安装或输入任意内容退出安装: " yes;
if [ "$yes" == "1" ]; then
re_install="2"
echo "开始安装堡塔云监控系统并还原数据..."
elif [ "$yes" == "2" ]; then
echo "开始全新安装堡塔云监控系统..."
else
echo -e "------------"
echo "取消安装"
exit;
fi
}
Backup_Monitor(){
if [ -f "/etc/init.d/btm" ]; then
/etc/init.d/btm stop
sleep 1
fi
if [ ! -d "${old_dir}" ];then
mkdir -p ${old_dir}
else
mv ${old_dir} ${old_dir}_$(date +%Y_%m_%d_%H_%M_%S)
mkdir -p ${old_dir}
fi
mv ${monitor_path}/data ${old_dir}/data
mv ${monitor_path}/config ${old_dir}/config
mv ${monitor_path}/ssl ${old_dir}/ssl
}
Reinstall_Monitor(){
rm -rf $monitor_path/data
rm -rf $monitor_path/config
rm -rf $monitor_path/ssl
mv $old_dir/data $monitor_path/data
mv $old_dir/config $monitor_path/config
mv $old_dir/ssl $monitor_path/ssl
rm -rf $old_dir
}
Get_Pack_Manager(){
if [ -f "/usr/bin/yum" ] && [ -d "/etc/yum.repos.d" ]; then
PM="yum"
elif [ -f "/usr/bin/apt-get" ] && [ -f "/usr/bin/dpkg" ]; then
PM="apt-get"
fi
}
Install_RPM_Pack(){
yumPacks="wget curl unzip gcc gcc-c++ make libcurl-devel openssl-devel xz-devel python-backports-lzma xz crontabs zlib zlib-devel sqlite-devel libffi-devel bzip2-devel lsof net-tools"
yum install -y ${yumPacks}
for yumPack in ${yumPacks}
do
rpmPack=$(rpm -q ${yumPack})
packCheck=$(echo ${rpmPack}|grep not)
if [ "${packCheck}" ]; then
yum install ${yumPack} -y
fi
done
}
Install_Deb_Pack(){
debPacks="wget curl unzip gcc g++ make cron libcurl4-openssl-dev libssl-dev liblzma-dev xz-utils libffi-dev libbz2-dev libsqlite3-dev libreadline-dev libgdbm-dev python3-bsddb3 tk-dev ncurses-dev uuid-dev zlib1g zlib1g-dev lsof net-tools";
apt-get update -y
apt-get install -y $debPacks --force-yes
for debPack in ${debPacks}
do
packCheck=$(dpkg -l ${debPack})
if [ "$?" -ne "0" ] ;then
apt-get install -y $debPack
fi
done
}
Check_Sys_Write(){
echo "正在检测系统关键目录是否可写"
if [ ! -d "/etc/init.d" ];then
mkdir -p /etc/init.d
fi
if [ -f /usr/bin/which ];then
Get_Pack_Manager
if [ "$PM" == "yum" ]; then
read_dir="/usr/lib/systemd/system/ /etc/init.d/ /var/spool/cron/"
else
read_dir="/usr/lib/systemd/system/ /etc/init.d/ /var/spool/cron/crontabs/"
fi
for dir in ${read_dir[@]}
do
if [[ -d "$dir" ]]; then
touch $dir/btm_install_test_111.pl
state=$(echo $?)
if [[ "$state" != "0" ]];then
echo -e "\033[31m错误:检测到系统关键目录不可写! $read_dir \033[0m"
echo "1、如果安装了[宝塔系统加固],请先临时关闭"
echo "2、如果安装了云锁,请临时关闭[系统加固]功能"
echo "3、如果安装了安全狗,请临时关闭[系统防护]功能"
echo "4、如果使用了其它安全软件,请先卸载 "
echo -e "5、如果使用了禁止写入命令,请执行命令取消禁止写入:\n chattr -iaR $read_dir "
echo ""
echo -e "\033[31m解决以上问题后,请尝试重新安装! \033[0m"
echo -e "如果无法解决请截图以上报错信息发帖至论坛www.bt.cn/bbs求助"
exit 1
else
rm -f $dir/btm_install_test_111.pl
fi
fi
done
fi
}
Check_Sys_Packs(){
echo "正在检查系统中是否存在必备的依赖包"
Packs="wget curl unzip gcc make"
for pack in ${Packs[@]}
do
check_pack=$(which $pack)
#echo $check_pack
if [[ "$check_pack" == "" ]]; then
echo -e "\033[31mERROR: $pack 命令不存在,尝试以下解决方法:\033[0m"
if [ "$PM" == "yum" ]; then
echo 1、使用命令重新安装依赖包:yum reinstall -y ${Packs}
else
echo 1、使用命令重新安装依赖包:apt-get reinstall -y ${Packs}
fi
echo -e "2、检查系统源是否可用?尝试更换可用的源参考教程:\n https://www.bt.cn/bbs/thread-58005-1-1.html "
echo ""
echo -e "\033[31m解决以上问题后,请尝试重新安装! \033[0m"
echo -e "如果无法解决请截图以上报错信息发帖至论坛www.bt.cn/bbs求助"
exit 1
fi
done
}
Install_Main(){
startTime=`date +%s`
Check_Sys_Write
System_Check
Get_Pack_Manager
get_node_url
if [ "$PM" == "yum" ]; then
Install_RPM_Pack
else
Install_Deb_Pack
fi
Check_Sys_Packs
Install_Python_Lib
Install_Monitor
Set_Firewall
Get_Ip_Address
Service_Add
if [ "$re_install" == "2" ]; then
Reinstall_Monitor
fi
Start_Monitor
}
Uninstall_Monitor(){
pkill BT-MONITOR
/etc/init.d/btm stop
if [ -f "/www/server/bt-monitor/sqlite-server.sh" ]; then
chmod +x /www/server/bt-monitor/sqlite-server.sh
/www/server/bt-monitor/sqlite-server.sh stop
sleep 1
fi
Service_Del
rm -rf $monitor_path
rm -rf /usr/bin/btm
rm -rf /etc/init.d/btm
echo -e "堡塔云监控主控端卸载成功!"
}
Uninstall_agent(){
get_node_url
if [ -f "/tmp/btmonitoragent.sh" ];then
rm -rf /tmp/btmonitoragent.sh
fi
curl -o /tmp/btmonitoragent.sh -sSO ${download_Url}/install/btmonitoragent.sh && bash /tmp/btmonitoragent.sh uninstall
}
action="${1}"
if [ "$action" == "uninstall" ];then
echo -e "----------------------------------------------------"
echo -e "\033[33m检测到您正在卸载堡塔云监控系统,请按照选项选择卸载方式!\033[0m"
echo -e "1) 备份数据后卸载:保存原有监控配置及数据并卸载堡塔云监控系统"
echo -e "2) 完全卸载:清空原有监控配置及数据并卸载堡塔云监控系统"
echo -e "----------------------------------------------------"
read -p "请输入对应选项[1|2]进行卸载或输入任意内容退出卸载: " yes;
if [ "$yes" == "1" ]; then
Backup_Monitor
echo -e "----------------------------------------------------"
echo -e "\033[33m已备份原有监控数据至: ${old_dir}\033[0m"
elif [ "$yes" == "2" ]; then
echo "正在清空堡塔云监控系统数据..."
else
echo -e "------------"
echo "取消卸载"
exit;
fi
Uninstall_agent
Uninstall_Monitor
exit 0
else
echo "
+----------------------------------------------------------------------
| Bt-Monitor FOR CentOS/Ubuntu/Debian
+----------------------------------------------------------------------
| Copyright © 2015-2099 BT-SOFT(https://www.bt.cn) All rights reserved.
+----------------------------------------------------------------------
| The Monitor URL will be https://SERVER_IP:806 when installed.
+----------------------------------------------------------------------
"
while [ "$go" != 'y' ] && [ "$go" != 'n' ]
do
read -p "Do you want to install Bt-Monitor to the $setup_path directory now?(y/n): " go;
done
if [ "$go" == 'n' ];then
exit;
fi
Install_Main
#curl -o /dev/null -fsSL --connect-time 10 "https://api.bt.cn/bt_monitor/setup_count?cloud_type=1&token=$md5_pl&src_code=$1"
#curl -o /dev/null -fsSL --connect-time 10 "https://api.bt.cn/bt_monitor/setup_count?cloud_type=1&token=$md5_pl&src_code=$1&status=1"
fi
echo -e "=================================================================="
echo -e "\033[32m堡塔云监控主控端安装完成! Installed successfully!\033[0m"
echo -e "=================================================================="
echo "外网访问地址: https://${getIpAddress}:${panelPort}${adminpath}"
echo "内网访问地址: https://${LOCAL_IP}:${panelPort}${adminpath}"
echo -e "username: admin"
echo -e "password: $password"
echo -e "\033[33mIf you cannot access the Monitor,\033[0m"
echo -e "\033[33mrelease the following Monitor port [${panelPort}] in the security group\033[0m"
echo -e "\033[33m若无法访问堡塔云监控主控端,请检查防火墙/安全组是否有放行[${panelPort}]端口\033[0m"
echo -e "=================================================================="
endTime=`date +%s`
((outTime=($endTime-$startTime)/60))
echo -e "Time consumed:\033[32m $outTime \033[0mMinute!"

150
public/install/public.sh

@ -0,0 +1,150 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
pyenv_bin=/www/server/panel/pyenv/bin
rep_path=${pyenv_bin}:$PATH
if [ -d "$pyenv_bin" ];then
PATH=$rep_path
fi
export PATH
export LANG=en_US.UTF-8
export LANGUAGE=en_US:en
get_node_url(){
nodes=(https://dg2.bt.cn https://download.bt.cn https://ctcc1-node.bt.cn https://cmcc1-node.bt.cn https://ctcc2-node.bt.cn https://hk1-node.bt.cn https://na1-node.bt.cn https://jp1-node.bt.cn);
if [ "$1" ];then
nodes=($(echo ${nodes[*]}|sed "s#${1}##"))
fi
tmp_file1=/dev/shm/net_test1.pl
tmp_file2=/dev/shm/net_test2.pl
[ -f "${tmp_file1}" ] && rm -f ${tmp_file1}
[ -f "${tmp_file2}" ] && rm -f ${tmp_file2}
touch $tmp_file1
touch $tmp_file2
for node in ${nodes[@]};
do
NODE_CHECK=$(curl --connect-timeout 3 -m 3 2>/dev/null -w "%{http_code} %{time_total}" ${node}/net_test|xargs)
RES=$(echo ${NODE_CHECK}|awk '{print $1}')
NODE_STATUS=$(echo ${NODE_CHECK}|awk '{print $2}')
TIME_TOTAL=$(echo ${NODE_CHECK}|awk '{print $3 * 1000 - 500 }'|cut -d '.' -f 1)
if [ "${NODE_STATUS}" == "200" ];then
if [ $TIME_TOTAL -lt 300 ];then
if [ $RES -ge 1500 ];then
echo "$RES $node" >> $tmp_file1
fi
else
if [ $RES -ge 1500 ];then
echo "$TIME_TOTAL $node" >> $tmp_file2
fi
fi
i=$(($i+1))
if [ $TIME_TOTAL -lt 200 ];then
if [ $RES -ge 3000 ];then
break;
fi
fi
fi
done
NODE_URL=$(cat $tmp_file1|sort -r -g -t " " -k 1|head -n 1|awk '{print $2}')
if [ -z "$NODE_URL" ];then
NODE_URL=$(cat $tmp_file2|sort -g -t " " -k 1|head -n 1|awk '{print $2}')
if [ -z "$NODE_URL" ];then
NODE_URL='https://download.bt.cn';
fi
fi
rm -f $tmp_file1
rm -f $tmp_file2
}
GetCpuStat(){
time1=$(cat /proc/stat |grep 'cpu ')
sleep 1
time2=$(cat /proc/stat |grep 'cpu ')
cpuTime1=$(echo ${time1}|awk '{print $2+$3+$4+$5+$6+$7+$8}')
cpuTime2=$(echo ${time2}|awk '{print $2+$3+$4+$5+$6+$7+$8}')
runTime=$((${cpuTime2}-${cpuTime1}))
idelTime1=$(echo ${time1}|awk '{print $5}')
idelTime2=$(echo ${time2}|awk '{print $5}')
idelTime=$((${idelTime2}-${idelTime1}))
useTime=$(((${runTime}-${idelTime})*3))
[ ${useTime} -gt ${runTime} ] && cpuBusy="true"
if [ "${cpuBusy}" == "true" ]; then
cpuCore=$((${cpuInfo}/2))
else
cpuCore=$((${cpuInfo}-1))
fi
}
GetPackManager(){
if [ -f "/usr/bin/yum" ] && [ -f "/etc/yum.conf" ]; then
PM="yum"
elif [ -f "/usr/bin/apt-get" ] && [ -f "/usr/bin/dpkg" ]; then
PM="apt-get"
fi
}
bt_check(){
p_path=/www/server/panel/class/panelPlugin.py
if [ -f $p_path ];then
is_ext=$(cat $p_path|grep btwaf)
if [ "$is_ext" != "" ];then
send_check
fi
fi
p_path=/www/server/panel/BTPanel/templates/default/index.html
if [ -f $p_path ];then
is_ext=$(cat $p_path|grep fbi)
if [ "$is_ext" != "" ];then
send_check
fi
fi
}
send_check(){
chattr -i /etc/init.d/bt
chmod +x /etc/init.d/bt
p_path2=/www/server/panel/class/common.py
p_version=$(cat $p_path2|grep "version = "|awk '{print $3}'|tr -cd [0-9.])
curl -sS --connect-timeout 3 -m 60 https://www.bt.cn/api/panel/notpro?version=$p_version
NODE_URL=""
exit 0;
}
GetSysInfo(){
if [ "${PM}" = "yum" ]; then
SYS_VERSION=$(cat /etc/redhat-release)
elif [ "${PM}" = "apt-get" ]; then
SYS_VERSION=$(cat /etc/issue)
fi
SYS_INFO=$(uname -msr)
SYS_BIT=$(getconf LONG_BIT)
MEM_TOTAL=$(free -m|grep Mem|awk '{print $2}')
CPU_INFO=$(getconf _NPROCESSORS_ONLN)
GCC_VER=$(gcc -v 2>&1|grep "gcc version"|awk '{print $3}')
CMAKE_VER=$(cmake --version|grep version|awk '{print $3}')
echo -e ${SYS_VERSION}
echo -e Bit:${SYS_BIT} Mem:${MEM_TOTAL}M Core:${CPU_INFO} gcc:${GCC_VER} cmake:${CMAKE_VER}
echo -e ${SYS_INFO}
}
cpuInfo=$(getconf _NPROCESSORS_ONLN)
if [ "${cpuInfo}" -ge "4" ];then
GetCpuStat
else
cpuCore="1"
fi
GetPackManager
if [ ! $NODE_URL ];then
EN_CHECK=$(cat /www/server/panel/config/config.json |grep English)
if [ -z "${EN_CHECK}" ];then
echo '正在选择下载节点...';
else
echo "selecting download node...";
fi
get_node_url
fi

BIN
public/install/src/bt-monitor-2.2.5.zip

BIN
public/install/src/panel6.zip

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

127
public/install/update6.sh

@ -0,0 +1,127 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
LANG=en_US.UTF-8
Btapi_Url='http://www.example.com'
if [ ! -d /www/server/panel/BTPanel ];then
echo "============================================="
echo "错误, 5.x不可以使用此命令升级!"
echo "5.9平滑升级到6.0的命令:curl http://download.bt.cn/install/update_to_6.sh|bash"
exit 0;
fi
if [ ! -f "/www/server/panel/pyenv/bin/python3" ];then
echo "============================================="
echo "错误, 当前面板过旧/py-2.7/无pyenv环境,无法升级至最新版面板"
echo "请截图发帖至论坛www.bt.cn/bbs求助"
exit 0;
fi
public_file=/www/server/panel/install/public.sh
if [ ! -f $public_file ];then
wget -O Tpublic.sh $Btapi_Url/install/public.sh -T 20;
fi
. $public_file
Centos8Check=$(cat /etc/redhat-release | grep ' 8.' | grep -iE 'centos|Red Hat')
if [ "${Centos8Check}" ];then
if [ ! -f "/usr/bin/python" ] && [ -f "/usr/bin/python3" ] && [ ! -d "/www/server/panel/pyenv" ]; then
ln -sf /usr/bin/python3 /usr/bin/python
fi
fi
mypip="pip"
env_path=/www/server/panel/pyenv/bin/activate
if [ -f $env_path ];then
mypip="/www/server/panel/pyenv/bin/pip"
fi
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.9'
fi
armCheck=$(uname -m|grep arm)
if [ "${armCheck}" ];then
version='7.7.0'
fi
if [ "$1" ];then
version=$1
fi
wget -T 5 -O /tmp/panel.zip $Btapi_Url/install/update/LinuxPanel-${version}.zip
dsize=$(du -b /tmp/panel.zip|awk '{print $1}')
if [ $dsize -lt 10240 ];then
echo "获取更新包失败,请稍后更新或联系宝塔运维"
exit;
fi
unzip -o /tmp/panel.zip -d $setup_path/server/ > /dev/null
rm -f /tmp/panel.zip
cd $setup_path/server/panel/
check_bt=`cat /etc/init.d/bt`
if [ "${check_bt}" = "" ];then
rm -f /etc/init.d/bt
wget -O /etc/init.d/bt $download_Url/install/src/bt7.init -T 20
chmod +x /etc/init.d/bt
fi
rm -f /www/server/panel/*.pyc
rm -f /www/server/panel/class/*.pyc
#pip install flask_sqlalchemy
#pip install itsdangerous==0.24
pip_list=$($mypip list)
request_v=$(btpip list 2>/dev/null|grep "requests "|awk '{print $2}'|cut -d '.' -f 2)
if [ "$request_v" = "" ] || [ "${request_v}" -gt "28" ];then
$mypip install requests==2.27.1
fi
openssl_v=$(echo "$pip_list"|grep pyOpenSSL)
if [ "$openssl_v" = "" ];then
$mypip install pyOpenSSL
fi
#cffi_v=$(echo "$pip_list"|grep cffi|grep 1.12.)
#if [ "$cffi_v" = "" ];then
# $mypip install cffi==1.12.3
#fi
pymysql=$(echo "$pip_list"|grep pymysql)
if [ "$pymysql" = "" ];then
$mypip install pymysql
fi
pymysql=$(echo "$pip_list"|grep pycryptodome)
if [ "$pymysql" = "" ];then
$mypip install pycryptodome
fi
#psutil=$(echo "$pip_list"|grep psutil|awk '{print $2}'|grep '5.7.')
#if [ "$psutil" = "" ];then
# $mypip install -U psutil
#fi
if [ -d /www/server/panel/class/BTPanel ];then
rm -rf /www/server/panel/class/BTPanel
fi
rm -f /www/server/panel/class/*.so
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
fi
chattr -i /etc/init.d/bt
chmod +x /etc/init.d/bt
echo "====================================="
rm -f /dev/shm/bt_sql_tips.pl
kill $(ps aux|grep -E "task.pyc|main.py"|grep -v grep|awk '{print $2}')
/etc/init.d/bt restart
echo 'True' > /www/server/panel/data/restart.pl
pkill -9 gunicorn &
echo "已成功升级到[$version]${Ver}";

446
public/install/update_btmonitor.sh

@ -0,0 +1,446 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
LANG=en_US.UTF-8
Btapi_Url='http://www.example.com'
monitor_path="/www/server/bt-monitor"
run_bin="/www/server/bt-monitor/BT-MONITOR"
is64bit=$(getconf LONG_BIT)
if [ ! -d $monitor_path ]; then
echo "没有安装云监控,请执行下面的命令安装堡塔云监控!"
echo "curl -sSO ${Btapi_Url}/install/install_btmonitor.sh && bash install_btmonitor.sh"
exit 1
fi
cd ~
setup_path="/www"
if [ -f "/etc/init.d/btm" ]; then
/etc/init.d/btm stop
sleep 1
fi
if [ -f "/www/server/bt-monitor/sqlite-server.sh" ]; then
chmod +x /www/server/bt-monitor/sqlite-server.sh
/www/server/bt-monitor/sqlite-server.sh stop
sleep 1
fi
get_node_url(){
if [ ! -f /bin/curl ];then
if [ "${PM}" = "yum" ]; then
yum install curl -y
elif [ "${PM}" = "apt-get" ]; then
apt-get install curl -y
fi
fi
if [ -f "/www/node.pl" ];then
download_Url=$(cat /www/node.pl)
echo "Download node: $download_Url";
echo '---------------------------------------------';
return
fi
echo '---------------------------------------------';
echo "Selected download node...";
# nodes=(http://dg2.bt.cn http://dg1.bt.cn http://125.90.93.52:5880 http://36.133.1.8:5880 http://123.129.198.197 http://38.34.185.130 http://116.213.43.206:5880 http://128.1.164.196);
#nodes=(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://116.213.43.206:5880);
nodes=(https://dg2.bt.cn https://dg1.bt.cn https://download.bt.cn);
tmp_file1=/dev/shm/net_test1.pl
tmp_file2=/dev/shm/net_test2.pl
[ -f "${tmp_file1}" ] && rm -f ${tmp_file1}
[ -f "${tmp_file2}" ] && rm -f ${tmp_file2}
touch $tmp_file1
touch $tmp_file2
for node in ${nodes[@]};
do
NODE_CHECK=$(curl --connect-timeout 3 -m 3 2>/dev/null -w "%{http_code} %{time_total}" ${node}/net_test|xargs)
RES=$(echo ${NODE_CHECK}|awk '{print $1}')
NODE_STATUS=$(echo ${NODE_CHECK}|awk '{print $2}')
TIME_TOTAL=$(echo ${NODE_CHECK}|awk '{print $3 * 1000 - 500 }'|cut -d '.' -f 1)
if [ "${NODE_STATUS}" == "200" ];then
if [ $TIME_TOTAL -lt 100 ];then
if [ $RES -ge 1500 ];then
echo "$RES $node" >> $tmp_file1
fi
else
if [ $RES -ge 1500 ];then
echo "$TIME_TOTAL $node" >> $tmp_file2
fi
fi
i=$(($i+1))
if [ $TIME_TOTAL -lt 100 ];then
if [ $RES -ge 3000 ];then
break;
fi
fi
fi
done
NODE_URL=$(cat $tmp_file1|sort -r -g -t " " -k 1|head -n 1|awk '{print $2}')
if [ -z "$NODE_URL" ];then
NODE_URL=$(cat $tmp_file2|sort -g -t " " -k 1|head -n 1|awk '{print $2}')
if [ -z "$NODE_URL" ];then
NODE_URL='https://download.bt.cn';
fi
fi
rm -f $tmp_file1
rm -f $tmp_file2
download_Url=$NODE_URL
echo "Download node: $download_Url";
echo '---------------------------------------------';
}
Get_Versions(){
redhat_version_file="/etc/redhat-release"
deb_version_file="/etc/issue"
if [ -f $redhat_version_file ];then
os_type='el'
is_aliyunos=$(cat $redhat_version_file|grep Aliyun)
if [ "$is_aliyunos" != "" ];then
return
fi
os_version=$(cat $redhat_version_file|grep CentOS|grep -Eo '([0-9]+\.)+[0-9]+'|grep -Eo '^[0-9]')
if [ "${os_version}" = "5" ];then
os_version=""
fi
if [ -z "${os_version}" ];then
os_version=$(cat /etc/redhat-release |grep Stream|grep -oE 8)
fi
else
os_type='ubuntu'
os_version=$(cat $deb_version_file|grep Ubuntu|grep -Eo '([0-9]+\.)+[0-9]+'|grep -Eo '^[0-9]+')
if [ "${os_version}" = "" ];then
os_type='debian'
os_version=$(cat $deb_version_file|grep Debian|grep -Eo '([0-9]+\.)+[0-9]+'|grep -Eo '[0-9]+')
if [ "${os_version}" = "" ];then
os_version=$(cat $deb_version_file|grep Debian|grep -Eo '[0-9]+')
fi
if [ "${os_version}" = "8" ];then
os_version=""
fi
if [ "${is64bit}" = '32' ];then
os_version=""
fi
else
if [ "$os_version" = "14" ];then
os_version=""
fi
if [ "$os_version" = "12" ];then
os_version=""
fi
if [ "$os_version" = "19" ];then
os_version=""
fi
if [ "$os_version" = "21" ];then
os_version=""
fi
if [ "$os_version" = "20" ];then
os_version2004=$(cat /etc/issue|grep 20.04)
if [ -z "${os_version2004}" ];then
os_version=""
fi
fi
fi
fi
}
Install_Python_Lib(){
curl -Ss --connect-timeout 3 -m 60 $download_Url/install/pip_select.sh|bash
pyenv_path="/www/server/bt-monitor"
if [ -f $pyenv_path/pyenv/bin/python ];then
is_ssl=$($python_bin -c "import ssl" 2>&1|grep cannot)
$pyenv_path/pyenv/bin/python3.7 -V
if [ $? -eq 0 ] && [ -z "${is_ssl}" ];then
chmod -R 700 $pyenv_path/pyenv/bin
is_package=$($python_bin -m psutil 2>&1|grep package)
if [ "$is_package" = "" ];then
wget -O $pyenv_path/pyenv/pip.txt $download_Url/install/pyenv/pip.txt -t 5 -T 10
$pyenv_path/pyenv/bin/pip install -U pip
$pyenv_path/pyenv/bin/pip install -U setuptools
$pyenv_path/pyenv/bin/pip install -r $pyenv_path/pyenv/pip.txt
$pyenv_path/pyenv/bin/pip install -U flask==2.2.0
$pyenv_path/pyenv/bin/pip install flask_sock
$pyenv_path/pyenv/bin/pip install cachelib
$pyenv_path/pyenv/bin/pip install py7zr
$pyenv_path/pyenv/bin/pip install backports.lzma
fi
source $pyenv_path/pyenv/bin/activate
chmod -R 700 $pyenv_path/pyenv/bin
return
else
rm -rf $pyenv_path/pyenv
fi
fi
py_version="3.7.9"
if [ ! -d "$pyenv_path" ]; then
mkdir -p $pyenv_path
fi
echo "True" > /www/disk.pl
if [ ! -w /www/disk.pl ];then
Red_Error "ERROR: Install python env fielded." "ERROR: /www目录无法写入,请检查目录/用户/磁盘权限!"
fi
os_type='el'
os_version='7'
is_export_openssl=0
Get_Versions
echo "OS: $os_type - $os_version"
is_aarch64=$(uname -a|grep aarch64)
if [ "$is_aarch64" != "" ];then
is64bit="aarch64"
fi
if [ -f "/www/server/bt-monitor/pymake.pl" ];then
os_version=""
rm -f /www/server/bt-monitor/pymake.pl
fi
if [[ $os_type =~ "debian" ]] || [[ $os_type =~ "ubuntu" ]]; then
isbtm="-btm"
fi
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}${isbtm}.tar.gz -t 5 -T 10
tmp_size=$(du -b $pyenv_file|awk '{print $1}')
if [ $tmp_size -lt 703460 ];then
rm -f $pyenv_file
echo "ERROR: Download python env fielded."
else
echo "Install python env..."
tar zxvf $pyenv_file -C $pyenv_path/ > /dev/null
chmod -R 700 $pyenv_path/pyenv/bin
rm -rf $pyenv_path/pyenv/bin/python
ln -sf $pyenv_path/pyenv/bin/python3.7 $pyenv_path/pyenv/bin/python
$pyenv_path/pyenv/bin/python -m pip install --upgrade --force-reinstall pip
$pyenv_path/pyenv/bin/pip install -U flask==2.2.0
$pyenv_path/pyenv/bin/pip install flask_sock
$pyenv_path/pyenv/bin/pip install cachelib
$pyenv_path/pyenv/bin/pip install py7zr
$pyenv_path/pyenv/bin/pip install backports.lzma
if [ ! -f $pyenv_path/pyenv/bin/python ];then
rm -f $pyenv_file
Red_Error "ERROR: Install python env fielded." "ERROR: 下载堡塔云监控运行环境失败,请尝试重新安装!"
fi
$pyenv_path/pyenv/bin/python3.7 -V
if [ $? -eq 0 ];then
rm -f $pyenv_file
ln -sf $pyenv_path/pyenv/bin/pip3.7 /usr/bin/btmpip
ln -sf $pyenv_path/pyenv/bin/python3.7 /usr/bin/btmpython
source $pyenv_path/pyenv/bin/activate
return
else
rm -f $pyenv_file
rm -rf $pyenv_path/pyenv
fi
fi
fi
cd /www
python_src='/www/python_src.tar.xz'
python_src_path="/www/Python-${py_version}"
wget -O $python_src $download_Url/src/Python-${py_version}.tar.xz -t 5 -T 10
tmp_size=$(du -b $python_src|awk '{print $1}')
if [ $tmp_size -lt 10703460 ];then
rm -f $python_src
Red_Error "ERROR: Download python source code fielded." "ERROR: 下载堡塔云监控运行环境失败,请尝试重新安装!"
fi
tar xvf $python_src
rm -f $python_src
cd $python_src_path
./configure --prefix=$pyenv_path/pyenv
make -j$cpu_cpunt
make install
if [ ! -f $pyenv_path/pyenv/bin/python3.7 ];then
rm -rf $python_src_path
Red_Error "ERROR: Make python env fielded." "ERROR: 编译堡塔云监控运行环境失败!"
fi
cd ~
rm -rf $python_src_path
wget -O $pyenv_path/pyenv/bin/activate $download_Url/install/pyenv/activate.panel -t 5 -T 10
wget -O $pyenv_path/pyenv/pip.txt $download_Url/install/pyenv/pip-3.7.8.txt -t 5 -T 10
ln -sf $pyenv_path/pyenv/bin/pip3.7 $pyenv_path/pyenv/bin/pip
ln -sf $pyenv_path/pyenv/bin/python3.7 $pyenv_path/pyenv/bin/python
ln -sf $pyenv_path/pyenv/bin/pip3.7 /usr/bin/btmpip
ln -sf $pyenv_path/pyenv/bin/python3.7 /usr/bin/btmpython
chmod -R 700 $pyenv_path/pyenv/bin
$pyenv_path/pyenv/bin/pip install -U pip
$pyenv_path/pyenv/bin/pip install -U setuptools
$pyenv_path/pyenv/bin/pip install -U wheel==0.34.2
$pyenv_path/pyenv/bin/pip install -r $pyenv_path/pyenv/pip.txt
$pyenv_path/pyenv/bin/pip install -U flask==2.2.0
$pyenv_path/pyenv/bin/pip install flask_sock
$pyenv_path/pyenv/bin/pip install cachelib
$pyenv_path/pyenv/bin/pip install py7zr
$pyenv_path/pyenv/bin/pip install backports.lzma
source $pyenv_path/pyenv/bin/activate
is_gevent=$($python_bin -m gevent 2>&1|grep -oE package)
is_psutil=$($python_bin -m psutil 2>&1|grep -oE package)
if [ "${is_gevent}" != "${is_psutil}" ];then
Red_Error "ERROR: psutil/gevent install failed!"
fi
}
Install_Monitor(){
version="1.0.2"
file_name="bt-monitor"
agent_src="bt-monitor.zip"
cd ~
version=`curl -sf ${Btapi_Url}/bt_monitor/latest_version |awk -F '\"version\"' '{print $2}'|awk -F ':' '{print $2}'|awk -F '"' '{print $2}'`
if [ -z $version ]; then
version="1.0.2"
fi
new_dir="/www/server/new_btmonitor"
if [ ! -d "$new_dir" ];then
mkdir -p $new_dir
fi
if [ ! -z "$action" ]; then
# 例如:sh update_btmonitor.sh /root/demo.zip
if [[ "$action" =~ "zip" ]]; then
version="指定版本"
unzip -o $action -d $new_dir/
else
wget -O $agent_src ${Btapi_Url}/install/src/$file_name-$version.zip -t 5 -T 10
unzip -o $agent_src -d $new_dir/ > /dev/null
fi
else
wget -O $agent_src ${Btapi_Url}/install/src/$file_name-$version.zip -t 5 -T 10
unzip -o $agent_src -d $new_dir/ > /dev/null
fi
if [ ! -f $new_dir/BT-MONITOR ];then
ls -lh $agent_src
Red_Error "ERROR: Failed to download, please try install again!" "ERROR: 下载堡塔云监控失败,请尝试重新安装!"
fi
rm -rf $new_dir/config
rm -rf $new_dir/data
rm -rf $new_dir/ssl
\cp -r $new_dir/* $monitor_path/
rm -rf $agent_src
rm -rf $new_dir
chmod +x $monitor_path/BT-MONITOR
chmod +x $monitor_path/tools.py
wget -O /etc/init.d/btm ${download_Url}/init/btmonitor.init -t 5 -T 10
chmod +x /etc/init.d/btm
ln -sf /etc/init.d/btm /usr/bin/btm
if [ ! -f $monitor_path/data/user.json ]; then
echo "{\"uid\":1,\"username\":\"Administrator\",\"ip\":\"127.0.0.1\",\"server_id\":\"1\",\"access_key\":\"test\",\"secret_key\":\"123456\"}" > $monitor_path/data/user.json
fi
if [ -f $monitor_path/core/include/c_loader/PluginLoader.so ]; then
rm -f $monitor_path/core/include/c_loader/PluginLoader.so
fi
}
Service_Add(){
if [ $Command_Exists systemctl ]; then
wget -O /usr/lib/systemd/system/btm.service ${download_Url}/init/systemd/btmonitor.service -t 5 -T 10
systemctl daemon-reload
systemctl enable btm
else
if [ "${PM}" == "yum" ] || [ "${PM}" == "dnf" ]; then
chkconfig --add btm
chkconfig --level 2345 btm on
elif [ "${PM}" == "apt-get" ]; then
update-rc.d btm defaults
fi
fi
}
Start_Monitor(){
/etc/init.d/btm start
if [ "$?" != "0" ]; then
echo "堡塔云监控启动失败!"
tail $monitor_path/logs/error.log
exit 1
fi
echo "已成功升级到[$version]${Ver}";
}
GetSysInfo(){
if [ -s "/etc/redhat-release" ];then
SYS_VERSION=$(cat /etc/redhat-release)
elif [ -s "/etc/issue" ]; then
SYS_VERSION=$(cat /etc/issue)
fi
SYS_INFO=$(uname -a)
SYS_BIT=$(getconf LONG_BIT)
MEM_TOTAL=$(free -m|grep Mem|awk '{print $2}')
CPU_INFO=$(getconf _NPROCESSORS_ONLN)
echo -e ${SYS_VERSION}
echo -e Bit:${SYS_BIT} Mem:${MEM_TOTAL}M Core:${CPU_INFO}
echo -e ${SYS_INFO}
echo -e "请截图以上报错信息发帖至论坛www.bt.cn/bbs求助"
}
Red_Error(){
echo '=================================================';
printf '\033[1;31;40m%b\033[0m\n' "$@";
GetSysInfo
exit 1;
}
Install_RPM_Pack(){
yumPacks="wget curl unzip gcc gcc-c++ make libcurl-devel openssl-devel xz-devel python-backports-lzma xz"
yum install -y ${yumPacks}
for yumPack in ${yumPacks}
do
rpmPack=$(rpm -q ${yumPack})
packCheck=$(echo ${rpmPack}|grep not)
if [ "${packCheck}" ]; then
yum install ${yumPack} -y
fi
done
}
Install_Deb_Pack(){
apt-get update -y
debPacks="wget curl unzip gcc g++ make libcurl4-openssl-dev libssl-dev liblzma-dev xz-utils libffi-dev libbz2-dev libsqlite3-dev libreadline-dev libgdbm-dev python3-bsddb3 tk-dev ncurses-dev uuid-dev";
apt-get install -y $debPacks --force-yes
for debPack in ${debPacks}
do
packCheck=$(dpkg -l ${debPack})
if [ "$?" -ne "0" ] ;then
apt-get install -y $debPack
fi
done
}
Get_Pack_Manager(){
if [ -f "/usr/bin/yum" ] && [ -d "/etc/yum.repos.d" ]; then
PM="yum"
elif [ -f "/usr/bin/apt-get" ] && [ -f "/usr/bin/dpkg" ]; then
PM="apt-get"
fi
}
Update_Monitor(){
Get_Pack_Manager
get_node_url
if [ $PM = "yum" ]; then
Install_RPM_Pack
else
Install_Deb_Pack
fi
if [ "$action" == "update_py" ]; then
Install_Python_Lib
fi
Install_Monitor
Service_Add
Start_Monitor
}
action=${1}
Update_Monitor

367
public/install/update_panel.sh

@ -0,0 +1,367 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
pyenv_bin=/www/server/panel/pyenv/bin
rep_path=${pyenv_bin}:$PATH
if [ -d "$pyenv_bin" ];then
PATH=$rep_path
fi
export PATH
LANG=en_US.UTF-8
setup_path=/www
is64bit=$(getconf LONG_BIT)
if [ "${is64bit}" != '64' ];then
echo "抱歉, 面板新版本不再支持32位系统, 无法进行升级";
echo "退出、不做任何操作"
exit 1
fi
Btapi_Url='http://www.example.com'
up_plugin=0
download_file(){
dst_file=$1
tmp_file=/tmp/bt_tmp_file.temp
if [ -f $tmp_file ];then
rm -f $tmp_file
fi
wget -O ${tmp_file} $2 -T 20
tmp_size=$(du -b $tmp_file|awk '{print $1}')
if [ $tmp_size -lt 10 ];then
echo "|-文件下载失败 $dst_file"
return
fi
if [ -f $dst_file ];then
rm -f $dst_file
fi
mv -f $tmp_file $dst_file
if [ -f $tmp_file ];then
rm -f $tmp_file
fi
}
Red_Error(){
echo '=================================================';
printf '\033[1;31;40m%b\033[0m\n' "$1";
exit 0;
}
check_panel(){
if [ ! -d /www/server/panel/BTPanel ];then
up_plugin=1
fi
}
select_node(){
public_file=/www/server/panel/install/public.sh
if [ ! -f $public_file ];then
download_file $public_file $Btapi_Url/install/public.sh
fi
. $public_file
download_Url=$NODE_URL
}
get_version(){
version=$(curl -Ss --connect-timeout 5 -m 2 $Btapi_Url/api/panel/get_version)
if [ "$version" = '' ];then
version='7.9.9'
fi
}
install_pack(){
if [ -f /usr/bin/yum ];then
yum install libcurl-devel libffi-devel zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel -y
else
apt install libcurl4-openssl-dev net-tools swig build-essential libffi-dev zlib1g.dev libbz2-dev libssl-dev libncurses-dev libsqlite3-dev libreadline-dev tk-dev libgdbm-dev libdb-dev libdb++-dev libpcap-dev xz-utils -y
fi
}
install_python(){
curl -Ss --connect-timeout 3 -m 60 $download_Url/install/pip_select.sh|bash
pyenv_path="/www/server/panel"
python_bin=$pyenv_path/pyenv/bin/python
if [ -f $pyenv_path/pyenv/bin/python ];then
is_err=$($pyenv_path/pyenv/bin/python3.7 -V 2>&1|grep 'Could not find platform')
if [ "$is_err" = "" ];then
chmod -R 700 $pyenv_path/pyenv/bin
is_package=$($python_bin -m psutil 2>&1|grep package)
if [ "$is_package" = "" ];then
wget -O $pyenv_path/pyenv/pip.txt $download_Url/install/pyenv/pip.txt -T 5
$pyenv_path/pyenv/bin/pip install -U pip
$pyenv_path/pyenv/bin/pip install -U setuptools
$pyenv_path/pyenv/bin/pip install -r $pyenv_path/pyenv/pip.txt
fi
source $pyenv_path/pyenv/bin/activate
return
else
rm -rf $pyenv_path/pyenv
fi
fi
install_pack
py_version="3.7.9"
mkdir -p $pyenv_path
os_type='el'
os_version='7'
is_export_openssl=0
Get_Versions
Centos6_Openssl
Other_Openssl
echo "OS: $os_type - $os_version"
is_aarch64=$(uname -a|grep aarch64)
if [ "$is_aarch64" != "" ];then
os_version="aarch64"
fi
up_plugin=1
if [ -f "/www/server/panel/pymake.pl" ];then
os_version=""
rm -f /www/server/panel/pymake.pl
fi
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
tmp_size=$(du -b $pyenv_file|awk '{print $1}')
if [ $tmp_size -lt 703460 ];then
rm -f $pyenv_file
echo "ERROR: Download python env fielded."
else
echo "Install python env..."
tar zxvf $pyenv_file -C $pyenv_path/ &> /dev/null
chmod -R 700 $pyenv_path/pyenv/bin
if [ ! -f $pyenv_path/pyenv/bin/python ];then
rm -f $pyenv_file
Red_Error "ERROR: Install python env fielded."
fi
is_err=$($pyenv_path/pyenv/bin/python3.7 -V 2>&1|grep 'Could not find platform')
if [ "$is_err" = "" ];then
rm -f $pyenv_file
ln -sf $pyenv_path/pyenv/bin/pip3.7 /usr/bin/btpip
ln -sf $pyenv_path/pyenv/bin/python3.7 /usr/bin/btpython
sync_python_lib
source $pyenv_path/pyenv/bin/activate
return
else
rm -rf $pyenv_path/pyenv
fi
fi
fi
if [ -f /usr/local/openssl/lib/libssl.so ];then
export LDFLAGS="-L/usr/local/openssl/lib"
export CPPFLAGS="-I/usr/local/openssl/include"
export PKG_CONFIG_PATH="/usr/local/openssl/lib/pkgconfig"
echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/openssl/lib" >> /etc/profile
source /etc/profile
fi
cd /www
python_src='/www/python_src.tar.xz'
python_src_path="/www/Python-${py_version}"
wget -O $python_src $download_Url/src/Python-${py_version}.tar.xz -T 5
tmp_size=$(du -b $python_src|awk '{print $1}')
if [ $tmp_size -lt 10703460 ];then
rm -f $python_src
Red_Error "ERROR: Download python source code fielded."
fi
tar xvf $python_src
rm -f $python_src
cd $python_src_path
./configure --prefix=$pyenv_path/pyenv
make -j$cpu_cpunt
make install
if [ ! -f $pyenv_path/pyenv/bin/python3.7 ];then
rm -rf $python_src_path
Red_Error "ERROR: Make python env fielded."
fi
cd ~
rm -rf $python_src_path
wget -O $pyenv_path/pyenv/bin/activate $download_Url/install/pyenv/activate.panel -T 5
wget -O $pyenv_path/pyenv/pip.txt $download_Url/install/pyenv/pip.txt -T 5
ln -sf $pyenv_path/pyenv/bin/pip3.7 $pyenv_path/pyenv/bin/pip
ln -sf $pyenv_path/pyenv/bin/python3.7 $pyenv_path/pyenv/bin/python
ln -sf $pyenv_path/pyenv/bin/pip3.7 /usr/bin/btpip
ln -sf $pyenv_path/pyenv/bin/python3.7 /usr/bin/btpython
chmod -R 700 $pyenv_path/pyenv/bin
$pyenv_path/pyenv/bin/pip install -U pip
$pyenv_path/pyenv/bin/pip install -U setuptools
$pyenv_path/pyenv/bin/pip install -r $pyenv_path/pyenv/pip.txt
sync_python_lib
source $pyenv_path/pyenv/bin/activate
}
sync_python_lib(){
pip_list=$(pip list 2>/dev/null|grep -v Package|grep -v '\-\-\-\-\-\-'|awk '{print $1}'|xargs)
$pyenv_path/pyenv/bin/pip install -U pip setuptools
$pyenv_path/pyenv/bin/pip install $pip_list
}
Other_Openssl(){
openssl_version=$(openssl version|grep -Eo '[0-9]\.[0-9]\.[0-9]')
if [ "$openssl_version" = '1.0.1' ] || [ "$openssl_version" = '1.0.0' ];then
opensslVersion="1.0.2r"
if [ ! -f "/usr/local/openssl/lib/libssl.so" ];then
cd /www
openssl_src_file=/www/openssl.tar.gz
wget -O $openssl_src_file ${download_Url}/src/openssl-${opensslVersion}.tar.gz
tmp_size=$(du -b $openssl_src_file|awk '{print $1}')
if [ $tmp_size -lt 703460 ];then
rm -f $openssl_src_file
Red_Error "ERROR: Download openssl-1.0.2 source code fielded."
fi
tar -zxf $openssl_src_file
rm -f $openssl_src_file
cd openssl-${opensslVersion}
./config --openssldir=/usr/local/openssl zlib-dynamic shared
make -j${cpuCore}
make install
echo "/usr/local/openssl/lib" > /etc/ld.so.conf.d/zopenssl.conf
ldconfig
cd ..
rm -rf openssl-${opensslVersion}
is_export_openssl=1
cd ~
fi
fi
}
Insatll_Libressl(){
openssl_version=$(openssl version|grep -Eo '[0-9]\.[0-9]\.[0-9]')
if [ "$openssl_version" = '1.0.1' ] || [ "$openssl_version" = '1.0.0' ];then
opensslVersion="3.0.2"
cd /www
openssl_src_file=/www/openssl.tar.gz
wget -O $openssl_src_file ${download_Url}/install/pyenv/libressl-${opensslVersion}.tar.gz
tmp_size=$(du -b $openssl_src_file|awk '{print $1}')
if [ $tmp_size -lt 703460 ];then
rm -f $openssl_src_file
Red_Error "ERROR: Download libressl-$opensslVersion source code fielded."
fi
tar -zxf $openssl_src_file
rm -f $openssl_src_file
cd libressl-${opensslVersion}
./config –prefix=/usr/local/lib
make -j${cpuCore}
make install
ldconfig
ldconfig -v
cd ..
rm -rf libressl-${opensslVersion}
is_export_openssl=1
cd ~
fi
}
Centos6_Openssl(){
if [ "$os_type" != 'el' ];then
return
fi
if [ "$os_version" != '6' ];then
return
fi
echo 'Centos6 install openssl-1.0.2...'
openssl_rpm_file="/www/openssl.rpm"
wget -O $openssl_rpm_file $download_Url/rpm/centos6/${is64bit}/bt-openssl102.rpm -T 10
tmp_size=$(du -b $openssl_rpm_file|awk '{print $1}')
if [ $tmp_size -lt 102400 ];then
rm -f $openssl_rpm_file
Red_Error "ERROR: Download python env fielded."
fi
rpm -ivh $openssl_rpm_file
rm -f $openssl_rpm_file
is_export_openssl=1
}
Get_Versions(){
redhat_version_file="/etc/redhat-release"
deb_version_file="/etc/issue"
if [ -f $redhat_version_file ];then
os_type='el'
is_aliyunos=$(cat $redhat_version_file|grep Aliyun)
if [ "$is_aliyunos" != "" ];then
return
fi
os_version=$(cat $redhat_version_file|grep CentOS|grep -Eo '([0-9]+\.)+[0-9]+'|grep -Eo '^[0-9]')
if [ "${os_version}" = "5" ];then
os_version=""
fi
else
os_type='ubuntu'
os_version=$(cat $deb_version_file|grep Ubuntu|grep -Eo '([0-9]+\.)+[0-9]+'|grep -Eo '^[0-9]+')
if [ "${os_version}" = "" ];then
os_type='debian'
os_version=$(cat $deb_version_file|grep Debian|grep -Eo '([0-9]+\.)+[0-9]+'|grep -Eo '[0-9]+')
if [ "${os_version}" = "" ];then
os_version=$(cat $deb_version_file|grep Debian|grep -Eo '[0-9]+')
fi
if [ "${os_version}" = "8" ];then
os_version=""
fi
if [ "${is64bit}" = '32' ];then
os_version=""
fi
fi
fi
}
update_panel(){
wget -T 5 -O /tmp/panel.zip $Btapi_Url/install/update/LinuxPanel-${version}.zip
dsize=$(du -b /tmp/panel.zip|awk '{print $1}')
if [ $dsize -lt 10240 ];then
echo "获取更新包失败,请稍后更新或联系宝塔运维"
exit;
fi
unzip -o /tmp/panel.zip -d $setup_path/server/ > /dev/null
rm -f /tmp/panel.zip
cd $setup_path/server/panel/
check_bt=`cat /etc/init.d/bt|grep BT-Task`
if [ "${check_bt}" = "" ];then
rm -f /etc/init.d/bt
wget -O /etc/init.d/bt $download_Url/install/src/bt7.init -T 20
chmod +x /etc/init.d/bt
fi
rm -f /www/server/panel/*.pyc
rm -f /www/server/panel/class/*.pyc
if [ ! -f $setup_path/server/panel/config/config.json ];then
wget -T 5 -O $setup_path/server/panel/config/config.json $download_Url/install/pyenv/config/config.json
wget -T 5 -O $setup_path/server/panel/config/dns_api.json $download_Url/install/pyenv/config/dns_api.json
fi
chattr -i /etc/init.d/bt
chmod +x /etc/init.d/bt
# if [ $up_plugin = 1 ];then
# $pyenv_bin/python /www/server/panel/tools.py update_to6
# fi
}
update_start(){
echo "====================================="
echo "开始升级宝塔Linux面板,请稍候..."
echo "====================================="
}
update_end(){
echo "====================================="
rm -f /dev/shm/bt_sql_tips.pl
kill $(ps aux|grep -E "task.py|main.py"|grep -v grep|awk '{print $2}') &>/dev/null
bash /www/server/panel/init.sh start
echo 'True' > /www/server/panel/data/restart.pl
pkill -9 gunicorn &>/dev/null &
echo "已成功升级到[$version]${Ver}";
}
rm -rf /www/server/phpmyadmin/pma
update_start
check_panel
select_node
install_python
get_version
update_panel
update_end

2
public/robots.txt

@ -0,0 +1,2 @@
User-agent: *
Disallow: /

19
public/router.php

@ -0,0 +1,19 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <[email protected]>
// +----------------------------------------------------------------------
// $Id$
if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) {
return false;
} else {
$_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php';
require __DIR__ . "/index.php";
}

6
public/static/css/bootstrap-table.css
File diff suppressed because it is too large
View File

716
public/static/css/download.css

@ -0,0 +1,716 @@
.nav .new,
.wapnava .new,
.nava.bbs {
display: flex;
align-items: center;
}
.nava.bbs .icon {
width: 20px;
height: 20px;
margin-right: 6px;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAg1JREFUWEftlz9IG1Ecx7+/d63dOwqV6GhXm1i6mM1SyB+sYCfzZ5WCg1BxMF26OHQodGoucXATw6WC2eKYswiC6NAqdhBxFLpU7L1fueKFmF4uL3eSZMiDW+5+fz7v+37v/d4Rejyox/nRfwDPtjNj2o0cCarMb+DgIFm8ahenrkDEyM6B+TUIM+2cFL//AiFvxvRFL/s6QNjI7BAwrRhc2YykjNaSxd1WDg0KZFg5ageGDOT24vr7AYBfBU5AfK6sOFMIgP3Uh/8lIN43Y4UJ5eQAJkupEAtx5gYQLqffCIseWUOy8u1V8dKx8SxCBjYA/FCFEMBTBmbdACLldBVMUwB+gmjNjOU/23Zd2wXPK9nHfM0vJfCRiI/MWCHaVQBHFUcJM67/m7yHAnRELLO1RMF0W4Lb9a42F12zbXMRdgAAEOEtLHnoBsCaNg7mFQDDXjUSCEC1+AYAAwU8FLgC8wszUTj2sQ2DlSATfRUWb9WSerExkvI2ZKBkSWtpP7l+GgzlrrcSgFsHsw8eKcS8HxgBnNTiut1X0BqgnK4yiydgXtpL6KXmRGEjs0pAzg/Arc+m9vDBO+vPzRe7Kf13FKsEniyl7G7W8WBNzIORAnAB4u++ATrO3OAQMdIfAFp2XvlSIAiA7Rs2sgsE/mTfCcy4PnqnGwYNrurvLKNzVe+/XzPVmdyXXc8V+Av1tCwwyQQB+wAAAABJRU5ErkJggg==');
background-position: center;
background-repeat: no-repeat;
background-size: cover;
border-radius: 2px;
image-rendering: -webkit-optimize-contrast;
display: none;
}
.wapnava .new .icon,
.nav .new .icon {
width: 28px;
height: 28px;
margin-right: 4px;
image-rendering: -webkit-optimize-contrast;
}
.head_pc {
background-color: #fff;
position: relative;
z-index: 2;
}
canvas {
height: 100%;
width: 100%;
position: absolute;
top: 0;
z-index: 0;
opacity: 0.5;
}
.top-tips {
z-index: 1;
}
.wrap {
padding: 0;
}
.d1 {
background: linear-gradient(0deg, #fff, #d8efdb, #edf7ef);
padding-bottom: 60px;
position: relative;
}
.d1 .wrap {
padding-top: 60px;
position: relative;
z-index: 1;
}
.d2 .wrap,
.d3 .wrap,
.d4 .wrap {
padding: 70px 0;
}
.install-box {
position: relative;
top: 0;
display: flex;
align-items: center;
width: 24%;
padding: 30px 10px 16px 20px;
border-radius: 8px;
background-color: #fff;
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 30%);
transition: all 0.3s;
}
.install-box:hover {
box-shadow: 0 6px 16px 0 rgb(0 0 0 / 50%);
top: -10px;
}
.install-box.linux,
.install-box.windows {
align-items: center;
width: 41.5%;
}
.install-box + .install-box {
margin-left: 2%;
}
.install-box::before {
content: '';
position: absolute;
top: 20px;
right: 0;
bottom: 0;
width: 100%;
background-repeat: no-repeat;
background-position: right bottom;
background-size: auto 100%;
z-index: 10;
transition: all 0.25s;
opacity: 0.3;
}
.install-box.linux::before {
background-image: url(../images/downico1_01.png);
}
.install-box.windows::before {
background-image: url(../images/downico2_01.png);
}
.install-box.cloud::before {
background-image: url(../images/downico3_01.png);
}
.install-box.ssh::before {
background-image: url(../images/downico4_01.png);
}
.install-box.app::before {
background-image: url(../images/downico5_01.png);
}
.install-box .img {
position: relative;
flex: 0 0 44%;
z-index: 12;
}
.install-box .img img {
display: block;
width: 100%;
}
.install-box .cont {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-left: 5%;
padding-bottom: 12px;
z-index: 12;
}
.install-box.linux .cont,
.install-box.windows .cont {
padding-bottom: 10px;
}
.install-box .bottom {
display: flex;
margin: 0 auto;
}
.install-box .title {
margin-bottom: 14px;
line-height: 1;
font-size: 20px;
font-weight: bold;
transition: color 0.25s;
}
.install-box.linux .title,
.install-box.windows .title {
font-size: 28px;
}
.install-box .desc {
margin-bottom: 14px;
font-size: 12px;
color: #777;
transition: color 0.25s;
}
.install-box.linux .desc,
.install-box.windows .desc {
font-size: 14px;
}
.install-box .mark {
margin-bottom: 20px;
height: 22px;
overflow: hidden;
}
.install-box .mark span {
margin-right: 10px;
line-height: 22px;
height: 22px;
padding: 0 6px;
font-size: 12px;
border-radius: 2px;
display: inline-block;
}
.install-box .mark span:nth-child(1) {
border: #6bdc6b 1px solid;
color: #fff;
background-color: #6bdc6b;
}
.install-box .mark span:nth-child(2) {
border: #ffa565 1px solid;
color: #fff;
background-color: #ffa565;
}
.install-box .mark span:nth-child(3) {
border: #65a7ff 1px solid;
color: #fff;
background-color: #65a7ff;
}
.install-box .mark span:last-child {
margin-right: 0;
}
.install-box .btn {
display: flex;
align-items: center;
justify-content: center;
height: 34px;
padding: 0 16px;
background-image: linear-gradient(#54b891, #22a53d);
border-radius: 6px;
color: #fff;
font-size: 14px;
font-weight: 700;
line-height: 1;
border: none;
}
.install-box .btn.default {
margin-left: 10px;
background: none;
border: 1px solid #777;
color: #777;
}
.install-box .btn:hover {
filter: brightness(120%);
-webkit-filter: brightness(120%);
}
.install-box:hover {
background-image: linear-gradient(#e5f5e5, #fff);
}
.wrap-title {
display: flex;
flex-wrap: wrap;
align-items: center;
margin-bottom: 50px;
}
.wrap-title::before {
content: '';
display: block;
width: 6px;
height: 36px;
margin-right: 20px;
background-color: #20a53a;
}
.wrap-title .text {
margin-right: 20px;
line-height: 36px;
font-size: 36px;
font-weight: bold;
}
.wrap-title a {
font-size: 16px;
}
.d2 .desc,
.d2 .tips,
.d4 .tips {
line-height: 30px;
}
.d2 .desc {
margin-bottom: 16px;
}
.d2 .tips,
.d4 .tips {
margin-top: 16px;
/* color: #20a53a; */
}
.install-code {
display: flex;
flex-wrap: wrap;
align-items: center;
line-height: 30px;
}
.install-code .osname {
display: inline-block;
width: 160px;
font-weight: 700;
}
.install-code + .install-code {
margin-top: 16px;
}
.install-code .code-cont {
display: flex;
align-items: center;
}
.install-code .command {
position: relative;
display: flex;
margin-left: 12px;
margin-right: 15px;
padding: 7px 10px;
border-radius: 3px;
background-color: #20202f;
box-shadow: 0 0 5px #ececec;
font-size: 16px;
color: #fff;
font-family: SimSun;
width: 950px;
}
.install-code .ico-copy {
display: block;
width: 26px;
height: 30px;
background: url(../images/ico-copy.png) no-repeat left center;
cursor: pointer;
width: 65px;
text-indent: 2.4em;
font-weight: bold;
color: #20a53a;
}
.d4 .desc {
margin-bottom: 16px;
line-height: 30px;
}
.bird {
width: 25px;
height: 5px;
display: inline-block;
position: absolute;
transform: skew(20deg, 20deg);
animation: wave 2.5s ease-in-out infinite;
z-index: 0;
}
.bird:before,
.bird:after {
content: '';
width: 100%;
height: 100%;
background-color: #1aa837;
position: absolute;
border-radius: 20%;
opacity: 0.1;
}
.bird:before {
right: 49%;
transform-origin: right;
transform: rotate(-40deg);
animation: flap-left 0.75s ease-in-out infinite;
}
.bird:after {
left: 49%;
transform-origin: left;
transform: rotate(40deg);
animation: flap-right 0.75s linear infinite;
}
/* ��װ�ű� */
.layui-layer .install-code {
display: flex;
flex-wrap: wrap;
line-height: 30px;
}
.layui-layer .install-code .osname {
display: inline-block;
width: 160px;
font-weight: 700;
margin: 20px auto;
font-size: 18px;
}
.layui-layer .install-code + .install-code {
margin-top: 16px;
}
.layui-layer .install-code .code-cont {
display: flex;
align-items: center;
}
.layui-layer .install-code .command {
position: relative;
display: flex;
margin-right: 15px;
padding: 7px 10px;
border-radius: 3px;
background-color: #20202f;
box-shadow: 0 0 5px #ececec;
color: #fff;
font-family: SimSun;
width: 950px;
}
.layui-layer .install-code .ico-copy {
display: block;
width: 26px;
height: 30px;
background: url(../images/ico-copy.png) no-repeat left center;
cursor: pointer;
width: 65px;
text-indent: 2.4em;
font-weight: bold;
color: #20a53a;
}
.layui-layer .install-code {
flex-direction: column;
align-items: flex-start;
}
.layui-layer .install-code .code-cont {
width: 100%;
}
.layui-layer .install-code .command {
flex: 1;
width: 0;
margin-left: 0;
line-height: 30px;
font-size: 12px;
word-break: break-all;
}
@keyframes wave {
40% {
transform: translateY(40px) skew(20deg, 20deg);
}
50% {
transform: translateY(50px) skew(20deg, 20deg);
}
60% {
transform: translateY(40px) skew(20deg, 20deg);
}
100% {
transform: translateY(0) skew(20deg, 20deg);
}
}
@keyframes flap-left {
60% {
transform: rotate(10deg);
}
75% {
transform: rotate(20deg);
}
100% {
transform: rotate(-40deg);
}
}
@keyframes flap-right {
60% {
transform: rotate(-10deg);
}
75% {
transform: rotate(-20deg);
}
100% {
transform: rotate(40deg);
}
}
@media screen and (max-width: 1440px) {
.wrap {
width: 97%;
}
}
@media only screen and (max-width: 1299px) {
.d2 .wrap,
.d3 .wrap,
.d4 .wrap {
padding: 60px 0;
}
}
@media only screen and (max-width: 980px) {
.install-code {
flex-direction: column;
align-items: flex-start;
}
.install-code .code-cont {
width: 100%;
}
.install-code .command {
flex: 1;
width: 0;
margin-left: 0;
line-height: 18px;
font-size: 12px;
word-break: break-all;
}
.d2 .desc,
.d2 .tips {
line-height: 24px;
}
}
@media only screen and (max-width: 768px) {
.d2 .wrap,
.d3 .wrap,
.d4 .wrap {
padding: 40px 0;
}
.d1 .wrap {
padding-top: 40px;
}
.wrap-title {
margin-bottom: 20px;
}
.wrap-title::before {
width: 3px;
height: 20px;
margin-right: 10px;
}
.wrap-title .text {
font-size: 20px;
margin-right: 10px;
}
.wrap-title a {
font-size: 12px;
}
}
@media screen and (max-width: 1440px) {
.wrap {
width: 97%;
}
}
@media only screen and (max-width: 1299px) {
.d2 .wrap,
.d3 .wrap,
.d4 .wrap {
padding: 60px 0;
}
.install-box {
padding: 24px 20px 10px;
padding-right: 0;
}
.install-box.linux .title,
.install-box.windows .title,
.install-box .title {
margin-bottom: 10px;
font-size: 20px;
}
.install-box .img {
flex: 0 0 40%;
}
.install-box.linux .title,
.install-box.windows .title {
margin-bottom: 16px;
}
.install-box .desc {
font-size: 13px;
}
.install-box .btn {
padding: 0 12px;
height: 30px;
border-radius: 8px;
font-size: 12px;
}
}
@media only screen and (max-width: 1200px) {
.install-box .title {
font-size: 16px;
}
.install-box .desc {
font-size: 12px;
}
}
@media only screen and (max-width: 980px) {
.install-list {
justify-content: flex-start;
flex-wrap: wrap;
}
.install-box,
.install-box.linux,
.install-box.windows {
width: 48%;
padding: 15px;
align-items: center;
}
.install-box:nth-child(2n) {
margin-left: 4%;
}
.install-box.app {
margin-top: 30px;
margin-left: 0;
}
.install-box.monitor {
margin-top: 30px;
}
.install-box.linux .title,
.install-box.windows .title,
.install-box .title {
font-size: 16px;
}
.install-box .btn {
height: 24px;
padding: 0 10px;
border-radius: 6px;
font-weight: normal;
}
.install-box .btn + .btn {
margin-left: 5px;
}
.install-code {
flex-direction: column;
align-items: flex-start;
}
.install-code .code-cont {
width: 100%;
}
.install-code .command {
flex: 1;
width: 0;
margin-left: 0;
line-height: 18px;
font-size: 12px;
word-break: break-all;
}
.online-install-cont {
flex-direction: column;
}
.online-install-cont .form {
margin-bottom: 20px;
}
.online-install-cont .pr-70 {
padding-right: 0;
}
.online-install-cont .pl-10 {
padding-left: 0;
}
.online-install-cont .first-line {
flex-wrap: wrap;
}
.online-install-cont .server-line {
width: 100%;
flex: none;
}
.online-install-cont .port-line {
flex: none;
margin-top: 20px;
width: 100%;
}
.d2 .desc,
.d2 .tips {
line-height: 24px;
}
}
@media only screen and (max-width: 768px) {
.d2 .wrap,
.d3 .wrap,
.d4 .wrap {
padding: 40px 0;
}
.d1 .wrap {
padding-top: 40px;
}
.install-box {
flex-wrap: wrap;
}
.install-box .img,
.install-box .cont {
flex: 0 0 100%;
}
.install-box .cont {
margin-top: 10px;
margin-left: 0;
padding: 0;
}
.install-box .title,
.install-box.linux .title,
.install-box.windows .title {
margin-bottom: 10px;
}
.install-box .desc {
margin-bottom: 10px;
}
.wrap-title {
margin-bottom: 20px;
}
.wrap-title::before {
width: 3px;
height: 20px;
margin-right: 10px;
}
.wrap-title .text {
font-size: 20px;
margin-right: 10px;
}
.wrap-title a {
font-size: 12px;
}
}

1057
public/static/css/sanren.css
File diff suppressed because it is too large
View File

1491
public/static/css/style.css
File diff suppressed because it is too large
View File

BIN
public/static/file/kaixin.zip

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

BIN
public/static/images/account.png

After

Width: 14  |  Height: 69  |  Size: 586 B

BIN
public/static/images/addc.png

After

Width: 50  |  Height: 50  |  Size: 1.3 KiB

BIN
public/static/images/bt.png

After

Width: 32  |  Height: 32  |  Size: 1.7 KiB

BIN
public/static/images/bt_monitor.png

After

Width: 1752  |  Height: 1164  |  Size: 194 KiB

BIN
public/static/images/bto_copy.png

After

Width: 16  |  Height: 16  |  Size: 265 B

BIN
public/static/images/close.png

After

Width: 32  |  Height: 33  |  Size: 888 B

BIN
public/static/images/downico1_01.png

After

Width: 152  |  Height: 180  |  Size: 5.4 KiB

BIN
public/static/images/downico2_01.png

After

Width: 203  |  Height: 180  |  Size: 3.9 KiB

BIN
public/static/images/eye.png

After

Width: 16  |  Height: 16  |  Size: 1.2 KiB

BIN
public/static/images/eye_close.png

After

Width: 16  |  Height: 16  |  Size: 1.4 KiB

BIN
public/static/images/footbg_02.jpg

After

Width: 1920  |  Height: 500  |  Size: 35 KiB

BIN
public/static/images/hot_03.png

After

Width: 84  |  Height: 83  |  Size: 7.0 KiB

BIN
public/static/images/i1abg_03.png

After

Width: 7  |  Height: 70  |  Size: 1.4 KiB

BIN
public/static/images/i1aico_03.png

After

Width: 27  |  Height: 30  |  Size: 1.3 KiB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save