mirror of
https://github.com/acepanel/panel.git
synced 2026-02-07 07:03:12 +08:00
特性(网站):新增备份功能及问题修复
This commit is contained in:
@@ -286,7 +286,10 @@ class PluginsController extends Controller
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
Plugin::query()->where('slug', $slug)->update(['show' => $show]);
|
||||
Plugin::query()->where('slug', $slug)->updateOrInsert(
|
||||
['slug' => $slug],
|
||||
['show' => $show]
|
||||
);
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = '设置成功';
|
||||
|
||||
@@ -34,7 +34,13 @@ class SettingsController extends Controller
|
||||
// 从nginx配置文件中获取面板端口
|
||||
$nginxConf = file_get_contents('/www/server/nginx/conf/nginx.conf');
|
||||
preg_match('/listen\s+(\d+)/', $nginxConf, $matches);
|
||||
if (!isset($matches[1])) {
|
||||
$res['code'] = 1;
|
||||
$res['msg'] = '获取面板端口失败,请检查nginx主配置文件';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
// API
|
||||
$api = 0;
|
||||
$apiToken = '';
|
||||
if (isset($settingArr['api']) && $settingArr['api'] == 1) {
|
||||
@@ -42,11 +48,12 @@ class SettingsController extends Controller
|
||||
$apiToken = $settingArr['api_token'] ?? '';
|
||||
}
|
||||
|
||||
if (!isset($matches[1])) {
|
||||
$res['code'] = 1;
|
||||
$res['msg'] = '获取面板端口失败,请检查nginx主配置文件';
|
||||
return response()->json($res);
|
||||
// 多设备登录
|
||||
$multiLogin = 0;
|
||||
if (isset($settingArr['multi_login']) && $settingArr['multi_login'] == 1) {
|
||||
$multiLogin = 1;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $settingArr['name'],
|
||||
'username' => $request->user()->username,
|
||||
@@ -54,6 +61,7 @@ class SettingsController extends Controller
|
||||
'port' => $matches[1],
|
||||
'api' => $api,
|
||||
'api_token' => $apiToken,
|
||||
'multi_login' => $multiLogin,
|
||||
];
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
@@ -72,10 +80,11 @@ class SettingsController extends Controller
|
||||
$settings = $request->all();
|
||||
// 将数据入库
|
||||
foreach ($settings as $key => $value) {
|
||||
if ($key == 'access_token' || $key == 'username' || $key == 'password' || $key == 'api_token' || $key == 'api') {
|
||||
if ($key == 'access_token' || $key == 'username' || $key == 'password' || $key == 'api_token' || $key == 'api' || $key == 'port') {
|
||||
continue;
|
||||
}
|
||||
Setting::query()->where('name', $key)->update(['value' => $value]);
|
||||
// 创建或更新
|
||||
Setting::query()->updateOrCreate(['name' => $key], ['value' => $value]);
|
||||
}
|
||||
// 单独处理用户名和密码
|
||||
if ($request->input('username') != $request->user()->username) {
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
* 耗子Linux面板 - 用户控制器
|
||||
* @author 耗子
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -34,15 +36,21 @@ class UsersController extends Controller
|
||||
'errors' => $e->errors()
|
||||
], 422);
|
||||
}
|
||||
if (auth()->attempt(['username' => $credentials['username'], 'password' => $credentials['password']], $credentials['remember'])) {
|
||||
if (auth()->attempt(['username' => $credentials['username'], 'password' => $credentials['password']],
|
||||
$credentials['remember'])) {
|
||||
$user = auth()->user();
|
||||
$user->tokens()->delete();
|
||||
// 多设备登录
|
||||
$multiLogin = Setting::query()->where('name', 'multi_login')->value('value');
|
||||
if ($multiLogin != 1) {
|
||||
$user->tokens()->delete();
|
||||
}
|
||||
$token = $user->createToken('token')->plainTextToken;
|
||||
return response()->json(['code' => 0, 'msg' => '登录成功', 'data' => ['access_token' => $token]]);
|
||||
} else {
|
||||
return response()->json(['code' => 1, 'msg' => '登录失败,用户名或密码错误']);
|
||||
}
|
||||
}
|
||||
|
||||
public function getInfo(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
@@ -30,6 +30,12 @@ class WebsitesController extends Controller
|
||||
'data' => []
|
||||
]);
|
||||
}
|
||||
foreach ($websiteList as $website) {
|
||||
// 如果PHP是0,将其设置为字符串的00
|
||||
if ($website->php == '0') {
|
||||
$website->php = '00';
|
||||
}
|
||||
}
|
||||
return response()->json([
|
||||
'code' => 0,
|
||||
'msg' => '获取成功',
|
||||
@@ -335,10 +341,10 @@ EOF;
|
||||
$website['domain'] .= PHP_EOL.$v;
|
||||
}
|
||||
}
|
||||
// 从nginx配置中root标记位提取全部根目录
|
||||
// 从nginx配置中root标记位提取运行目录
|
||||
$root_raw = $this->cut('# root标记位开始', '# root标记位结束', $nginx_config);
|
||||
preg_match_all('/root\s+(.+);/', $root_raw, $matches2);
|
||||
$website['path'] = $matches2[1][0];
|
||||
$website['root'] = $matches2[1][0];
|
||||
// 从nginx配置中index标记位提取全部默认文件
|
||||
$index_raw = $this->cut('# index标记位开始', '# index标记位结束', $nginx_config);
|
||||
preg_match_all('/index\s+(.+);/', $index_raw, $matches3);
|
||||
@@ -396,6 +402,11 @@ EOF;
|
||||
// 读取访问日志
|
||||
$website['log'] = shell_exec('tail -n 100 /www/wwwlogs/'.$name.'.log');
|
||||
|
||||
// 如果PHP是0,将其设置为字符串的00
|
||||
if ($website['php'] == '0') {
|
||||
$website['php'] = '00';
|
||||
}
|
||||
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = $website;
|
||||
@@ -413,6 +424,20 @@ EOF;
|
||||
$name = $request->input('name');
|
||||
$config = $request->input('config');
|
||||
|
||||
$website = Website::query()->where('name', $name)->first();
|
||||
if (!$website) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '网站不存在',
|
||||
], 200);
|
||||
}
|
||||
if ($website->status != 1) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '网站已停用,请先启用',
|
||||
], 200);
|
||||
}
|
||||
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
|
||||
@@ -467,12 +492,12 @@ EOF;
|
||||
$configRaw = str_replace($port_config_old, PHP_EOL.$port.PHP_EOL.' ', $configRaw);
|
||||
}
|
||||
|
||||
// 网站目录
|
||||
// 运行目录
|
||||
$pathConfig = $this->cut('# root标记位开始', '# root标记位结束', $configRaw);
|
||||
preg_match_all('/root\s+(.+);/', $pathConfig, $matches1);
|
||||
$pathConfigOld = $matches1[1][0];
|
||||
if (!empty(trim($pathConfigOld)) && $pathConfigOld != PHP_EOL) {
|
||||
$pathConfigNew = str_replace($pathConfigOld, $config['path'], $pathConfig);
|
||||
$pathConfigNew = str_replace($pathConfigOld, $config['root'], $pathConfig);
|
||||
$configRaw = str_replace($pathConfig, $pathConfigNew, $configRaw);
|
||||
}
|
||||
|
||||
@@ -497,7 +522,7 @@ EOF;
|
||||
if (is_dir($config['path'])) {
|
||||
file_put_contents($config['path'].'/.user.ini', $open_basedir);
|
||||
// 为.user.ini文件添加i权限
|
||||
shell_exec('chattr +i '.$config['path'].'/.user.ini');
|
||||
// shell_exec('chattr +i '.$config['path'].'/.user.ini');
|
||||
}
|
||||
} else {
|
||||
// 移除.user.ini文件的i权限
|
||||
@@ -600,8 +625,10 @@ EOL;
|
||||
}
|
||||
|
||||
// 将数据入库
|
||||
Website::query()->where('name', $name)->update(['php' => $config['php']]);
|
||||
Website::query()->where('name', $name)->update(['ssl' => $config['ssl']]);
|
||||
$website->php = $config['php'];
|
||||
$website->ssl = $config['ssl'];
|
||||
$website->path = $config['path'];
|
||||
$website->save();
|
||||
file_put_contents('/www/server/vhost/'.$name.'.conf', $configRaw);
|
||||
file_put_contents('/www/server/vhost/rewrite/'.$name.'.conf', $config['rewrite']);
|
||||
shell_exec('systemctl reload nginx');
|
||||
@@ -637,7 +664,376 @@ EOL;
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
// 裁剪字符串
|
||||
/**
|
||||
* 获取备份列表
|
||||
*/
|
||||
public function getBackupList(): JsonResponse
|
||||
{
|
||||
$backupPath = '/www/backup/website';
|
||||
// 判断备份目录是否存在
|
||||
if (!is_dir($backupPath)) {
|
||||
mkdir($backupPath, 0644, true);
|
||||
}
|
||||
$backupFiles = scandir($backupPath);
|
||||
$backupFiles = array_diff($backupFiles, ['.', '..']);
|
||||
$backupFiles = array_values($backupFiles);
|
||||
$backupFiles = array_map(function ($backupFile) {
|
||||
return [
|
||||
'backup' => $backupFile,
|
||||
'size' => formatBytes(filesize('/www/backup/website/'.$backupFile)),
|
||||
];
|
||||
}, $backupFiles);
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = $backupFiles;
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建备份
|
||||
*/
|
||||
public function createBackup(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒数据
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'name' => 'required|max:255',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '参数错误:'.$e->getMessage(),
|
||||
'errors' => $e->errors()
|
||||
], 200);
|
||||
}
|
||||
|
||||
$backupPath = '/www/backup/website';
|
||||
// 判断备份目录是否存在
|
||||
if (!is_dir($backupPath)) {
|
||||
mkdir($backupPath, 0644, true);
|
||||
}
|
||||
|
||||
// 从数据库中获取网站目录
|
||||
$sitePath = Website::query()->where('name', $credentials['name'])->value('path');
|
||||
$backupFile = $backupPath.'/'.$credentials['name'].'_'.date('YmdHis').'.zip';
|
||||
shell_exec('zip -r '.$backupFile.' '.$sitePath.' 2>&1');
|
||||
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传备份
|
||||
*/
|
||||
public function uploadBackup(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒数据
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'file' => 'required|file',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '参数错误:'.$e->getMessage(),
|
||||
'errors' => $e->errors()
|
||||
], 200);
|
||||
}
|
||||
|
||||
$file = $request->file('file');
|
||||
$backupPath = '/www/backup/website';
|
||||
|
||||
// 判断备份目录是否存在
|
||||
if (!is_dir($backupPath)) {
|
||||
mkdir($backupPath, 0644, true);
|
||||
}
|
||||
$backupFile = $backupPath.'/'.$file->getClientOriginalName();
|
||||
$file->move($backupPath, $file->getClientOriginalName());
|
||||
|
||||
// 返回文件名
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = $file->getClientOriginalName();
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复备份
|
||||
*/
|
||||
public function restoreBackup(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒数据
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'name' => 'required|max:255',
|
||||
'backup' => 'required|max:255',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '参数错误:'.$e->getMessage(),
|
||||
'errors' => $e->errors()
|
||||
], 200);
|
||||
}
|
||||
|
||||
$backupPath = '/www/backup/website';
|
||||
// 判断备份目录是否存在
|
||||
if (!is_dir($backupPath)) {
|
||||
mkdir($backupPath, 0644, true);
|
||||
}
|
||||
$backupFile = $backupPath.'/'.$credentials['backup'];
|
||||
// 判断备份文件是否存在
|
||||
if (!is_file($backupFile)) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '备份文件不存在',
|
||||
], 200);
|
||||
}
|
||||
|
||||
shell_exec('rm -rf /www/wwwroot/'.$credentials['name'].'/*');
|
||||
shell_exec('unzip -o '.$backupFile.' -d /www/wwwroot/'.$credentials['name'].' 2>&1');
|
||||
// 设置权限
|
||||
shell_exec('chown -R www:www /www/wwwroot/'.$credentials['name']);
|
||||
shell_exec('chmod -R 755 /www/wwwroot/'.$credentials['name']);
|
||||
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除备份
|
||||
*/
|
||||
public function deleteBackup(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒数据
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'backup' => 'required|max:255',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '参数错误:'.$e->getMessage(),
|
||||
'errors' => $e->errors()
|
||||
], 200);
|
||||
}
|
||||
|
||||
$backupPath = '/www/backup/website';
|
||||
// 判断备份目录是否存在
|
||||
if (!is_dir($backupPath)) {
|
||||
mkdir($backupPath, 0644, true);
|
||||
}
|
||||
$backupFile = $backupPath.'/'.$credentials['backup'];
|
||||
// 判断备份文件是否存在
|
||||
if (!is_file($backupFile)) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '备份文件不存在',
|
||||
], 200);
|
||||
}
|
||||
|
||||
unlink($backupFile);
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置网站配置文件
|
||||
*/
|
||||
public function resetSiteConfig(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'name' => 'required|max:255',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '参数错误:'.$e->getMessage(),
|
||||
'errors' => $e->errors()
|
||||
], 200);
|
||||
}
|
||||
|
||||
$website = Website::query()->where('name', $credentials['name'])->first();
|
||||
if (!$website) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '网站不存在',
|
||||
], 200);
|
||||
}
|
||||
|
||||
// 如果PHP是0,将其设置为字符串的00
|
||||
if ($website['php'] == '0') {
|
||||
$website['php'] = '00';
|
||||
}
|
||||
|
||||
// 更新网站状态为运行
|
||||
$website->status = 1;
|
||||
// 更新网站ssl状态为关闭
|
||||
$website->ssl = 0;
|
||||
$website->save();
|
||||
|
||||
$nginxConfig = <<<EOF
|
||||
# 配置文件中的标记位请勿随意修改,改错将导致面板无法识别!
|
||||
# 有自定义配置需求的,请将自定义的配置写在各标记位下方。
|
||||
server
|
||||
{
|
||||
# port标记位开始
|
||||
listen 80;
|
||||
# port标记位结束
|
||||
# server_name标记位开始
|
||||
server_name localhost;
|
||||
# server_name标记位结束
|
||||
# index标记位开始
|
||||
index index.php index.html;
|
||||
# index标记位结束
|
||||
# root标记位开始
|
||||
root $website[path];
|
||||
# root标记位结束
|
||||
|
||||
# ssl标记位开始
|
||||
# ssl标记位结束
|
||||
|
||||
# php标记位开始
|
||||
include enable-php-$website[php].conf;
|
||||
# php标记位结束
|
||||
|
||||
# waf标记位开始
|
||||
waf on;
|
||||
waf_rule_path /www/server/nginx/ngx_waf/assets/rules/;
|
||||
waf_mode DYNAMIC;
|
||||
waf_cc_deny rate=1000r/m duration=60m;
|
||||
waf_cache capacity=50;
|
||||
# waf标记位结束
|
||||
|
||||
# 错误页配置,可自行设置
|
||||
#error_page 404 /404.html;
|
||||
#error_page 502 /502.html;
|
||||
|
||||
# 伪静态规则引入,修改后将导致面板设置的伪静态规则失效
|
||||
include /www/server/vhost/rewrite/$website[name].conf;
|
||||
|
||||
# 面板默认禁止访问部分敏感目录,可自行修改
|
||||
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn)
|
||||
{
|
||||
return 404;
|
||||
}
|
||||
# 面板默认不记录静态资源的访问日志并开启1小时浏览器缓存,可自行修改
|
||||
location ~ .*\.(js|css)$
|
||||
{
|
||||
expires 1h;
|
||||
error_log /dev/null;
|
||||
access_log /dev/null;
|
||||
}
|
||||
access_log /www/wwwlogs/$website[name].log;
|
||||
error_log /www/wwwlogs/$website[name].log;
|
||||
}
|
||||
EOF;
|
||||
file_put_contents('/www/server/vhost/'.$website['name'].'.conf', $nginxConfig);
|
||||
// 重置伪静态规则
|
||||
shell_exec('echo "" > /www/server/vhost/rewrite/'.$website['name'].'.conf');
|
||||
// 重载nginx
|
||||
shell_exec('systemctl reload nginx');
|
||||
|
||||
// 返回
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置网站运行状态
|
||||
*/
|
||||
public function setSiteStatus(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'name' => 'required|max:255',
|
||||
'status' => 'required|in:0,1',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '参数错误:'.$e->getMessage(),
|
||||
'errors' => $e->errors()
|
||||
], 200);
|
||||
}
|
||||
|
||||
$website = Website::query()->where('name', $credentials['name'])->first();
|
||||
if (!$website) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '网站不存在',
|
||||
], 200);
|
||||
}
|
||||
|
||||
$nginxConfig = file_get_contents('/www/server/vhost/'.$website['name'].'.conf');
|
||||
|
||||
// 运行目录
|
||||
$pathConfig = $this->cut('# root标记位开始', '# root标记位结束', $nginxConfig);
|
||||
preg_match_all('/root\s+(.+);/', $pathConfig, $matches1);
|
||||
$pathConfigOld = $matches1[1][0];
|
||||
if (!empty(trim($pathConfigOld)) && $pathConfigOld != PHP_EOL) {
|
||||
if ($credentials['status'] == 0) {
|
||||
$pathConfigNew = str_replace($pathConfigOld, '/www/server/nginx/html', $pathConfig);
|
||||
// 将旧配置追加到新配置中
|
||||
$pathConfigNew .= '# '.$pathConfigOld.PHP_EOL.' ';
|
||||
} else {
|
||||
// 匹配旧配置
|
||||
preg_match_all('/# (.+)/', $pathConfig, $matches2);
|
||||
// 还原旧配置
|
||||
$pathConfigNew = str_replace($pathConfigOld, $matches2[1][0], $pathConfig);
|
||||
// 删除旧配置
|
||||
$pathConfigNew = str_replace(PHP_EOL.' # '.$matches2[1][0], '', $pathConfigNew);
|
||||
}
|
||||
$nginxConfig = str_replace($pathConfig, $pathConfigNew, $nginxConfig);
|
||||
}
|
||||
|
||||
// 默认文件
|
||||
$indexConfig = $this->cut('# index标记位开始', '# index标记位结束', $nginxConfig);
|
||||
preg_match_all('/index\s+(.+);/', $indexConfig, $matches2);
|
||||
$indexConfigOld = $matches2[1][0];
|
||||
if (!empty(trim($indexConfigOld)) && $indexConfigOld != PHP_EOL) {
|
||||
if ($credentials['status'] == 0) {
|
||||
$indexConfigNew = str_replace($indexConfigOld, 'stop.html', $indexConfig);
|
||||
// 将旧配置追加到新配置中
|
||||
$indexConfigNew .= '# '.$indexConfigOld.PHP_EOL.' ';
|
||||
} else {
|
||||
// 匹配旧配置
|
||||
preg_match_all('/# (.+)/', $indexConfig, $matches2);
|
||||
// 还原旧配置
|
||||
$indexConfigNew = str_replace($indexConfigOld, $matches2[1][0], $indexConfig);
|
||||
// 删除旧配置
|
||||
$indexConfigNew = str_replace(PHP_EOL.' # '.$matches2[1][0], '', $indexConfigNew);
|
||||
}
|
||||
$nginxConfig = str_replace($indexConfig, $indexConfigNew, $nginxConfig);
|
||||
}
|
||||
|
||||
// 写入配置文件
|
||||
file_put_contents('/www/server/vhost/'.$website['name'].'.conf', $nginxConfig);
|
||||
|
||||
$website->status = $credentials['status'];
|
||||
$website->save();
|
||||
|
||||
// 重载nginx
|
||||
shell_exec('systemctl reload nginx');
|
||||
|
||||
// 返回
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪字符串
|
||||
* @param $begin
|
||||
* @param $end
|
||||
* @param $str
|
||||
* @return string
|
||||
*/
|
||||
private function cut($begin, $end, $str): string
|
||||
{
|
||||
$b = mb_strpos($str, $begin) + mb_strlen($begin);
|
||||
|
||||
Reference in New Issue
Block a user