Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a8b8a1e789 | |||
| 4b9abaf9df | |||
| 75dbf21148 | |||
| d4fd16060c | |||
| b0bbd0f43e | |||
| 9175f4c074 | |||
| e11c4a4456 | |||
| a77f8c6ecc | |||
| 0864e2fa1d | |||
| 8a82b01af0 | |||
| 255892a0bf | |||
| abe00725e4 | |||
| 861077bfee | |||
| d7e7cd1485 | |||
| 45161c522c | |||
| 330d9bbb87 | |||
| 6e7cb261e3 | |||
| a1a8659258 | |||
| f91d079296 | |||
| d4de5c6697 | |||
| 7a01ee6f45 | |||
| 66957ca86d | |||
| ab85294dd6 | |||
| ed36715d90 | |||
| 99df59eb1d | |||
| 371612e6c0 | |||
| c28c79ac62 | |||
| 182d811664 | |||
| beb333e74f | |||
| 7df1fab7ff | |||
| 642d3339fc | |||
| b4b43cb4c4 | |||
| 939867d100 | |||
| 53baea1422 | |||
| 3b5eb5ecf3 | |||
| 85997100ed | |||
| 408d486e76 | |||
| 5a22d0c966 | |||
| b32f8ececf | |||
| 0e4f5cc29d | |||
| d5eed03dae | |||
| 3c2c928e83 | |||
| 268d9fe2f0 | |||
| 289831540b | |||
| 73510f5972 | |||
| 6faf2950b8 | |||
| a52f9029f2 | |||
| ae3addbc40 | |||
| bfe2cf3472 | |||
| 1aafa756c6 | |||
| 36b80c4b6f | |||
| 329f58da4b | |||
| 699562d504 | |||
| c500dd292e | |||
| b0008444cd | |||
| 397d96d0ed | |||
| e9ffcac3b1 | |||
| 9c63a56b14 |
@@ -1,69 +0,0 @@
|
||||
# Config file for [Air](https://github.com/air-verse/air) in TOML format
|
||||
|
||||
# Working directory
|
||||
# . or absolute path, please note that the directories following must be under root.
|
||||
root = "."
|
||||
tmp_dir = "storage/temp"
|
||||
|
||||
[build]
|
||||
# Array of commands to run before each build
|
||||
pre_cmd = []
|
||||
# Just plain old shell command. You could use `make` as well.
|
||||
cmd = "go build -trimpath -buildvcs=false -o storage/temp/main.exe ./cmd/web"
|
||||
# Array of commands to run after ^C
|
||||
post_cmd = []
|
||||
# Binary file yields from `cmd`.
|
||||
bin = "storage/temp/main.exe"
|
||||
# Customize binary, can setup environment variables when run your app.
|
||||
full_bin = ""
|
||||
# Watch these filename extensions.
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
# Ignore these filename extensions or directories.
|
||||
exclude_dir = ["storage", "web"]
|
||||
# Watch these directories if you specified.
|
||||
include_dir = []
|
||||
# Watch these files.
|
||||
include_file = []
|
||||
# Exclude files.
|
||||
exclude_file = []
|
||||
# Exclude specific regular expressions.
|
||||
exclude_regex = ["_test\\.go"]
|
||||
# Exclude unchanged files.
|
||||
exclude_unchanged = true
|
||||
# Follow symlink for directories
|
||||
follow_symlink = true
|
||||
# This log file places in your tmp_dir.
|
||||
log = "build-errors.log"
|
||||
# It's not necessary to trigger build each time file changes if it's too frequent.
|
||||
delay = 2000
|
||||
# Stop running old binary when build errors occur.
|
||||
stop_on_error = true
|
||||
# Send Interrupt signal before killing process (windows does not support this feature)
|
||||
send_interrupt = false
|
||||
# Delay after sending Interrupt signal
|
||||
kill_delay = 500 # nanosecond
|
||||
# Rerun binary or not
|
||||
rerun = false
|
||||
# Delay after each execution
|
||||
rerun_delay = 500
|
||||
|
||||
[log]
|
||||
# Show log time
|
||||
time = false
|
||||
# Only show main log (silences watcher, build, runner)
|
||||
main_only = false
|
||||
|
||||
[color]
|
||||
# Customize each part's color. If no color found, use the raw app log.
|
||||
main = "magenta"
|
||||
watcher = "cyan"
|
||||
build = "yellow"
|
||||
runner = "green"
|
||||
|
||||
[misc]
|
||||
# Delete tmp directory on exit
|
||||
clean_on_exit = true
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = true
|
||||
keep_scroll = true
|
||||
@@ -1,6 +0,0 @@
|
||||
*.sh linguist-language=Go
|
||||
*.ts linguist-language=Go
|
||||
*.js linguist-language=Go
|
||||
*.css linguist-language=Go
|
||||
*.scss linguist-language=Go
|
||||
*.html linguist-language=Go
|
||||
@@ -1,246 +0,0 @@
|
||||
# 贡献指南
|
||||
|
||||
感谢你考虑为 AcePanel 做出贡献!这份文档将帮助你了解如何参与到项目中来。
|
||||
|
||||
## 目录
|
||||
|
||||
- [我能做什么贡献?](#我能做什么贡献)
|
||||
- [开发环境设置](#开发环境设置)
|
||||
- [开发流程](#开发流程)
|
||||
- [代码规范](#代码规范)
|
||||
- [提交信息规范](#提交信息规范)
|
||||
- [Pull Request 流程](#pull-request-流程)
|
||||
|
||||
## 我能做什么贡献?
|
||||
|
||||
你可以通过以下方式为 AcePanel 做出贡献:
|
||||
|
||||
- 🐛 报告 Bug
|
||||
- 💡 提出新功能建议
|
||||
- 📝 改进文档
|
||||
- 🔧 修复 Bug
|
||||
- ✨ 实现新功能
|
||||
- 🧪 编写测试
|
||||
- 🌍 翻译面板及文档
|
||||
|
||||
## 开发环境设置
|
||||
|
||||
### 前置要求
|
||||
|
||||
- Go 1.25 或更高版本
|
||||
- Node.js 22+ 和 pnpm
|
||||
- Git
|
||||
- 本地开发环境没有特殊要求,但必须在 Linux 上运行和测试
|
||||
- 在开发及测试前端项目时,需修改面板配置文件打开 debug 模式或者关闭安全入口
|
||||
|
||||
### 克隆仓库
|
||||
|
||||
```bash
|
||||
git clone https://github.com/acepanel/panel.git
|
||||
cd panel
|
||||
```
|
||||
|
||||
### 后端设置
|
||||
|
||||
1. 安装 Go 依赖:
|
||||
|
||||
```bash
|
||||
go mod download
|
||||
```
|
||||
|
||||
2. 复制配置文件:
|
||||
|
||||
```bash
|
||||
cp config.example.yml config.yml # 按需修改配置
|
||||
```
|
||||
|
||||
3. 构建项目:
|
||||
|
||||
```bash
|
||||
go build -o ace ./cmd/ace # 主程序
|
||||
go build -o cli ./cmd/cli # CLI 工具
|
||||
```
|
||||
|
||||
### 前端设置
|
||||
|
||||
1. 进入前端目录:
|
||||
|
||||
```bash
|
||||
cd web
|
||||
```
|
||||
|
||||
2. 安装依赖:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. 配置开发环境:
|
||||
|
||||
```bash
|
||||
cp .env.production .env # 按需修改
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts # 配置 Linux 测试服务器信息
|
||||
pnpm run gen-auto-import # 生成自动导入文件,开发中无需导入 vue alova 等常用包
|
||||
pnpm run gettext:compile # 预编译翻译文件,否则开发中没有翻译
|
||||
```
|
||||
|
||||
4. 启动开发服务器:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 开发流程
|
||||
|
||||
### 项目架构
|
||||
|
||||
AcePanel 采用类 DDD 分层架构,依赖关系为:route → service → biz ← data
|
||||
|
||||
主要目录结构:
|
||||
|
||||
- `cmd/` - 程序入口(ace 主程序、cli 工具)
|
||||
- `internal/route/` - HTTP 路由定义
|
||||
- `internal/service/` - 服务层(处理 HTTP 请求/响应)
|
||||
- `internal/biz/` - 业务逻辑层(定义业务接口和领域模型)
|
||||
- `internal/data/` - 数据访问层(实现 biz 接口)
|
||||
- `pkg/` - 工具函数和通用包
|
||||
- `web/` - Vue 3 前端项目
|
||||
|
||||
### 开发新功能的标准流程
|
||||
|
||||
1. **在 `internal/route/` 中添加路由**
|
||||
- 按需注入需要的服务
|
||||
|
||||
2. **在 `internal/service/` 中实现服务方法**
|
||||
- 处理请求验证和响应格式化
|
||||
- 使用 `Success()` 返回成功响应
|
||||
- 使用 `Error()` 返回错误响应
|
||||
- 使用 `ErrorSystem()` 返回系统严重错误
|
||||
|
||||
3. **在 `internal/biz/` 中定义业务接口**
|
||||
- 定义 Repository 接口
|
||||
- 定义领域模型结构体
|
||||
- 保持接口简洁明确
|
||||
|
||||
4. **在 `internal/data/` 中实现 biz 接口**
|
||||
- 创建 repo 结构体
|
||||
- 实现构造函数
|
||||
- 实现所有接口方法
|
||||
|
||||
5. **使用 Wire 进行依赖注入**
|
||||
- 在对应的 `包名.go` 文件中添加 Provider
|
||||
- 运行 `go generate ./...` 生成依赖注入代码
|
||||
|
||||
## 代码规范
|
||||
|
||||
**所有代码注释必须使用简体中文**
|
||||
|
||||
面板基于 Gettext 搭建了自动化国际化流程,所有对用户可见的文本均需支持国际化,原文使用英文。
|
||||
|
||||
开发中 Go 代码中注入 `*gotext.Locale`,前端导入 `useGettext` 进行翻译。
|
||||
|
||||
### Go 代码规范
|
||||
|
||||
- 遵循 Go 官方代码风格
|
||||
- 使用 `gofmt` 格式化代码和 `golangci-lint` 检查代码质量
|
||||
- 函数和方法注释必须以函数名开头,复杂逻辑应添加注释说明
|
||||
|
||||
### 前端代码规范
|
||||
|
||||
- 使用 Vue 3 Composition API
|
||||
- 遵循项目已有的组件结构和编码风格
|
||||
- 使用 TypeScript 进行类型检查
|
||||
- 运行 `pnpm lint` 检查代码质量
|
||||
|
||||
## 提交信息规范
|
||||
|
||||
我们使用语义化的提交信息,格式为:
|
||||
|
||||
```
|
||||
<类型>(<范围>): <简短描述>
|
||||
|
||||
<详细描述>(可选)
|
||||
|
||||
<关联的 Issue>(可选)
|
||||
```
|
||||
|
||||
### 类型
|
||||
|
||||
- `feat`: 新功能
|
||||
- `fix`: Bug 修复
|
||||
- `docs`: 文档更新
|
||||
- `style`: 代码格式调整(不影响功能)
|
||||
- `refactor`: 代码重构
|
||||
- `perf`: 性能优化
|
||||
- `test`: 测试相关
|
||||
- `chore`: 构建/工具链相关
|
||||
|
||||
### 示例
|
||||
|
||||
```
|
||||
feat(website): 添加网站备份功能
|
||||
|
||||
实现了网站配置和数据的自动备份功能,支持:
|
||||
- 按计划自动备份
|
||||
- 手动立即备份
|
||||
- 备份文件压缩存储
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
```
|
||||
fix(apache): 修复代理配置解析错误
|
||||
|
||||
修复了在解析包含特殊字符的代理配置时的崩溃问题
|
||||
```
|
||||
|
||||
## Pull Request 流程
|
||||
|
||||
### 1. Fork 项目
|
||||
|
||||
点击 GitHub 页面右上角的 "Fork" 按钮。
|
||||
|
||||
### 2. 创建开发分支
|
||||
|
||||
```bash
|
||||
git checkout -b your-develop-name
|
||||
```
|
||||
|
||||
### 3. 进行开发
|
||||
|
||||
- 遵循代码规范
|
||||
- 中文编写必要的代码注释
|
||||
- 添加必要的测试,目前主要针对 `pkg` 目录下的公共包
|
||||
- 若进行大范围的重构/修改,请提前与维护者沟通
|
||||
|
||||
### 4. 提交更改
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat(scope): 描述你的更改"
|
||||
```
|
||||
|
||||
### 5. 推送到你的 Fork
|
||||
|
||||
```bash
|
||||
git push origin your-develop-name
|
||||
```
|
||||
|
||||
### 6. 创建 Pull Request
|
||||
|
||||
1. 访问你 Fork 的仓库
|
||||
2. 点击 "New Pull Request"
|
||||
3. 选择你的分支
|
||||
4. 填写 PR 检查单并点击 "Create Pull Request"
|
||||
|
||||
### 7. 等待审查
|
||||
|
||||
当 Pull Request 开发完毕后,请为其添加 `🚀 Review Ready` 标签,维护者将及时进行评审并提供反馈。请及时响应评论并根据需要进行修改。
|
||||
|
||||
## 许可证
|
||||
|
||||
通过向本项目贡献代码,你同意你的贡献将在与项目相同的许可证下发布。
|
||||
|
||||
---
|
||||
|
||||
再次感谢你的贡献!🎉
|
||||
@@ -1 +0,0 @@
|
||||
custom: [ 'https://afdian.com/a/tnborg' ]
|
||||
@@ -1,115 +0,0 @@
|
||||
name: ☢️ 报告问题 (Bug Report)
|
||||
description: 创建一个报告以帮助我们改进 (Create a report to help us improve)
|
||||
type: Bug
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**请仅使用 简体中文 或 英文 进行填写**
|
||||
**Please only use Simplified Chinese or English to fill in**
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: 在提问之前 (Before Asking)
|
||||
description: |
|
||||
提问之前,先回答几个小问题。
|
||||
Before asking questions, answer a few quick questions.
|
||||
options:
|
||||
- label: 我已经搜索了现有的 Issues, Discussions 和 Google (I've searched the existing Issues, Discussions and Google)
|
||||
required: true
|
||||
- label: 我已经阅读了文档中除版本记录以外的所有内容 (I've read everything in the documentation except the version notes)
|
||||
required: true
|
||||
- label: 这个问题可以被稳定复现 (The problem can be stably reproduced)
|
||||
required: false
|
||||
- label: 问题是在更新之后产生的 (The problem is generated after upgrading)
|
||||
required: false
|
||||
- type: input
|
||||
id: system
|
||||
attributes:
|
||||
label: 系统版本 (OS Version)
|
||||
description: |
|
||||
请提供服务器所使用的操作系统及其版本号。
|
||||
Please provide the operating system and its version number used by the server.
|
||||
placeholder: "Ubuntu 24.04"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 面板版本 (Panel Version)
|
||||
description: |
|
||||
请提供面板的版本号。
|
||||
Please provide the version number of the panel.
|
||||
placeholder: "2.4.0"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: describe
|
||||
attributes:
|
||||
label: 描述问题 (Describe The Problem)
|
||||
description: |
|
||||
简明概要地描述您遇到的问题。
|
||||
Briefly describe the problem you are having.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: 如何复现 (How To Reproduce)
|
||||
description: |
|
||||
重现该问题的详细步骤。
|
||||
Detailed steps to reproduce the problem.
|
||||
value: |
|
||||
1. 安装面板
|
||||
2. 设置 '...'
|
||||
3. 点击 '...'
|
||||
4. 出现问题
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 预期行为 (Expected Behavior)
|
||||
description: |
|
||||
简明概要地描述您期望发生的事情。
|
||||
Briefly describe what you expect to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 相关日志 (Related Logs)
|
||||
description: |
|
||||
请复制并粘贴任何相关的日志输出。
|
||||
Please copy and paste any relevant log output.
|
||||
可以把文件拖入这个区域以添加日志文件。
|
||||
Files can be dragged into this area to add log files.
|
||||
面板日志文件可在面板目录 `storage/logs` 中找到。
|
||||
Panel log files can be found in the panel directory `storage/logs`.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: 截图 (Screenshots)
|
||||
description: |
|
||||
如果有,添加屏幕截图可帮助更快定位您的问题。
|
||||
If so, adding screenshots can help locate your issue faster.
|
||||
可以复制图片后在此区域内粘贴以添加图片。
|
||||
Pictures can be copied and pasted in this area to add pictures.
|
||||
如有必要,使用马赛克遮盖敏感信息。
|
||||
Use a mosaic to obscure sensitive information if necessary.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: others
|
||||
attributes:
|
||||
label: 还有别的吗 (Anything Else)
|
||||
description: |
|
||||
运行环境?浏览器?软件版本?相关的配置?链接?参考资料?
|
||||
Environment? Browser? Software version? Related configuration? Link? References?
|
||||
任何能让我们对您所遇到的问题有更多了解的东西。
|
||||
Anything that can give us more insight into the problem you're having.
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ❓ 讨论、问答和非项目问题 (Discussions, questions, and non-project issues)
|
||||
url: https://jq.qq.com/?_wv=1027&k=I1oJKSTH
|
||||
about: 其他不明之处,请移步我们的QQ群 12370907 (For other unclear things, please move to our qq group 12370907)
|
||||
@@ -1,54 +0,0 @@
|
||||
name: ✨ 功能请求 (Feature Request)
|
||||
description: 为这个项目提出一个想法 (Suggest an idea for this project)
|
||||
type: Feature
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**请仅使用 简体中文 或 英文 进行填写**
|
||||
**Please only use Simplified Chinese or English to fill in**
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: 在提问之前 (Before Asking)
|
||||
description: |
|
||||
提问之前,先回答几个小问题。
|
||||
Before asking questions, answer a few quick questions.
|
||||
options:
|
||||
- label: 我已经搜索了全部 Issues 和 Commits (I have searched all Issues and Commits)
|
||||
required: true
|
||||
- label: 它们当中没有我将要提交的新功能 (None of them have new feature that I'm going to submit)
|
||||
required: true
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: 描述功能 (Describe Feature)
|
||||
description: |
|
||||
简明概要地描述您的新功能,以及它将解决什么问题。
|
||||
Briefly describe your new feature and what problem it will solve.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: workflow
|
||||
attributes:
|
||||
label: 工作流程 (WorkFlow)
|
||||
description: |
|
||||
请向我们提供有关该功能的实现流程。
|
||||
Please provide us with the implementation flow for this feature.
|
||||
value: |
|
||||
1. 新建文件 ....
|
||||
2. 添加函数 ....
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: others
|
||||
attributes:
|
||||
label: 还有别的吗 (Anything Else)
|
||||
description: |
|
||||
运行环境?浏览器?软件版本?相关的配置?链接?参考资料?
|
||||
Environment? Browser? Software version? Related configuration? Link? References?
|
||||
任何能让我们对该功能的实现有更多帮助的东西。
|
||||
Anything that can help us more with the implementation of this feature.
|
||||
validations:
|
||||
required: false
|
||||
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 819 KiB |
|
Before Width: | Height: | Size: 64 KiB |
@@ -1,206 +0,0 @@
|
||||
## 项目概述
|
||||
|
||||
AcePanel 是基于 Go 语言开发的新一代 Linux 服务器运维管理面板。项目采用前后端分离架构:
|
||||
- 后端:Go 1.25 + go-chi 路由 + GORM + Wire 依赖注入
|
||||
- 前端:Vue 3 + Vite + Naive UI + pnpm
|
||||
|
||||
## 语言和编码规范
|
||||
|
||||
**所有代码注释、文档和回复必须使用简体中文。**
|
||||
|
||||
## 构建和测试
|
||||
|
||||
### 后端构建
|
||||
|
||||
构建主程序:
|
||||
```bash
|
||||
go build -o ace ./cmd/ace
|
||||
```
|
||||
|
||||
构建 CLI 工具:
|
||||
```bash
|
||||
go build -o cli ./cmd/cli
|
||||
```
|
||||
|
||||
### 前端开发
|
||||
|
||||
进入前端目录:
|
||||
```bash
|
||||
cd web
|
||||
```
|
||||
|
||||
安装依赖:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
开发模式(带热重载):
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
构建生产版本:
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 代码架构
|
||||
|
||||
项目采用类 DDD 分层架构,依赖关系为:route -> service -> biz <- data
|
||||
|
||||
### 核心目录结构
|
||||
|
||||
- **`cmd/`**: 程序入口
|
||||
- `ace/`: 面板主程序
|
||||
- `cli/`: 命令行工具
|
||||
|
||||
- **`internal/app/`**: 应用入口和配置
|
||||
|
||||
- **`internal/route/`**: HTTP 路由定义
|
||||
- 定义路由规则
|
||||
- 注入所需的 service 依赖
|
||||
|
||||
- **`internal/service/`**: 服务层(类似 DDD 的 application 层)
|
||||
- 处理 HTTP 请求/响应
|
||||
- DTO 到 DO 的转换
|
||||
- 协调多个 biz 接口完成业务流程
|
||||
- **不应处理复杂业务逻辑**
|
||||
|
||||
- **`internal/biz/`**: 业务逻辑层(类似 DDD 的 domain 层)
|
||||
- 定义业务接口(Repository 模式)
|
||||
- 定义领域模型和数据结构
|
||||
- 使用依赖倒置原则:biz 定义接口,data 实现接口
|
||||
|
||||
- **`internal/data/`**: 数据访问层(类似 DDD 的 repository 层)
|
||||
- 实现 biz 中定义的业务接口
|
||||
- 封装数据库、缓存等操作
|
||||
- 处理数据持久化逻辑
|
||||
|
||||
- **`internal/http/`**: HTTP 相关
|
||||
- `middleware/`: 自定义中间件
|
||||
- `request/`: 请求结构体定义
|
||||
- `rule/`: 自定义验证规则
|
||||
|
||||
- **`internal/apps/`**: 面板子应用实现
|
||||
|
||||
- **`internal/bootstrap/`**: 各模块启动引导
|
||||
|
||||
- **`internal/migration/`**: 数据库迁移
|
||||
|
||||
- **`internal/job/`**: 后台任务
|
||||
|
||||
- **`internal/queuejob/`**: 任务队列
|
||||
|
||||
- **`pkg/`**: 工具函数和通用包
|
||||
- 包含各种独立的工具模块
|
||||
- 可被项目任何部分引用
|
||||
|
||||
- **`web/`**: Vue 3 前端项目
|
||||
|
||||
## 开发新功能的标准流程
|
||||
|
||||
1. **在 `internal/route/` 中添加路由**
|
||||
- 参考已有路由文件(如 `http.go`)
|
||||
- 注入需要的 service 依赖
|
||||
- 定义路由规则和 handler 映射
|
||||
|
||||
2. **在 `internal/service/` 中实现服务方法**
|
||||
- **先阅读已有的类似服务**以了解代码风格
|
||||
- 处理请求验证和响应格式化
|
||||
- 使用 `Success()` 返回成功响应
|
||||
- 使用 `Error()` 返回错误响应
|
||||
- 使用 `ErrorSystem()` 返回系统严重错误
|
||||
- 调用 biz 层接口完成业务逻辑
|
||||
|
||||
3. **在 `internal/biz/` 中定义业务接口**
|
||||
- **先阅读已有的类似接口定义**
|
||||
- 定义 Repository 接口(如 `WebsiteRepo`)
|
||||
- 定义领域模型结构体(如 `Website`)
|
||||
- 保持接口简洁明确
|
||||
|
||||
4. **在 `internal/data/` 中实现 biz 接口**
|
||||
- **先阅读已有的类似实现**
|
||||
- 创建 repo 结构体(如 `websiteRepo`)
|
||||
- 实现构造函数(如 `NewWebsiteRepo`)
|
||||
- 实现所有接口方法
|
||||
- 处理数据库操作和缓存逻辑
|
||||
|
||||
5. **使用 Wire 进行依赖注入**
|
||||
- 在对应的 wire.go 文件中添加 provider
|
||||
- 运行 `go generate` 生成依赖注入代码
|
||||
|
||||
## 技术栈特定注意事项
|
||||
|
||||
### Go 语言规范
|
||||
|
||||
- 使用 Go 1.25 稳定版本
|
||||
- 遵循 Go 标准库和习惯用法
|
||||
- 日志使用标准库的 `slog` 包
|
||||
- 使用 `github.com/samber/lo` 进行函数式编程辅助
|
||||
|
||||
### 当前框架
|
||||
|
||||
- 路由:`github.com/go-chi/chi/v5`
|
||||
- ORM:`gorm.io/gorm`
|
||||
- 依赖注入:`github.com/google/wire`
|
||||
- 验证:`github.com/gookit/validate`
|
||||
|
||||
### 助手函数(service 层)
|
||||
|
||||
在 service 层使用以下助手函数:
|
||||
- `Success(w, data)`: 返回成功响应
|
||||
- `Error(w, statusCode, format, args...)`: 返回错误响应
|
||||
- `ErrorSystem(w, format, args...)`: 返回系统严重错误(500)
|
||||
- `Bind[T](r)`: 绑定请求参数到泛型类型 T
|
||||
- `Paginate[T](...)`: 构建分页响应
|
||||
|
||||
### 数据库
|
||||
|
||||
- 使用 SQLite(`github.com/ncruces/go-sqlite3`)
|
||||
- 使用 GORM 进行数据库迁移和操作
|
||||
|
||||
### 安全性
|
||||
|
||||
- 不需要实现命令注入过滤,文件名过滤等,因为这是服务器面板,所有登录的用户都被视为管理员
|
||||
|
||||
## 代码风格
|
||||
|
||||
- 所有代码注释必须使用简体中文
|
||||
- 遵循 Go 官方代码风格
|
||||
- 使用 `gofmt` 格式化代码
|
||||
- 复杂逻辑添加注释说明
|
||||
- 导出的函数和类型必须有注释
|
||||
|
||||
## Wire 依赖注入
|
||||
|
||||
项目使用 Wire 进行依赖注入。当添加新的依赖时:
|
||||
|
||||
1. 在 `cmd/ace/wire.go` 或 `cmd/cli/wire.go` 中添加 provider
|
||||
2. 运行生成命令:
|
||||
```bash
|
||||
go generate ./...
|
||||
```
|
||||
|
||||
## 前端开发注意事项
|
||||
|
||||
- 使用 Vue 3 Composition API
|
||||
- UI 框架:Naive UI
|
||||
- 状态管理:Pinia
|
||||
- HTTP 请求:Alova.js,使用 useRequest 等助手函数进行数据获取,无需处理 onError 错误。
|
||||
- 图标:@iconify/vue
|
||||
- 终端:xterm.js
|
||||
- 遵循项目已有的组件结构和编码风格
|
||||
|
||||
## 配置文件
|
||||
|
||||
开发时需要准备配置文件:
|
||||
```bash
|
||||
cp config.example.yml config.yml
|
||||
```
|
||||
|
||||
前端开发配置:
|
||||
```bash
|
||||
cd web
|
||||
cp .env.production .env
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts
|
||||
```
|
||||
@@ -1,93 +0,0 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Build frontend
|
||||
working-directory: web
|
||||
run: |
|
||||
cp .env.production .env
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts
|
||||
pnpm run gettext:compile
|
||||
pnpm build
|
||||
- name: Upload frontend
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: frontend
|
||||
path: web/dist/
|
||||
backend:
|
||||
needs: frontend
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goarch: [ amd64, arm64 ]
|
||||
fail-fast: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install dependencies
|
||||
run: go mod tidy
|
||||
- name: Download frontend
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: frontend
|
||||
path: pkg/embed/frontend
|
||||
- name: Set build info
|
||||
run: |
|
||||
echo "VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo '0.0.0')" >> $GITHUB_ENV
|
||||
echo "BUILD_TIME=$(date -u '+%F %T UTC')" >> $GITHUB_ENV
|
||||
echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
echo "GO_VERSION=$(go version | cut -d' ' -f3)" >> $GITHUB_ENV
|
||||
echo "BUILD_ID=${{ github.run_id }}" >> $GITHUB_ENV
|
||||
echo "BUILD_USER=$(whoami)" >> $GITHUB_ENV
|
||||
echo "BUILD_HOST=$(hostname)" >> $GITHUB_ENV
|
||||
- name: Build ${{ matrix.goarch }}
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
GOOS: linux
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
run: |
|
||||
LDFLAGS="-s -w --extldflags '-static'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.Version=${VERSION}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildTime=${BUILD_TIME}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.CommitHash=${COMMIT_HASH}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.GoVersion=${GO_VERSION}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildID=${BUILD_ID}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildUser=${BUILD_USER}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildHost=${BUILD_HOST}'"
|
||||
go build -trimpath -buildvcs=false -ldflags "${LDFLAGS}" -o ace-${{ matrix.goarch }} ./cmd/ace
|
||||
go build -trimpath -buildvcs=false -ldflags "${LDFLAGS}" -o cli-${{ matrix.goarch }} ./cmd/cli
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: backend-${{ matrix.goarch }}
|
||||
path: |
|
||||
ace-${{ matrix.goarch }}
|
||||
cli-${{ matrix.goarch }}
|
||||
@@ -1,49 +0,0 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Build frontend
|
||||
working-directory: web
|
||||
run: |
|
||||
cp .env.production .env
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts
|
||||
pnpm run gettext:compile
|
||||
pnpm build
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
echo "GOVERSION=$(go version | cut -d' ' -f3)" >> $GITHUB_ENV
|
||||
echo "HOSTNAME=$(hostname)" >> $GITHUB_ENV
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,23 +0,0 @@
|
||||
name: Issue Auto Lock
|
||||
on:
|
||||
schedule:
|
||||
- cron: "*/5 * * * *"
|
||||
workflow_dispatch:
|
||||
issues:
|
||||
types: [ opened ]
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
jobs:
|
||||
issue-lock:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Set GH_REPO
|
||||
run: echo "GH_REPO=${{ github.repository }}" >> $GITHUB_ENV
|
||||
- name: Auto Lock Issue
|
||||
uses: devhaozi/issue-auto-lock@v1
|
||||
with:
|
||||
gh_repo: ${{ github.repository }}
|
||||
gh_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue_labels: "⭐ No Star"
|
||||
@@ -1,59 +0,0 @@
|
||||
name: L10n
|
||||
on:
|
||||
workflow_dispatch:
|
||||
concurrency:
|
||||
group: l10n
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
jobs:
|
||||
l10n:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install gettext
|
||||
run: |
|
||||
sudo apt-get install -y gettext
|
||||
- name: Install xgotext
|
||||
run: |
|
||||
go install github.com/leonelquinteros/gotext/cli/xgotext@latest
|
||||
- name: Generate pot files
|
||||
run: |
|
||||
~/go/bin/xgotext -default backend -pkg-tree ./cmd/ace -out ./pkg/embed/locales
|
||||
cd web && pnpm run gettext:extract
|
||||
- uses: stefanzweifel/git-auto-commit-action@v7
|
||||
name: Commit changes
|
||||
with:
|
||||
commit_message: "chore(l10n): update pot files"
|
||||
- name: Sync with Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
config: crowdin.yml
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
export_only_approved: true
|
||||
create_pull_request: true
|
||||
pull_request_title: 'l10n: sync translations with Crowdin'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
@@ -1,59 +0,0 @@
|
||||
name: Lint
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
golangci:
|
||||
name: golanci-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
skip-cache: true
|
||||
version: latest
|
||||
args: --timeout=30m ./...
|
||||
govulncheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install Govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
- name: Run Govulncheck
|
||||
run: govulncheck ./...
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Run pnpm lint
|
||||
working-directory: web
|
||||
run: pnpm lint
|
||||
@@ -1,29 +0,0 @@
|
||||
name: Mockery
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
mockery:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install Mockery
|
||||
run: |
|
||||
go install github.com/vektra/mockery/v2@latest
|
||||
- name: Generate Mocks
|
||||
run: |
|
||||
~/go/bin/mockery
|
||||
git pull
|
||||
- uses: stefanzweifel/git-auto-commit-action@v7
|
||||
name: Commit changes
|
||||
with:
|
||||
commit_message: "chore: update mocks"
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
unit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y curl jq
|
||||
# https://github.com/golang/go/issues/75031
|
||||
- name: Set toolchain version
|
||||
run: go env -w GOTOOLCHAIN=go1.25.1+auto
|
||||
- name: Set up environment
|
||||
run: |
|
||||
cp config.example.yml config.yml
|
||||
- name: Run tests
|
||||
run: sudo go test -v -coverprofile="coverage.out" ./...
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
disable_search: true
|
||||
files: coverage.out
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
@@ -1,70 +1,3 @@
|
||||
# `go test -c` 生成的二进制文件
|
||||
*.test
|
||||
|
||||
# go coverage 工具
|
||||
*.out
|
||||
*.prof
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
# 编译文件
|
||||
ace
|
||||
cli
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
|
||||
# 压缩包
|
||||
# Git 自带压缩,如果这些压缩包里有代码,建议解压后 commit
|
||||
*.7z
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
|
||||
# 日志文件和数据库及配置
|
||||
*.log
|
||||
*.sqlite
|
||||
*.db
|
||||
config.yml
|
||||
|
||||
# 临时文件
|
||||
tmp/
|
||||
.tmp/
|
||||
|
||||
# 系统生成文件
|
||||
.idea
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
.TemporaryItems
|
||||
.fseventsd
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# IDE 和编辑器
|
||||
.idea/
|
||||
/go_build_*
|
||||
out/
|
||||
.vscode/
|
||||
.vscode/settings.json
|
||||
*.sublime*
|
||||
__debug_bin
|
||||
.project
|
||||
|
||||
# 傻逼 node_modules
|
||||
node_modules/
|
||||
vendor/
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
version: 2
|
||||
project_name: panel
|
||||
|
||||
builds:
|
||||
- id: ace
|
||||
main: ./cmd/ace
|
||||
binary: ace
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
flags:
|
||||
- -trimpath
|
||||
- -buildvcs=false
|
||||
ldflags:
|
||||
- -s -w --extldflags "-static"
|
||||
- -X 'github.com/acepanel/panel/internal/app.Version={{ .Version }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.CommitHash={{ .ShortCommit }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.GoVersion={{ .Env.GOVERSION }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildUser={{ .Env.USER }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildHost={{ .Env.HOSTNAME }}'
|
||||
- id: cli
|
||||
main: ./cmd/cli
|
||||
binary: cli
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
flags:
|
||||
- -trimpath
|
||||
- -buildvcs=false
|
||||
ldflags:
|
||||
- -s -w --extldflags "-static"
|
||||
- -X 'github.com/acepanel/panel/internal/app.Version={{ .Version }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.CommitHash={{ .ShortCommit }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.GoVersion={{ .Env.GOVERSION }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildUser={{ .Env.USER }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildHost={{ .Env.HOSTNAME }}'
|
||||
|
||||
archives:
|
||||
- id: panel
|
||||
ids:
|
||||
- ace
|
||||
- cli
|
||||
formats: ["zip"]
|
||||
wrap_in_directory: false
|
||||
strip_binary_directory: true
|
||||
files:
|
||||
- LICENSE
|
||||
- config.example.yml
|
||||
- storage/*
|
||||
@@ -1,11 +0,0 @@
|
||||
with-expecter: True
|
||||
disable-version-string: True
|
||||
dir: mocks/{{ replaceAll .InterfaceDirRelative "internal" "" }}
|
||||
mockname: "{{.InterfaceName}}"
|
||||
outpkg: "{{.PackageName}}"
|
||||
filename: "{{.InterfaceName}}.go"
|
||||
all: True
|
||||
packages:
|
||||
github.com/acepanel/panel/internal/biz:
|
||||
config:
|
||||
recursive: True
|
||||
@@ -0,0 +1,98 @@
|
||||
# 更新日志
|
||||
|
||||
所有重要的更改都将在此文件中记录。
|
||||
|
||||
## [20230130] - 常规更新
|
||||
|
||||
- 优化数据库备份流程
|
||||
|
||||
## [20230130] - 常规更新
|
||||
|
||||
- 代码格式化
|
||||
- 更新许可证
|
||||
- 修复更新备份逻辑错误
|
||||
- 主页新增新年祝福
|
||||
- 主页新增检查更新
|
||||
|
||||
## [20230113] - 常规更新
|
||||
|
||||
- 优化更新流程
|
||||
- 优化备份流程
|
||||
|
||||
## [20230108] - 常规更新
|
||||
|
||||
- 提升面板安全性
|
||||
- 修复网站删除报错的Bug
|
||||
- 规范网站名称
|
||||
|
||||
## [20221222] - 常规更新
|
||||
|
||||
- 优化计划任务日志格式
|
||||
- 网站管理支持分页
|
||||
- 文件管理的初步实现
|
||||
- 样式微调
|
||||
- 部分代码优化
|
||||
- 框架版本更新
|
||||
|
||||
## [20221212] - 常规更新
|
||||
|
||||
- 修复创建网站可能重复添加listen端口的Bug
|
||||
|
||||
## [20221210] - 安全更新
|
||||
|
||||
- 增强面板安全性
|
||||
- 其他微调及Bug修复
|
||||
|
||||
## [20221208] - 常规更新
|
||||
|
||||
- 新增网站免费SSL证书申请
|
||||
- 前端细节优化
|
||||
|
||||
## [20221205] - 常规更新
|
||||
|
||||
- 新增自动备份插件(需配合20221203+版本的面板使用)
|
||||
- 优化面板命令行显示
|
||||
- 前端细节优化
|
||||
|
||||
## [20221203] - 常规更新
|
||||
|
||||
- 修复禁用Ping不生效的问题
|
||||
- 修复网站设置PHP版本未赋值的问题
|
||||
- 新增网站目录备份功能
|
||||
- 新增面板多用户登录功能
|
||||
- 新增资源监控时间选择功能
|
||||
- 实装网站运行状态设置功能
|
||||
- 实装网站配置文件重置功能
|
||||
- 前端多处优化
|
||||
|
||||
## [20221202] - 常规更新
|
||||
|
||||
- 修复网站添加数据库不能为空的问题
|
||||
- 校验网站目录必须以/开头
|
||||
- 修复网站名可以编辑的问题
|
||||
- 新增面板API开关
|
||||
- 新增计划任务模块
|
||||
- 安装脚本优化,提高在不同地区下的安装成功率
|
||||
|
||||
## [20221201] - 首版发布
|
||||
|
||||
- 修复了二测时发现的一些问题
|
||||
- 首发于Hostloc,感谢各位mjj大佬的支持。
|
||||
|
||||
## [20221130] - 内部二测
|
||||
|
||||
- 内部二测
|
||||
- 修复了一测发现的问题
|
||||
- 新增了一些插件,基本功能已经完善
|
||||
|
||||
## [20221121] - 内部一测
|
||||
|
||||
- 内部一测
|
||||
|
||||
## 2022-10 - 2022-11
|
||||
|
||||
- 重构版本开始开发
|
||||
|
||||
## 2022-07 - 2022-08
|
||||
|
||||
- 项目启动、早期开发
|
||||
@@ -1,210 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目概述
|
||||
|
||||
AcePanel 是基于 Go 语言开发的新一代 Linux 服务器运维管理面板。项目采用前后端分离架构:
|
||||
- 后端:Go 1.25 + go-chi 路由 + GORM + Wire 依赖注入
|
||||
- 前端:Vue 3 + Vite + Naive UI + pnpm
|
||||
|
||||
## 语言和编码规范
|
||||
|
||||
**所有代码注释、文档和回复必须使用简体中文。**
|
||||
|
||||
## 构建和测试
|
||||
|
||||
### 后端构建
|
||||
|
||||
构建主程序:
|
||||
```bash
|
||||
go build -o ace ./cmd/ace
|
||||
```
|
||||
|
||||
构建 CLI 工具:
|
||||
```bash
|
||||
go build -o cli ./cmd/cli
|
||||
```
|
||||
|
||||
### 前端开发
|
||||
|
||||
进入前端目录:
|
||||
```bash
|
||||
cd web
|
||||
```
|
||||
|
||||
安装依赖:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
开发模式(带热重载):
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
构建生产版本:
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 代码架构
|
||||
|
||||
项目采用类 DDD 分层架构,依赖关系为:route -> service -> biz <- data
|
||||
|
||||
### 核心目录结构
|
||||
|
||||
- **`cmd/`**: 程序入口
|
||||
- `ace/`: 面板主程序
|
||||
- `cli/`: 命令行工具
|
||||
|
||||
- **`internal/app/`**: 应用入口和配置
|
||||
|
||||
- **`internal/route/`**: HTTP 路由定义
|
||||
- 定义路由规则
|
||||
- 注入所需的 service 依赖
|
||||
|
||||
- **`internal/service/`**: 服务层(类似 DDD 的 application 层)
|
||||
- 处理 HTTP 请求/响应
|
||||
- DTO 到 DO 的转换
|
||||
- 协调多个 biz 接口完成业务流程
|
||||
- **不应处理复杂业务逻辑**
|
||||
|
||||
- **`internal/biz/`**: 业务逻辑层(类似 DDD 的 domain 层)
|
||||
- 定义业务接口(Repository 模式)
|
||||
- 定义领域模型和数据结构
|
||||
- 使用依赖倒置原则:biz 定义接口,data 实现接口
|
||||
|
||||
- **`internal/data/`**: 数据访问层(类似 DDD 的 repository 层)
|
||||
- 实现 biz 中定义的业务接口
|
||||
- 封装数据库、缓存等操作
|
||||
- 处理数据持久化逻辑
|
||||
|
||||
- **`internal/http/`**: HTTP 相关
|
||||
- `middleware/`: 自定义中间件
|
||||
- `request/`: 请求结构体定义
|
||||
- `rule/`: 自定义验证规则
|
||||
|
||||
- **`internal/apps/`**: 面板子应用实现
|
||||
|
||||
- **`internal/bootstrap/`**: 各模块启动引导
|
||||
|
||||
- **`internal/migration/`**: 数据库迁移
|
||||
|
||||
- **`internal/job/`**: 后台任务
|
||||
|
||||
- **`internal/queuejob/`**: 任务队列
|
||||
|
||||
- **`pkg/`**: 工具函数和通用包
|
||||
- 包含各种独立的工具模块
|
||||
- 可被项目任何部分引用
|
||||
|
||||
- **`web/`**: Vue 3 前端项目
|
||||
|
||||
## 开发新功能的标准流程
|
||||
|
||||
1. **在 `internal/route/` 中添加路由**
|
||||
- 参考已有路由文件(如 `http.go`)
|
||||
- 注入需要的 service 依赖
|
||||
- 定义路由规则和 handler 映射
|
||||
|
||||
2. **在 `internal/service/` 中实现服务方法**
|
||||
- **先阅读已有的类似服务**以了解代码风格
|
||||
- 处理请求验证和响应格式化
|
||||
- 使用 `Success()` 返回成功响应
|
||||
- 使用 `Error()` 返回错误响应
|
||||
- 使用 `ErrorSystem()` 返回系统严重错误
|
||||
- 调用 biz 层接口完成业务逻辑
|
||||
|
||||
3. **在 `internal/biz/` 中定义业务接口**
|
||||
- **先阅读已有的类似接口定义**
|
||||
- 定义 Repository 接口(如 `WebsiteRepo`)
|
||||
- 定义领域模型结构体(如 `Website`)
|
||||
- 保持接口简洁明确
|
||||
|
||||
4. **在 `internal/data/` 中实现 biz 接口**
|
||||
- **先阅读已有的类似实现**
|
||||
- 创建 repo 结构体(如 `websiteRepo`)
|
||||
- 实现构造函数(如 `NewWebsiteRepo`)
|
||||
- 实现所有接口方法
|
||||
- 处理数据库操作和缓存逻辑
|
||||
|
||||
5. **使用 Wire 进行依赖注入**
|
||||
- 在对应的 wire.go 文件中添加 provider
|
||||
- 运行 `go generate` 生成依赖注入代码
|
||||
|
||||
## 技术栈特定注意事项
|
||||
|
||||
### Go 语言规范
|
||||
|
||||
- 使用 Go 1.25 稳定版本
|
||||
- 遵循 Go 标准库和习惯用法
|
||||
- 日志使用标准库的 `slog` 包
|
||||
- 使用 `github.com/samber/lo` 进行函数式编程辅助
|
||||
|
||||
### 当前框架
|
||||
|
||||
- 路由:`github.com/go-chi/chi/v5`
|
||||
- ORM:`gorm.io/gorm`
|
||||
- 依赖注入:`github.com/google/wire`
|
||||
- 验证:`github.com/gookit/validate`
|
||||
|
||||
### 助手函数(service 层)
|
||||
|
||||
在 service 层使用以下助手函数:
|
||||
- `Success(w, data)`: 返回成功响应
|
||||
- `Error(w, statusCode, format, args...)`: 返回错误响应
|
||||
- `ErrorSystem(w, format, args...)`: 返回系统严重错误(500)
|
||||
- `Bind[T](r)`: 绑定请求参数到泛型类型 T
|
||||
- `Paginate[T](...)`: 构建分页响应
|
||||
|
||||
### 数据库
|
||||
|
||||
- 使用 SQLite(`github.com/ncruces/go-sqlite3`)
|
||||
- 使用 GORM 进行数据库迁移和操作
|
||||
|
||||
### 安全性
|
||||
|
||||
- 不需要实现命令注入过滤,文件名过滤等,因为这是服务器面板,所有登录的用户都被视为管理员
|
||||
|
||||
## 代码风格
|
||||
|
||||
- 所有代码注释必须使用简体中文
|
||||
- 遵循 Go 官方代码风格
|
||||
- 使用 `gofmt` 格式化代码
|
||||
- 复杂逻辑添加注释说明
|
||||
- 导出的函数和类型必须有注释
|
||||
|
||||
## Wire 依赖注入
|
||||
|
||||
项目使用 Wire 进行依赖注入。当添加新的依赖时:
|
||||
|
||||
1. 在 `cmd/ace/wire.go` 或 `cmd/cli/wire.go` 中添加 provider
|
||||
2. 运行生成命令:
|
||||
```bash
|
||||
go generate ./...
|
||||
```
|
||||
|
||||
## 前端开发注意事项
|
||||
|
||||
- 使用 Vue 3 Composition API
|
||||
- UI 框架:Naive UI
|
||||
- 状态管理:Pinia
|
||||
- HTTP 请求:Alova.js,使用 useRequest 等助手函数进行数据获取,无需处理 onError 错误。
|
||||
- 图标:@iconify/vue
|
||||
- 终端:xterm.js
|
||||
- 遵循项目已有的组件结构和编码风格
|
||||
|
||||
## 配置文件
|
||||
|
||||
开发时需要准备配置文件:
|
||||
```bash
|
||||
cp config.example.yml config.yml
|
||||
```
|
||||
|
||||
前端开发配置:
|
||||
```bash
|
||||
cd web
|
||||
cp .env.production .env
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts
|
||||
```
|
||||
@@ -1,17 +0,0 @@
|
||||
## 行为准则
|
||||
|
||||
AcePanel 遵守业界通用的行为准则。任何违反行为准则的行为都可以报告给我们:
|
||||
|
||||
- 参与者将容忍反对意见。
|
||||
- 参与者必须确保他们的语言和行为没有人身攻击和贬低个人言论。
|
||||
- 在解释他人的言行时,参与者应始终保持良好的意图。
|
||||
- 不能容忍可合理视为骚扰的行为。
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
The AcePanel complies with the industry's common code of conduct. Any breach of the Code of Conduct can be reported to us:
|
||||
|
||||
- Participants will be tolerant of opposing views.
|
||||
- Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
||||
- When interpreting the words and actions of others, participants should always assume good intentions.
|
||||
- Behavior that can be reasonably considered harassment will not be tolerated.
|
||||
@@ -1,38 +1,202 @@
|
||||
Copyright (c) 2022-2025, AcePanel contributors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
1. Definitions.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
免责声明
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
本软件是按照现有技术和条件所能达到的现状提供的,用户出于自愿而使用本软件,树新峰(耗子科技)不作任何形式的保证,包括但不限于:
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
1. 树新峰不对于因使用或无法使用本软件而造成的任何直接、间接、偶然、附带、特殊及后续的「损害」承担责任,即使已被告知此类「损害」的可能性;
|
||||
2. 树新峰无组织本软件的交流社区的义务与责任,不承担因技术交流导致某一方故障而产生的「损害」;
|
||||
3. 树新峰不对本软件的无故障、适用性、可用性、准确性、质量满意度等做任何形式的保证;
|
||||
4. 树新峰不保证本软件可以满足您的要求,也不保证其内容、服务或功能无任何错误或不会中断,不保证任何缺陷将得到更正;
|
||||
5. 树新峰对本软件提供的任何信息或建议均不构成任何担保;
|
||||
6. 用户因使用本软件违反国家法律法规的,树新峰不承担任何责任;
|
||||
7. 本条款所指的「损害」包括但不限于经济损失、利润损失、收入损失、业务中断、数据丢失等。
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,91 +1,35 @@
|
||||
<p align="right">
|
||||
[简体中文] | [<a href="README_EN.md">English</a>]
|
||||
</p>
|
||||
耗子Linux面板
|
||||
===============
|
||||
|
||||
<p align="center"><a href="https://acepanel.net"><img src=".github/assets/logo-full.png" alt="AcePanel" width="300" /></a></p>
|
||||
#### 这是耗子Linux面板的开源仓库,基于Apache License 2.0协议进行开源,目前处于积极开发状态。
|
||||
|
||||
<p align="center">新一代全能服务器运维管理面板<br>简单轻量,高效运维</p>
|
||||
#### 我们正在进行基于 Golang 的 V2 版本的开发,加入我们的 交流QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | QQ频道:[耗子](https://pd.qq.com/s/fyol46wfy) 获取最新动态。
|
||||
|
||||
<div align="center">
|
||||
## 运行环境
|
||||
|
||||
官网:[acepanel.net](https://acepanel.net) | QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
| 系统 | 版本 |
|
||||
|---------------|-----|
|
||||
| CentOS Stream | 9 |
|
||||
| CentOS Stream | 8 |
|
||||
| RockyLinux | 9 |
|
||||
| RockyLinux | 8 |
|
||||
| AlmaLinux | 9 |
|
||||
| AlmaLinux | 8 |
|
||||
| 其他RHEL发行版 | 8 |
|
||||
| 其他RHEL发行版 | 9 |
|
||||
|
||||
</div>
|
||||
广告: [`WeAvatar` —统一头像服务](https://weavatar.com)
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://go.dev/)
|
||||
[](https://github.com/acepanel/panel/releases)
|
||||
[](https://github.com/acepanel/panel/actions)
|
||||
[](https://goreportcard.com/report/github.com/acepanel/panel)
|
||||
[](https://github.com/acepanel/panel)
|
||||
[](https://opensource.org/license/bsd-3-clause)
|
||||
|
||||
</div>
|
||||
|
||||
## 优势
|
||||
|
||||
1. **极低占用:** Go 语言开发,安装包小,占用低,单文件运行,不会对系统性能造成影响
|
||||
2. **低破坏性:** 设计为尽可能减少对系统的额外修改,在同类产品中,我们对系统的修改最少
|
||||
3. **追随时代:** 整体设计走在时代前沿,对新系统兼容性好,在同类产品中处于领先地位
|
||||
4. **高效运维:** 功能完善,自定义能力强,既可快速部署小型网站,也可基于定制化需求部署复杂应用
|
||||
5. **离线运行:** 支持离线模式,甚至可以在部署完成后停止面板进程,不会对已有服务造成任何影响
|
||||
6. **安全稳定:** 面板采用业界多种技术保障本体安全性,已在我们的多个生产环境中长期稳定运行
|
||||
7. **全面开源:** 少有的全开源面板,您可以在遵守开源协议的前提下对面板自由修改、二次开发
|
||||
8. **永久免费:** 承诺面板本体未来不会引入任何收费/授权功能,永久免费使用
|
||||
|
||||
## 快速安装
|
||||
|
||||
支持 `amd64` | `arm64` 架构下的干净的主流系统,具体支持的系统请参考[安装文档](https://acepanel.github.io/quickstart/install)。
|
||||
服务器赞助:
|
||||
[](http://www.ddunyun.com/aff/PNYAXMKI)
|
||||
## 安装说明
|
||||
|
||||
```shell
|
||||
bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
|
||||
wget -O install_panel.sh https://dl.panel.haozi.xyz/script/install_panel.sh && bash install_panel.sh
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> 耗子面板 2.x -> AcePanel 3.0 迁移工具将在近期发布,敬请期待!
|
||||
## 更新说明
|
||||
|
||||
## UI 截图
|
||||
|
||||

|
||||
|
||||
## 合作伙伴
|
||||
|
||||
如果 AcePanel 对您有帮助,欢迎[赞助我们](https://github.com/acepanel/panel/issues/90),同时感谢以下支持者/赞助商的支持:
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="60" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="60" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://cloud.panguidc.com/aff/DMRRFVJX">
|
||||
<img height="60" src=".github/assets/pangu.png" alt="盘古云">
|
||||
</a>
|
||||
<a href="https://waf.pro/">
|
||||
<img height="60" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="60" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://1ms.run/">
|
||||
<img height="60" src=".github/assets/1ms.svg" alt="毫秒镜像">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.com/a/tnborg">
|
||||
<img alt="sponsors" src="https://github.com/tnborg/sponsor/blob/main/sponsors.svg?raw=true"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Star 历史
|
||||
|
||||
<a href="https://star-history.com/#acepanel/panel&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=acepanel/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=acepanel/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=acepanel/panel&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
```shell
|
||||
panel update
|
||||
```
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
<p align="right">
|
||||
[<a href="README.md">简体中文</a>] | [English]
|
||||
</p>
|
||||
|
||||
<p align="center"><a href="https://acepanel.net"><img src=".github/assets/logo-full.png" alt="AcePanel" width="300" /></a></p>
|
||||
|
||||
<p align="center">New generation of all-in-one server operation and maintenance management panel<br>Simple and lightweight, efficient operation and maintenance</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
Website: [acepanel.net](https://acepanel.net) | QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://go.dev/)
|
||||
[](https://github.com/acepanel/panel/releases)
|
||||
[](https://github.com/acepanel/panel/actions)
|
||||
[](https://goreportcard.com/report/github.com/acepanel/panel)
|
||||
[](https://github.com/acepanel/panel)
|
||||
[](https://opensource.org/license/bsd-3-clause)
|
||||
|
||||
</div>
|
||||
|
||||
## Advantages
|
||||
|
||||
1. **Extremely low occupancy:** Developed in Go language, small installation package, low occupancy, single file operation, will not affect system performance
|
||||
2. **Low Destructiveness:** Designed to minimize additional modifications to the system, we make the fewest modifications to the system among similar products
|
||||
3. **Follow the Times:** The overall design is at the forefront of the times, with good compatibility with new systems, leading in the same type of products
|
||||
4. **Efficient Operation and Maintenance:** Complete functions, strong customization capabilities, can quickly deploy small websites, and deploy complex applications based on customized requirements
|
||||
5. **Offline Operation:** Support offline mode, and even stop the panel process after deployment, without affecting any existing services
|
||||
6. **Safe and Stable:** The panel adopts a variety of industry technologies to ensure the security of the body, and has been running stably in multiple survival environments for a long time
|
||||
7. **Fully Open Source:** Few fully open source panels, you can freely modify and develop the panel on the premise of complying with the open source agreement
|
||||
8. **Permanently free:** Commit to the panel body will not introduce any charging/authorization functions in the future, and will be permanently free to use
|
||||
|
||||
## Quick Install
|
||||
|
||||
Supported clean mainstream systems under `amd64` | `arm64` architecture, please refer to the [installation documentation](https://acepanel.github.io/en/quickstart/install) for specific supported systems.
|
||||
|
||||
```shell
|
||||
bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
|
||||
```
|
||||
|
||||
## UI Screenshots
|
||||
|
||||

|
||||
|
||||
## Partners
|
||||
|
||||
If the AcePanel is helpful to you, welcome to [sponsor us](https://github.com/acepanel/panel/issues/90), also thanks to the following supporters/sponsors:
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="60" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="60" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://cloud.panguidc.com/aff/DMRRFVJX">
|
||||
<img height="60" src=".github/assets/pangu.png" alt="盘古云">
|
||||
</a>
|
||||
<a href="https://waf.pro/">
|
||||
<img height="60" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="60" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://1ms.run/">
|
||||
<img height="60" src=".github/assets/1ms.svg" alt="毫秒镜像">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.com/a/tnborg">
|
||||
<img alt="sponsors" src="https://github.com/tnborg/sponsor/blob/main/sponsors.svg?raw=true"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://star-history.com/#acepanel/panel&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=acepanel/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=acepanel/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=acepanel/panel&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
@@ -1,30 +0,0 @@
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
|---------|--------------------|
|
||||
| v2.x | :white_check_mark: |
|
||||
| v1.x | :x: |
|
||||
|
||||
## Security Policy
|
||||
|
||||
If you find any security issues while using the panel, please do not submit an Issue. You can contact us directly through the following methods:
|
||||
|
||||
- (Recommend) [GitHub Security Advisories](https://github.com/acepanel/panel/security/advisories/new)
|
||||
- Email: [admin@haozi.net](mailto:admin@haozi.net)
|
||||
- Telegram: @devhaozi
|
||||
|
||||
Thank you in advance for your support and help!
|
||||
|
||||
To some security beginners: Any operation performed through an already logged-in panel's `session` / `access_token` (including but not limited to: obtaining root permissions, reading/writing sensitive system files, executing arbitrary shell commands, etc.) is not considered a security issue. Please do not waste each other's time by submitting such reports.
|
||||
|
||||
## 安全说明
|
||||
|
||||
如果您在面板中发现任何安全问题,请勿提交 Issue,可通过以下方式直接联系我们:
|
||||
|
||||
- (推荐)[GitHub 安全公告](https://github.com/acepanel/panel/security/advisories/new)
|
||||
- 邮箱:[admin@haozi.net](mailto:admin@haozi.net)
|
||||
- QQ:826896000
|
||||
|
||||
提前感谢您的支持与帮助!
|
||||
|
||||
致某些安全初学者:通过已登录面板的 `session` / `access_token` 执行的任何操作(包括且不限于:获取 root 权限、读取/写入系统敏感文件、执行任意 shell 命令等)均不被认为是安全问题,请不要刷此类报告浪费彼此的时间。
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Monitor as MonitorModel;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class Monitor extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monitor';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '耗子Linux面板 - 系统监控';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (Setting::query()->where('name', 'monitor')->value('value')) {
|
||||
$info = self::getNowMonitor();
|
||||
MonitorModel::query()->create(['info' => json_encode($info)]);
|
||||
// 删除过期的记录
|
||||
$days = Setting::query()->where('name', 'monitor_days')->value('value');
|
||||
MonitorModel::query()->where('created_at', '<', Carbon::now()->subDays($days))->delete();
|
||||
$this->info(time().' 监控完成');
|
||||
} else {
|
||||
$this->info('监控未开启');
|
||||
}
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统资源统计
|
||||
* @return array
|
||||
*/
|
||||
private function getNowMonitor(): array
|
||||
{
|
||||
// 第一次获取网络信息
|
||||
$netInfo1 = getNetInfo();
|
||||
// 卡它一秒钟
|
||||
sleep(1);
|
||||
// 第二次获取网络信息
|
||||
$netInfo2 = getNetInfo();
|
||||
|
||||
// CPU统计信息及负载
|
||||
$cpuInfoRaw = file_get_contents('/proc/cpuinfo');
|
||||
$physicalArr = array();
|
||||
$siblingsSum = 0;
|
||||
preg_match("/(\d+\.\d+), (\d+\.\d+), (\d+\.\d+)/", exec('uptime'), $uptime);
|
||||
$uptime1 = $uptime[1] ?? 0;
|
||||
|
||||
$processorArr = explode("\nprocessor", $cpuInfoRaw);
|
||||
foreach ($processorArr as $v) {
|
||||
preg_match("/physical id\s*:\s(.*)/", $v, $physical);
|
||||
preg_match("/siblings\s*:\s(.*)/", $v, $siblings);
|
||||
if (isset($physical[1])) {
|
||||
if (!in_array($physical[1], $physicalArr)) {
|
||||
if (isset($siblings[1])) {
|
||||
$siblingsSum += $siblings[1];
|
||||
}
|
||||
}
|
||||
$physicalArr[] = $physical[1];
|
||||
}
|
||||
}
|
||||
|
||||
// CPU使用率
|
||||
$cpuUse = 0.1;
|
||||
$cpuRaw = explode("\n", shell_exec('ps aux'));
|
||||
// 弹出第一项和最后一项
|
||||
array_pop($cpuRaw);
|
||||
array_shift($cpuRaw);
|
||||
// 获取当前php进程的pid
|
||||
$pid = getmypid();
|
||||
foreach ($cpuRaw as $v) {
|
||||
$v = preg_replace("/\s+/", " ", $v);
|
||||
$v = (explode(' ', $v));
|
||||
// 排除当前进程
|
||||
if ($v[1] == $pid) {
|
||||
continue;
|
||||
}
|
||||
$cpuUse += isset($v[2]) ? (float) $v[2] : 0;
|
||||
}
|
||||
$cpuUse = $siblingsSum > 0 ? ($cpuUse / $siblingsSum) : $cpuUse;
|
||||
$cpuUse = round($cpuUse, 2);
|
||||
$cpuUse = min($cpuUse, 100);
|
||||
|
||||
|
||||
// 内存使用率
|
||||
$memRaw = explode("\n", shell_exec('free -m'));
|
||||
foreach ($memRaw as $v) {
|
||||
if (str_contains($v, 'Mem')) {
|
||||
$memList = preg_replace("/\s+/", " ", $v);
|
||||
} elseif (str_contains($v, 'Swap')) {
|
||||
$swapList = preg_replace("/\s+/", " ", $v);
|
||||
}
|
||||
}
|
||||
$memArr = explode(' ', $memList);
|
||||
$swapArr = explode(' ', $swapList);
|
||||
// 内存大小MB
|
||||
$memTotal = $memArr[1];
|
||||
// Swap大小MB
|
||||
$swapTotal = $swapArr[1];
|
||||
// 使用中MB
|
||||
$memUse = (str_contains($memRaw[0], 'buff/cache')) ? $memArr[2] : ($memArr[2] - $memArr[5] - $memArr[6]);
|
||||
// Swap使用中MB
|
||||
$swapUse = $swapArr[2];
|
||||
// 使用中%
|
||||
$memUseP = round($memUse / $memTotal, 2) * 100;
|
||||
// Swap使用中%
|
||||
$swapUseP = round($swapUse / $swapTotal, 2) * 100;
|
||||
// 1分钟负载%
|
||||
$uptime1P = round(min($uptime1 * 10, 100), 2);
|
||||
|
||||
// 构建返回数组
|
||||
$res['cpu_use'] = $cpuUse;
|
||||
$res['uptime'] = $uptime1;
|
||||
$res['uptime_p'] = $uptime1P;
|
||||
$res['mem_total'] = $memTotal;
|
||||
$res['mem_use'] = $memUse;
|
||||
$res['mem_use_p'] = $memUseP;
|
||||
$res['swap_total'] = $swapTotal;
|
||||
$res['swap_use'] = $swapUse;
|
||||
$res['swap_use_p'] = $swapUseP;
|
||||
$res['tx_now'] = $netInfo2['tx'] - $netInfo1['tx'];
|
||||
$res['rx_now'] = $netInfo2['rx'] - $netInfo1['rx'];
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Plugin;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
use App\Models\Website;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Command\Command as CommandAlias;
|
||||
|
||||
class Panel extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'panel {action?} {a1?} {a2?} {a3?} {a4?} {a5?} {a6?} {a7?} {a8?} {a9?} {a10?}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '耗子Linux面板命令行工具';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
// 检测是否以root用户运行
|
||||
if (trim(shell_exec('whoami')) != 'root') {
|
||||
$this->error('耗子Linux面板:请以root用户运行');
|
||||
return 1;
|
||||
}
|
||||
$action = $this->argument('action');
|
||||
switch ($action) {
|
||||
case 'init':
|
||||
$this->init();
|
||||
break;
|
||||
case 'update':
|
||||
$this->update();
|
||||
break;
|
||||
case 'getInfo':
|
||||
$this->getInfo();
|
||||
break;
|
||||
case 'getPort':
|
||||
$this->getPort();
|
||||
break;
|
||||
case 'writePluginInstall':
|
||||
$this->writePluginInstall();
|
||||
break;
|
||||
case 'writePluginUnInstall':
|
||||
$this->writePluginUnInstall();
|
||||
break;
|
||||
case 'writeMysqlPassword':
|
||||
$this->writeMysqlPassword();
|
||||
break;
|
||||
case 'cleanRunningTask':
|
||||
$this->cleanRunningTask();
|
||||
break;
|
||||
case 'backup':
|
||||
$this->backup();
|
||||
break;
|
||||
case 'writeSite':
|
||||
$this->writeSite();
|
||||
break;
|
||||
case 'deleteSite':
|
||||
$this->deleteSite();
|
||||
break;
|
||||
case 'writeSetting':
|
||||
$this->writeSetting();
|
||||
break;
|
||||
case 'deleteSetting':
|
||||
$this->deleteSetting();
|
||||
break;
|
||||
default:
|
||||
$this->info('耗子Linux面板命令行工具');
|
||||
$this->info('请使用以下命令:');
|
||||
$this->info('panel update 更新/修复面板到最新版本');
|
||||
$this->info('panel getInfo 重新初始化面板账号信息');
|
||||
$this->info('panel getPort 获取面板访问端口');
|
||||
$this->info('panel cleanRunningTask 强制清理面板正在运行的任务');
|
||||
$this->info('panel backup {website/mysql/postgresql} {name} {path} 备份网站/MySQL数据库/PostgreSQL数据库到指定目录');
|
||||
$this->warn('以下命令请在开发者指导下使用:');
|
||||
$this->info('panel init 初始化面板');
|
||||
$this->info('panel writePluginInstall {slug} 写入插件安装状态');
|
||||
$this->info('panel writePluginUnInstall {slug} 移除插件安装状态');
|
||||
$this->info('panel writeMysqlPassword {password} 写入MySQL root密码');
|
||||
$this->info('panel writeSite {name} {status} {path} {php} {ssl} 写入网站数据到面板');
|
||||
$this->info('panel deleteSite {name} 删除面板网站数据');
|
||||
$this->info('panel writeSetting {name} {value} 写入/更新面板设置数据');
|
||||
$this->info('panel deleteSetting {name} 删除面板设置数据');
|
||||
break;
|
||||
}
|
||||
return CommandAlias::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @return void
|
||||
*/
|
||||
private function init(): void
|
||||
{
|
||||
Setting::query()->updateOrCreate(['name' => 'name'], ['value' => '耗子Linux面板']);
|
||||
Setting::query()->updateOrCreate(['name' => 'monitor'], ['value' => '1']);
|
||||
Setting::query()->updateOrCreate(['name' => 'monitor_days'], ['value' => '30']);
|
||||
Setting::query()->updateOrCreate(['name' => 'mysql_root_password'], ['value' => '']);
|
||||
Setting::query()->updateOrCreate(['name' => 'postgresql_root_password'], ['value' => '']);
|
||||
User::query()->create([
|
||||
'id' => 1,
|
||||
'username' => 'admin',
|
||||
'email' => 'panel@haozi.net',
|
||||
'password' => Hash::make(Str::random()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新面板
|
||||
* @return void
|
||||
*/
|
||||
private function update(): void
|
||||
{
|
||||
/**
|
||||
* 检查当前是否有任务正在运行
|
||||
*/
|
||||
if (Task::query()->where('status', '!=', 'finished')->count()) {
|
||||
$this->error('当前有任务正在运行,请稍后再试');
|
||||
$this->info('如需强制更新,请先执行:panel cleanRunningTask');
|
||||
return;
|
||||
}
|
||||
$this->info('正在下载面板...');
|
||||
shell_exec('wget -O /tmp/panel.zip https://api.panel.haozi.xyz/api/version/latest');
|
||||
// 检查下载是否成功
|
||||
if (!file_exists('/tmp/panel.zip') || filesize('/tmp/panel.zip') < 4096) {
|
||||
$this->error('检测到面板新版本下载失败,已终止更新,请加QQ群:12370907 反馈处理');
|
||||
return;
|
||||
}
|
||||
$this->info('正在备份数据库...');
|
||||
shell_exec('rm -rf /tmp/database.sqlite');
|
||||
if (!copy('/www/panel/database/database.sqlite', '/tmp/database.sqlite')) {
|
||||
$this->error('备份数据库失败,已终止更新,请加QQ群:12370907 反馈处理');
|
||||
return;
|
||||
}
|
||||
$this->info('正在备份插件...');
|
||||
shell_exec('rm -rf /tmp/plugins');
|
||||
shell_exec('\cp -rf /www/panel/plugins /tmp/plugins');
|
||||
if (!is_dir('/tmp/plugins/Openresty')) {
|
||||
$this->error('检测到面板插件备份失败,已终止更新,请加QQ群:12370907 反馈处理');
|
||||
return;
|
||||
}
|
||||
$this->info('正在删除旧版本...');
|
||||
shell_exec('rm -rf /www/panel');
|
||||
shell_exec('mkdir /www/panel');
|
||||
$this->info('正在解压新版本...');
|
||||
shell_exec('unzip -o /tmp/panel.zip -d /www/panel');
|
||||
if (!file_exists('/www/panel/artisan')) {
|
||||
$this->error('检测到面板新版本解压失败,请加QQ群:12370907 反馈处理');
|
||||
return;
|
||||
}
|
||||
$this->info('正在恢复数据库...');
|
||||
shell_exec('rm -rf /www/panel/database/database.sqlite');
|
||||
if (!copy('/tmp/database.sqlite', '/www/panel/database/database.sqlite')) {
|
||||
$this->error('检测到面板数据库恢复失败,请加QQ群:12370907 反馈处理');
|
||||
return;
|
||||
}
|
||||
$this->info('正在恢复插件...');
|
||||
shell_exec('rm -rf /www/panel/plugins');
|
||||
shell_exec('\cp -rf /tmp/plugins /www/panel/plugins');
|
||||
if (!is_dir('/www/panel/plugins/Openresty')) {
|
||||
$this->error('检测到面板插件恢复失败,请加QQ群:12370907 反馈处理');
|
||||
return;
|
||||
}
|
||||
$this->info('正在更新面板数据库...');
|
||||
shell_exec('cd /www/panel && php-panel artisan migrate');
|
||||
$this->info('正在设置面板权限...');
|
||||
shell_exec('chown -R root:root /www/panel');
|
||||
shell_exec('chmod -R 600 /www/panel');
|
||||
shell_exec('chmod 755 /www/panel');
|
||||
shell_exec('chmod -R 755 /www/panel/public');
|
||||
shell_exec('chown -R root:root /www/server/cron');
|
||||
shell_exec('chmod -R 700 /www/server/cron');
|
||||
shell_exec('chmod -R 600 /www/server/cron/logs');
|
||||
shell_exec('chown -R root:root /www/server/vhost');
|
||||
shell_exec('chmod -R 644 /www/server/vhost');
|
||||
$this->info('正在重载面板服务...');
|
||||
$reloadCheck = shell_exec('systemctl reload panel.service 2>&1');
|
||||
// 检查重启是否成功
|
||||
if (!empty($reloadCheck)) {
|
||||
$this->error('检测到面板服务重载失败,请加QQ群:12370907 反馈处理');
|
||||
} else {
|
||||
$this->info('正在清理临时文件...');
|
||||
shell_exec('rm -rf /tmp/panel.zip');
|
||||
shell_exec('rm -rf /tmp/database.sqlite');
|
||||
shell_exec('rm -rf /tmp/plugins');
|
||||
$this->info('面板更新成功');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取面板信息
|
||||
* @return void
|
||||
*/
|
||||
private function getInfo(): void
|
||||
{
|
||||
$user = User::query()->where('id', 1);
|
||||
// 生成唯一信息
|
||||
$username = Str::random(6);
|
||||
$password = Str::random(12);
|
||||
// 入库
|
||||
$user->update([
|
||||
'username' => $username,
|
||||
'password' => Hash::make($password),
|
||||
]);
|
||||
|
||||
// 从nginx配置文件中获取面板端口
|
||||
$nginxConf = file_get_contents('/www/server/nginx/conf/nginx.conf');
|
||||
preg_match('/listen\s+(\d+)/', $nginxConf, $matches);
|
||||
|
||||
if (!isset($matches[1])) {
|
||||
$this->info('获取面板端口失败,请检查nginx主配置文件');
|
||||
}
|
||||
|
||||
$this->info('面板用户名:'.$username);
|
||||
$this->info('面板密码:'.$password);
|
||||
$this->info('访问地址:http://IP:'.$matches[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取端口
|
||||
* @return void
|
||||
*/
|
||||
private function getPort(): void
|
||||
{
|
||||
// 从nginx配置文件中获取面板端口
|
||||
$nginxConf = file_get_contents('/www/server/nginx/conf/nginx.conf');
|
||||
preg_match('/listen\s+(\d+)/', $nginxConf, $matches);
|
||||
|
||||
if (isset($matches[1])) {
|
||||
$this->info('面板访问端口为:'.$matches[1]);
|
||||
} else {
|
||||
$this->error('获取面板端口失败,请检查nginx主配置文件');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入插件安装状态
|
||||
* @return void
|
||||
*/
|
||||
private function writePluginInstall(): void
|
||||
{
|
||||
$pluginSlug = $this->argument('a1');
|
||||
|
||||
// 判空
|
||||
if (empty($pluginSlug)) {
|
||||
$this->error('参数错误');
|
||||
return;
|
||||
}
|
||||
// 入库
|
||||
Plugin::query()->updateOrCreate(
|
||||
['slug' => $pluginSlug],
|
||||
['show' => 0]
|
||||
);
|
||||
$this->info('成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入插件卸载状态
|
||||
* @return void
|
||||
*/
|
||||
private function writePluginUnInstall(): void
|
||||
{
|
||||
$pluginSlug = $this->argument('a1');
|
||||
|
||||
// 判空
|
||||
if (empty($pluginSlug)) {
|
||||
$this->error('参数错误');
|
||||
return;
|
||||
}
|
||||
if ($pluginSlug == 'openresty') {
|
||||
$this->error('耗子Linux面板:请不要花样作死!');
|
||||
return;
|
||||
}
|
||||
// 入库
|
||||
Plugin::query()->where('slug', $pluginSlug)->delete();
|
||||
$this->info('成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入MySQL密码
|
||||
* @return void
|
||||
*/
|
||||
private function writeMysqlPassword(): void
|
||||
{
|
||||
$password = $this->argument('a1');
|
||||
|
||||
// 判空
|
||||
if (empty($password)) {
|
||||
$this->error('参数错误');
|
||||
return;
|
||||
}
|
||||
// 入库
|
||||
Setting::query()->where('name', 'mysql_root_password')->updateOrCreate([
|
||||
'name' => 'mysql_root_password',
|
||||
], [
|
||||
'value' => $password,
|
||||
]);
|
||||
$this->info('成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有运行中和等待中的任务
|
||||
* @return void
|
||||
*/
|
||||
private function cleanRunningTask(): void
|
||||
{
|
||||
// 更新任务状态
|
||||
Task::query()->update(['status' => 'finished']);
|
||||
// 将所有队列任务清空
|
||||
shell_exec('php-panel /www/panel/artisan queue:clear');
|
||||
$this->info('成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份网站/MySQL数据库/PostgreSQL数据库到指定目录
|
||||
* @return void
|
||||
*/
|
||||
private function backup(): void
|
||||
{
|
||||
$type = $this->argument('a1');
|
||||
$name = $this->argument('a2');
|
||||
$path = $this->argument('a3');
|
||||
|
||||
// 判空
|
||||
if (empty($type) || empty($name) || empty($path)) {
|
||||
$this->error('参数错误');
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断目录是否存在
|
||||
if (!is_dir($path)) {
|
||||
$this->error('目录不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断目录是否可写
|
||||
if (!is_writable($path)) {
|
||||
$this->error('目录不可写');
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断备份目录是否以/结尾,有则去掉
|
||||
if (str_ends_with($path, '/')) {
|
||||
$path = substr($path, 0, -1);
|
||||
}
|
||||
|
||||
// 判断类型
|
||||
if ($type == 'website') {
|
||||
// 备份网站
|
||||
// 从数据库中获取网站目录
|
||||
$sitePath = Website::query()->where('name', $name)->value('path');
|
||||
if (empty($sitePath)) {
|
||||
$this->error('网站不存在');
|
||||
return;
|
||||
}
|
||||
$backupFile = $path.'/'.$name.'_'.date('YmdHis').'.zip';
|
||||
shell_exec('zip -r '.$backupFile.' '.escapeshellarg($sitePath).' 2>&1');
|
||||
$this->info('成功');
|
||||
} elseif ($type == 'mysql') {
|
||||
// 备份MySQL数据库
|
||||
$password = Setting::query()->where('name', 'mysql_root_password')->value('value');
|
||||
$backupFile = $path.'/'.$name.'_'.date('YmdHis').'.sql.zip';
|
||||
$tempFile = $name.'_'.date('YmdHis').'.sql';
|
||||
// 判断数据库是否存在
|
||||
$name = escapeshellarg($name);
|
||||
$check = shell_exec("mysql -u root -p".$password." -e 'use ".$name."' 2>&1");
|
||||
if (str_contains($check, 'ERROR')) {
|
||||
$this->error('数据库不存在');
|
||||
return;
|
||||
}
|
||||
// 傻逼MySQL,瞎jb输出警告
|
||||
//shell_exec("mysqldump -u root -p".$password." ".$name." 2> /dev/null > /tmp/".$tempFile);
|
||||
putenv('MYSQL_PWD='.$password);
|
||||
shell_exec("mysqldump -u root ".$name." > /tmp/".$tempFile);
|
||||
// zip压缩
|
||||
shell_exec('cd /tmp && zip -r '.$tempFile.'.zip '.escapeshellarg($tempFile).' 2>&1');
|
||||
// 移动文件
|
||||
if (file_exists($backupFile)) {
|
||||
$this->error('检测到备份已存在,已跳过此次备份');
|
||||
unlink('/tmp/'.$tempFile);
|
||||
return;
|
||||
}
|
||||
rename('/tmp/'.$tempFile.'.zip', $backupFile);
|
||||
// 删除临时文件
|
||||
unlink('/tmp/'.$tempFile);
|
||||
$this->info('成功');
|
||||
} elseif ($type == 'postgresql') {
|
||||
// 备份PostgreSQL数据库
|
||||
$backupFile = $path.'/'.$name.'_'.date('YmdHis').'.sql.zip';
|
||||
$tempFile = $name.'_'.date('YmdHis').'.sql';
|
||||
// 判断数据库是否存在
|
||||
$check = shell_exec('su - postgres -c "psql -l" 2>&1');
|
||||
if (!str_contains($check, $name)) {
|
||||
$this->error('数据库不存在');
|
||||
return;
|
||||
}
|
||||
$name = escapeshellarg($name);
|
||||
shell_exec('su - postgres -c "pg_dump '.$name.'" > /tmp/'.$tempFile.' 2>&1');
|
||||
// zip压缩
|
||||
shell_exec('cd /tmp && zip -r '.$tempFile.'.zip '.escapeshellarg($tempFile).' 2>&1');
|
||||
// 移动文件
|
||||
if (file_exists($backupFile)) {
|
||||
$this->error('检测到备份已存在,已跳过此次备份');
|
||||
unlink('/tmp/'.$tempFile);
|
||||
return;
|
||||
}
|
||||
rename('/tmp/'.$tempFile.'.zip', $backupFile);
|
||||
// 删除临时文件
|
||||
unlink('/tmp/'.$tempFile);
|
||||
$this->info('成功');
|
||||
} else {
|
||||
$this->error('参数错误');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入网站数据到面板
|
||||
* @return void
|
||||
*/
|
||||
private function writeSite(): void
|
||||
{
|
||||
$name = $this->argument('a1');
|
||||
$status = $this->argument('a2');
|
||||
$path = $this->argument('a3');
|
||||
$php = $this->argument('a4');
|
||||
$ssl = $this->argument('a5');
|
||||
|
||||
// 判空
|
||||
if (empty($name) || empty($status) || empty($path) || empty($php) || empty($ssl)) {
|
||||
$this->error('参数错误');
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断网站是否存在
|
||||
if (Website::query()->where('name', $name)->exists()) {
|
||||
$this->error('网站已存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断目录是否存在
|
||||
if (!is_dir($path)) {
|
||||
$this->error('目录不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 写入网站
|
||||
Website::query()->create([
|
||||
'name' => $name,
|
||||
'status' => $status,
|
||||
'path' => $path,
|
||||
'php' => $php,
|
||||
'ssl' => $ssl,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除面板网站数据
|
||||
* @return void
|
||||
*/
|
||||
private function deleteSite(): void
|
||||
{
|
||||
$name = $this->argument('a1');
|
||||
|
||||
// 判空
|
||||
if (empty($name)) {
|
||||
$this->error('参数错误');
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断网站是否存在
|
||||
if (!Website::query()->where('name', $name)->exists()) {
|
||||
$this->error('网站不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 删除网站
|
||||
Website::query()->where('name', $name)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入/更新面板设置数据
|
||||
* @return void
|
||||
*/
|
||||
private function writeSetting(): void
|
||||
{
|
||||
$name = $this->argument('a1');
|
||||
$value = $this->argument('a2');
|
||||
|
||||
// 判空
|
||||
if (empty($name) || empty($value)) {
|
||||
$this->error('参数错误');
|
||||
return;
|
||||
}
|
||||
Setting::query()->updateOrCreate(['name' => $name], ['value' => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除面板设置数据
|
||||
* @return void
|
||||
*/
|
||||
private function deleteSetting(): void
|
||||
{
|
||||
$name = $this->argument('a1');
|
||||
|
||||
// 判空
|
||||
if (empty($name)) {
|
||||
$this->error('参数错误');
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断设置是否存在
|
||||
if (!Setting::query()->where('name', $name)->exists()) {
|
||||
$this->error('设置不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 删除设置
|
||||
Setting::query()->where('name', $name)->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
use App\Models\Cron;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
$schedule->command('monitor')->everyMinute();
|
||||
// 查询所有计划任务
|
||||
$crons = Cron::all();
|
||||
foreach ($crons as $cron) {
|
||||
$file = '/www/server/cron/'.$cron->shell;
|
||||
// 检查文件是否存在,及所有者是否为root
|
||||
if (!file_exists($file) || fileowner($file) != 0) {
|
||||
file_put_contents('/www/server/cron/logs/'.$cron->id.'.log',
|
||||
PHP_EOL.'耗子Linux面板:检测到脚本文件异常,为确保安全已终止运行,如果你不知道发生了什么,这通常意味着服务器已被入侵。'.PHP_EOL,
|
||||
FILE_APPEND);
|
||||
continue;
|
||||
}
|
||||
$schedule->exec('bash '.escapeshellarg($file))->withoutOverlapping()->cron($cron->time)->appendOutputTo('/www/server/cron/logs/'.$cron->id.'.log')->when(function (
|
||||
) use ($cron) {
|
||||
return (boolean) $cron->status;
|
||||
})->after(function () use ($cron) {
|
||||
$cron->updated_at = now();
|
||||
$cron->save();
|
||||
})->onSuccess(function () use ($cron) {
|
||||
file_put_contents('/www/server/cron/logs/'.$cron->id.'.log',
|
||||
PHP_EOL.Carbon::now()->toDateTimeString().' 任务执行成功'.PHP_EOL, FILE_APPEND);
|
||||
})->onFailure(function () use ($cron) {
|
||||
file_put_contents('/www/server/cron/logs/'.$cron->id.'.log',
|
||||
PHP_EOL.Carbon::now()->toDateTimeString().' 任务执行失败'.PHP_EOL, FILE_APPEND);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of exception types with their corresponding custom log levels.
|
||||
*
|
||||
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
|
||||
*/
|
||||
protected $levels = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array<int, class-string<\Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子Linux面板 - 计划任务控制器
|
||||
* @author 耗子
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Cron;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CronsController extends Controller
|
||||
{
|
||||
/**
|
||||
* 面板计划任务列表
|
||||
*/
|
||||
public function getList(Request $request): JsonResponse
|
||||
{
|
||||
$limit = $request->input('limit', 10);
|
||||
|
||||
$crons = Cron::query()->orderBy('id', 'desc')->paginate($limit);
|
||||
$cronData = [];
|
||||
|
||||
foreach ($crons as $k => $v) {
|
||||
// 格式化时间
|
||||
$cronData[$k]['id'] = $v['id'];
|
||||
$cronData[$k]['name'] = $v['name'];
|
||||
$cronData[$k]['status'] = $v['status'];
|
||||
$cronData[$k]['type'] = $v['type'];
|
||||
$cronData[$k]['time'] = $v['time'];
|
||||
$cronData[$k]['shell'] = $v['shell'];
|
||||
$cronData[$k]['script'] = @file_get_contents('/www/server/cron/'.$v['shell']);
|
||||
$cronData[$k]['created_at'] = Carbon::create($v['created_at'])->toDateTimeString();
|
||||
$cronData[$k]['updated_at'] = Carbon::create($v['updated_at'])->toDateTimeString();
|
||||
}
|
||||
|
||||
$data['code'] = 0;
|
||||
$data['msg'] = 'success';
|
||||
$data['count'] = $crons->total();
|
||||
$data['data'] = $cronData;
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加计划任务
|
||||
*/
|
||||
public function add(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'name' => 'required|max:255',
|
||||
'time' => [
|
||||
'required',
|
||||
'regex:/^((\*|\d+|\d+-\d+|\d+\/\d+|\d+-\d+\/\d+|\*\/\d+)(\,(\*|\d+|\d+-\d+|\d+\/\d+|\d+-\d+\/\d+|\*\/\d+))*\s?){5}$/'
|
||||
],
|
||||
'script' => 'required',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
// 将script写入shell文件
|
||||
$shellDir = '/www/server/cron/';
|
||||
$shellLogDir = '/www/server/cron/logs/';
|
||||
if (!is_dir($shellDir)) {
|
||||
mkdir($shellDir, 700, true);
|
||||
}
|
||||
if (!is_dir($shellLogDir)) {
|
||||
mkdir($shellLogDir, 600, true);
|
||||
}
|
||||
$shellFile = uniqid().'.sh';
|
||||
file_put_contents($shellDir.$shellFile, $credentials['script']);
|
||||
|
||||
$cron = new Cron();
|
||||
$cron->name = $credentials['name'];
|
||||
$cron->status = 1;
|
||||
$cron->type = '脚本';
|
||||
$cron->time = $credentials['time'];
|
||||
$cron->shell = $shellFile;
|
||||
$cron->save();
|
||||
|
||||
$data['code'] = 0;
|
||||
$data['msg'] = 'success';
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改计划任务
|
||||
*/
|
||||
public function edit(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'id' => 'required|integer',
|
||||
'name' => 'required|max:255',
|
||||
'time' => ['required', 'regex:/^((\*|\d+|\d+-\d+|\d+\/\d+)(\,(\*|\d+|\d+-\d+|\d+\/\d+))*\s?){5}$/'],
|
||||
'script' => 'required',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
$cron = Cron::query()->find($credentials['id']);
|
||||
$cron->name = $credentials['name'];
|
||||
$cron->time = $credentials['time'];
|
||||
// 将script写入shell文件
|
||||
$shellDir = '/www/server/cron/';
|
||||
$shellLogDir = '/www/server/cron/logs/';
|
||||
if (!is_dir($shellDir)) {
|
||||
mkdir($shellDir, 700, true);
|
||||
}
|
||||
if (!is_dir($shellLogDir)) {
|
||||
mkdir($shellLogDir, 600, true);
|
||||
}
|
||||
$shellFile = $cron->shell;
|
||||
file_put_contents($shellDir.$shellFile, $credentials['script']);
|
||||
// 将文件转为unix格式
|
||||
exec('dos2unix '.$shellDir.$shellFile);
|
||||
// 设置文件权限
|
||||
exec('chmod 700 '.$shellDir.$shellFile);
|
||||
$cron->save();
|
||||
|
||||
$data['code'] = 0;
|
||||
$data['msg'] = 'success';
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除计划任务
|
||||
*/
|
||||
public function delete(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'id' => 'required|integer',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
$cron = Cron::query()->find($credentials['id']);
|
||||
// 删除shell文件
|
||||
$shellDir = '/www/server/cron/';
|
||||
$shellFile = $cron->shell;
|
||||
@unlink($shellDir.$shellFile);
|
||||
// 删除日志文件
|
||||
$shellLogDir = '/www/server/cron/logs/';
|
||||
$shellLogFile = $shellFile.'.log';
|
||||
@unlink($shellLogDir.$shellLogFile);
|
||||
$cron->delete();
|
||||
|
||||
$data['code'] = 0;
|
||||
$data['msg'] = 'success';
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改计划任务状态
|
||||
*/
|
||||
public function setStatus(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'id' => 'required|integer',
|
||||
'status' => 'required|integer',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
$cron = Cron::query()->find($credentials['id']);
|
||||
$cron->status = $credentials['status'];
|
||||
$cron->save();
|
||||
|
||||
$data['code'] = 0;
|
||||
$data['msg'] = 'success';
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划任务日志
|
||||
*/
|
||||
public function getLog(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'id' => 'required|integer',
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
$log = @file_get_contents('/www/server/cron/logs/'.$credentials['id'].'.log');
|
||||
if ($log === false) {
|
||||
$log = '暂无日志';
|
||||
}
|
||||
|
||||
$data['code'] = 0;
|
||||
$data['msg'] = 'success';
|
||||
$data['data'] = htmlspecialchars($log);
|
||||
return response()->json($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子Linux面板 - 文件控制器
|
||||
* @author 耗子
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class FilesController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取某个目录下的文件(夹)列表
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getList(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'limit' => 'required|integer',
|
||||
'page' => 'required|integer',
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$limit = $credentials['limit'];
|
||||
$page = $credentials['page'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '目录不存在']);
|
||||
}
|
||||
|
||||
$files = scandir($path);
|
||||
$fileData = [];
|
||||
foreach ($files as $k => $v) {
|
||||
if ($v == '.' || $v == '..') {
|
||||
continue;
|
||||
}
|
||||
$fileData[$k]['name'] = $v;
|
||||
$fileData[$k]['path'] = $path.'/'.$v;
|
||||
$fileData[$k]['type'] = filetype($path.'/'.$v);
|
||||
$fileData[$k]['size'] = filesize($path.'/'.$v);
|
||||
$fileData[$k]['mtime'] = Carbon::createFromTimestamp(filemtime($path.'/'.$v))->toDateTimeString();
|
||||
}
|
||||
|
||||
// 分页
|
||||
|
||||
$total = count($fileData);
|
||||
$filesArr = array_slice($fileData, ($page - 1) * $limit, $limit);
|
||||
$data['code'] = 0;
|
||||
$data['msg'] = 'success';
|
||||
$data['count'] = $total;
|
||||
$data['data'] = $filesArr;
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件内容
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getFileContent(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'file' => ['required', 'regex:/^\/.*$/'],
|
||||
]);
|
||||
$file = $credentials['file'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($file)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件不存在']);
|
||||
}
|
||||
|
||||
$content = @file_get_contents($file);
|
||||
$data['code'] = 0;
|
||||
$data['msg'] = 'success';
|
||||
$data['data'] = $content;
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件内容
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function saveFileContent(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'file' => ['required', 'regex:/^\/.*$/'],
|
||||
'content' => 'required',
|
||||
]);
|
||||
$file = $credentials['file'];
|
||||
$content = $credentials['content'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($file)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件不存在']);
|
||||
}
|
||||
|
||||
$res = @file_put_contents($file, $content);
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '保存失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建目录
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function createDir(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'name' => 'required',
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$name = $credentials['name'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '目录不存在']);
|
||||
}
|
||||
|
||||
$res = @mkdir($path.'/'.$name);
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '创建失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名文件或目录
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function rename(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'name' => 'required',
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$name = $credentials['name'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path) && !is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
|
||||
}
|
||||
|
||||
$res = @rename($path, dirname($path).'/'.$name);
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '重命名失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function uploadFile(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'file' => 'required|file',
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$file = $credentials['file'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '目录不存在']);
|
||||
}
|
||||
|
||||
$res = @move_uploaded_file($file->getRealPath(), $path.'/'.$file->getClientOriginalName());
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '上传失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param Request $request
|
||||
* @return JsonResponse|BinaryFileResponse
|
||||
*/
|
||||
public function downloadFile(Request $request): BinaryFileResponse|JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件不存在']);
|
||||
}
|
||||
|
||||
return response()->download($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件
|
||||
*/
|
||||
public function createFile(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'name' => 'required',
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$name = $credentials['name'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '目录不存在']);
|
||||
}
|
||||
|
||||
$res = @file_put_contents($path.'/'.$name, '');
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '创建失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置权限
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function chmod(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'mode' => 'required',
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$mode = $credentials['mode'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path) && !is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
|
||||
}
|
||||
|
||||
$res = @chmod($path, $mode);
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '设置失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置所有者
|
||||
*/
|
||||
public function chown(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'user' => 'required',
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$user = $credentials['user'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path) && !is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
|
||||
}
|
||||
|
||||
$res = @chown($path, $user);
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '设置失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置所有者组
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function chgrp(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'group' => 'required',
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$group = $credentials['group'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path) && !is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
|
||||
}
|
||||
|
||||
$res = @chgrp($path, $group);
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '设置失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文件/目录
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function copy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'dest' => ['required', 'regex:/^\/.*$/'],
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$dest = $credentials['dest'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path) && !is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
|
||||
}
|
||||
|
||||
if (!is_dir($dest)) {
|
||||
return response()->json(['code' => 1, 'msg' => '目标目录不存在']);
|
||||
}
|
||||
|
||||
$res = @copy($path, $dest.'/'.basename($path));
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '复制失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件/目录
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function move(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
'dest' => ['required', 'regex:/^\/.*$/'],
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
$dest = $credentials['dest'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path) && !is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
|
||||
}
|
||||
|
||||
if (!is_dir($dest)) {
|
||||
return response()->json(['code' => 1, 'msg' => '目标目录不存在']);
|
||||
}
|
||||
|
||||
$res = @rename($path, $dest.'/'.basename($path));
|
||||
if ($res === false) {
|
||||
return response()->json(['code' => 1, 'msg' => '移动失败']);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件/目录
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function delete(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path) && !is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
|
||||
}
|
||||
|
||||
$res = @shell_exec('rm -rf '.escapeshellarg($path).' 2>&1');
|
||||
if (!empty($res)) {
|
||||
return response()->json(['code' => 1, 'msg' => '删除失败:'.$res]);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩文件/目录
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function zip(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path) && !is_dir($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
|
||||
}
|
||||
|
||||
$zipPath = dirname($path).'/'.basename($path).'.zip';
|
||||
$res = @shell_exec('zip -r '.escapeshellarg($zipPath).' '.escapeshellarg($path).' 2>&1');
|
||||
if (!empty($res)) {
|
||||
return response()->json(['code' => 1, 'msg' => '压缩失败:'.$res]);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压文件/目录
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function unzip(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'path' => ['required', 'regex:/^\/.*$/'],
|
||||
]);
|
||||
$path = $credentials['path'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
if (!is_file($path)) {
|
||||
return response()->json(['code' => 1, 'msg' => '文件不存在']);
|
||||
}
|
||||
|
||||
$res = @shell_exec('unzip -o '.escapeshellarg($path).' -d '.escapeshellarg(dirname($path)).' 2>&1');
|
||||
if (!empty($res)) {
|
||||
return response()->json(['code' => 1, 'msg' => '解压失败:'.$res]);
|
||||
}
|
||||
|
||||
return response()->json(['code' => 0, 'msg' => 'success']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子Linux面板 - 信息控制器
|
||||
* @author 耗子
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Plugin;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class InfosController extends Controller
|
||||
{
|
||||
public function getMenu(): JsonResponse
|
||||
{
|
||||
$menu = array(
|
||||
"code" => 0,
|
||||
"msg" => "",
|
||||
"data" => array(
|
||||
array(
|
||||
"name" => "home",
|
||||
"title" => "主页",
|
||||
"icon" => "layui-icon-home",
|
||||
"jump" => "/"
|
||||
),
|
||||
array(
|
||||
"name" => "website",
|
||||
"title" => "网站管理",
|
||||
"icon" => "layui-icon-website",
|
||||
"jump" => "website/list"
|
||||
),
|
||||
array(
|
||||
"name" => "monitor",
|
||||
"title" => "资源监控",
|
||||
"icon" => "layui-icon-chart-screen",
|
||||
"jump" => "monitor"
|
||||
),
|
||||
array(
|
||||
"name" => "safe",
|
||||
"title" => "系统安全",
|
||||
"icon" => "layui-icon-auz",
|
||||
"jump" => "safe"
|
||||
),
|
||||
array(
|
||||
"name" => "file",
|
||||
"title" => "文件管理",
|
||||
"icon" => "layui-icon-file",
|
||||
"jump" => "file"
|
||||
),
|
||||
array(
|
||||
"name" => "cron",
|
||||
"title" => "计划任务",
|
||||
"icon" => "layui-icon-date",
|
||||
"jump" => "cron"
|
||||
),
|
||||
array(
|
||||
"name" => "plugin",
|
||||
"title" => "插件中心",
|
||||
"icon" => "layui-icon-app",
|
||||
"jump" => "plugin"
|
||||
),
|
||||
array(
|
||||
"name" => "setting",
|
||||
"title" => "面板设置",
|
||||
"icon" => "layui-icon-set",
|
||||
"jump" => "setting"
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return response()->json($menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统资源统计
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getNowMonitor(): JsonResponse
|
||||
{
|
||||
// 第一次获取网络信息
|
||||
$netInfo1 = getNetInfo();
|
||||
// 卡它一秒钟
|
||||
sleep(1);
|
||||
// 第二次获取网络信息
|
||||
$netInfo2 = getNetInfo();
|
||||
|
||||
// CPU统计信息及负载
|
||||
$cpuInfoRaw = file_get_contents('/proc/cpuinfo');
|
||||
$physicalArr = array();
|
||||
$physicalSum = 0;
|
||||
$coresSum = 0;
|
||||
$siblingsSum = 0;
|
||||
preg_match("/model name\s*:\s(.*)/", $cpuInfoRaw, $cpuName);
|
||||
preg_match("/vendor_id\s*:\s(.*)/", $cpuInfoRaw, $cpuVendor);
|
||||
preg_match("/cpu family\s*:\s(.*)/", $cpuInfoRaw, $cpuFamily);
|
||||
preg_match("/cpu MHz\s*:\s(.*)/", $cpuInfoRaw, $cpuFreq);
|
||||
preg_match("/cache size\s*:\s(.*)/", $cpuInfoRaw, $cpuCache);
|
||||
preg_match("/(\d+\.\d+), (\d+\.\d+), (\d+\.\d+)/", exec('uptime'), $uptime);
|
||||
$cpuName = $cpuName[1] ?? 'No';
|
||||
$cpuVendor = $cpuVendor[1] ?? 'No';
|
||||
$cpuFamily = $cpuFamily[1] ?? 'No';
|
||||
$cpuFreq = isset($cpuFreq[1]) ? round($cpuFreq[1], 2) : 'No';
|
||||
$cpuCache = $cpuCache[1] ?? 'No';
|
||||
$uptime1 = $uptime[1] ?? 0;
|
||||
$uptime5 = $uptime[2] ?? 0;
|
||||
$uptime15 = $uptime[3] ?? 0;
|
||||
|
||||
$processorArr = explode("\nprocessor", $cpuInfoRaw);
|
||||
foreach ($processorArr as $v) {
|
||||
preg_match("/physical id\s*:\s(.*)/", $v, $physical);
|
||||
preg_match("/cpu cores\s*:\s(.*)/", $v, $cores);
|
||||
preg_match("/siblings\s*:\s(.*)/", $v, $siblings);
|
||||
if (isset($physical[1])) {
|
||||
if (!in_array($physical[1], $physicalArr)) {
|
||||
$physicalSum += 1;
|
||||
if (isset($cores[1])) {
|
||||
$coresSum += $cores[1];
|
||||
}
|
||||
|
||||
if (isset($siblings[1])) {
|
||||
$siblingsSum += $siblings[1];
|
||||
}
|
||||
}
|
||||
$physicalArr[] = $physical[1];
|
||||
}
|
||||
}
|
||||
|
||||
// CPU使用率
|
||||
$cpuUse = 0.1;
|
||||
$cpuRaw = explode("\n", shell_exec('ps aux'));
|
||||
// 弹出第一项和最后一项
|
||||
array_pop($cpuRaw);
|
||||
array_shift($cpuRaw);
|
||||
// 获取当前php进程的pid
|
||||
$pid = getmypid();
|
||||
foreach ($cpuRaw as $v) {
|
||||
$v = preg_replace("/\s+/", " ", $v);
|
||||
$v = (explode(' ', $v));
|
||||
// 排除当前进程
|
||||
if ($v[1] == $pid) {
|
||||
continue;
|
||||
}
|
||||
$cpuUse += isset($v[2]) ? (float) $v[2] : 0;
|
||||
}
|
||||
$cpuUse = $siblingsSum > 0 ? ($cpuUse / $siblingsSum) : $cpuUse;
|
||||
$cpuUse = round($cpuUse, 2);
|
||||
$cpuUse = $cpuUse > 100 ? 100 .'%' : $cpuUse.'%';
|
||||
|
||||
// 内存使用率
|
||||
$memRaw = explode("\n", shell_exec('free -m'));
|
||||
foreach ($memRaw as $v) {
|
||||
if (str_contains($v, 'Mem')) {
|
||||
$memList = preg_replace("/\s+/", " ", $v);
|
||||
}
|
||||
}
|
||||
$memArr = explode(' ', $memList);
|
||||
// 内存大小MB
|
||||
$memTotal = $memArr[1];
|
||||
// 使用中MB
|
||||
$memUse = (str_contains($memRaw[0], 'buff/cache')) ? $memArr[2] : ($memArr[2] - $memArr[5] - $memArr[6]);
|
||||
// 使用中%
|
||||
$memUseP = round($memUse / $memTotal, 2) * 100 .'%';
|
||||
// 1分钟负载%
|
||||
$uptime1P = $uptime1 * 10;
|
||||
$uptime1P = $uptime1P > 100 ? 100 .'%' : $uptime1P.'%';
|
||||
// 5分钟负载%
|
||||
$uptime5P = $uptime5 * 10;
|
||||
$uptime5P = $uptime5P > 100 ? 100 .'%' : $uptime5P.'%';
|
||||
// 15分钟负载%
|
||||
$uptime15P = $uptime15 * 10;
|
||||
$uptime15P = $uptime15P > 100 ? 100 .'%' : $uptime15P.'%';
|
||||
|
||||
// 构建返回数组
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = [
|
||||
'cpu_use' => $cpuUse,
|
||||
'uptime_1' => $uptime1,
|
||||
'uptime_1_p' => $uptime1P,
|
||||
'uptime_5' => $uptime5,
|
||||
'uptime_5_p' => $uptime5P,
|
||||
'uptime_15' => $uptime15,
|
||||
'uptime_15_p' => $uptime15P,
|
||||
'mem_total' => $memTotal,
|
||||
'mem_use' => $memUse,
|
||||
'mem_use_p' => $memUseP,
|
||||
'tx_total' => formatBytes($netInfo1['tx']),
|
||||
'rx_total' => formatBytes($netInfo1['rx']),
|
||||
'tx_now' => formatBytes($netInfo2['tx'] - $netInfo1['tx']),
|
||||
'rx_now' => formatBytes($netInfo2['rx'] - $netInfo1['rx']),
|
||||
'cpu_info' => [
|
||||
'name' => $cpuName,
|
||||
'cores' => $coresSum,
|
||||
'physical' => $physicalSum,
|
||||
'siblings' => $siblingsSum,
|
||||
'vendor' => $cpuVendor,
|
||||
'family' => $cpuFamily,
|
||||
'freq' => $cpuFreq,
|
||||
'cache' => $cpuCache,
|
||||
],
|
||||
];
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统信息
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getSystemInfo(): JsonResponse
|
||||
{
|
||||
$os_name = file_get_contents('/etc/redhat-release');
|
||||
$uptime = file_get_contents('/proc/uptime');
|
||||
$uptime = explode(' ', $uptime)[0];
|
||||
$uptime = round($uptime / 86400, 1);
|
||||
$panel_version = config('panel.version');
|
||||
// 构建返回数组
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = [
|
||||
'os_name' => $os_name,
|
||||
'uptime' => $uptime,
|
||||
'panel_version' => $panel_version,
|
||||
];
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页插件列表
|
||||
*/
|
||||
public function getHomePlugins(): JsonResponse
|
||||
{
|
||||
$plugins = Plugin::query()->where('show', 1)->get();
|
||||
// 判空
|
||||
if ($plugins->isEmpty()) {
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = [];
|
||||
} else {
|
||||
$plugins = $plugins->toArray();
|
||||
$plugins = array_map(function ($item) {
|
||||
$item['name'] = PLUGINS[$item['slug']]['name'];
|
||||
return $item;
|
||||
}, $plugins);
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = $plugins;
|
||||
}
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已安装的数据库和PHP版本
|
||||
*/
|
||||
public function getInstalledDbAndPhp(): JsonResponse
|
||||
{
|
||||
$dbVersions = [];
|
||||
// 判断mysql插件是否安装
|
||||
if (isset(PLUGINS['mysql'])) {
|
||||
$dbVersions['mysql'] = PLUGINS['mysql']['version'];
|
||||
} else {
|
||||
$dbVersions['mysql'] = false;
|
||||
}
|
||||
// 判断postgresql插件是否安装
|
||||
if (isset(PLUGINS['postgresql'])) {
|
||||
$dbVersions['postgresql'] = PLUGINS['postgresql']['version'];
|
||||
} else {
|
||||
$dbVersions['postgresql'] = false;
|
||||
}
|
||||
// 循环获取已安装的PHP版本
|
||||
$php_versions = Plugin::query()->where('slug', 'like', 'php%')->get();
|
||||
$php_versions = $php_versions->toArray();
|
||||
$php_versions = array_column($php_versions, 'slug');
|
||||
$php_versions = array_map(function ($item) {
|
||||
return str_replace('php', '', $item);
|
||||
}, $php_versions);
|
||||
|
||||
$php_version = shell_exec('ls /www/server/php');
|
||||
$php_version = trim($php_version);
|
||||
|
||||
if (!empty($php_version)) {
|
||||
$php_versions = explode("\n", $php_version);
|
||||
}
|
||||
$php_versions[] = '00';
|
||||
unset($php_versions[array_search('panel', $php_versions)]);
|
||||
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = array(
|
||||
'db_version' => $dbVersions,
|
||||
'php_version' => $php_versions
|
||||
);
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查更新
|
||||
*/
|
||||
public function checkUpdate(): JsonResponse
|
||||
{
|
||||
$version = config('panel.version');
|
||||
$remoteVersion = Http::get('https://api.panel.haozi.xyz/api/version/info')->json();
|
||||
|
||||
if ($remoteVersion['code'] != 0) {
|
||||
$res['code'] = 1;
|
||||
$res['msg'] = '获取远程版本信息失败';
|
||||
$res['data'] = [];
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
if (version_compare($version, $remoteVersion['data']['version'], '<')) {
|
||||
$res['data'] = [
|
||||
'version' => $remoteVersion['data']['version'],
|
||||
'describe' => $remoteVersion['data']['describe'],
|
||||
];
|
||||
} else {
|
||||
$res['data'] = [];
|
||||
}
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子Linux面板 - 监控控制器
|
||||
* @author 耗子
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Monitor;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class MonitorsController extends Controller
|
||||
{
|
||||
/**
|
||||
* 修改监控开关
|
||||
*/
|
||||
public function setMonitorSwitch(Request $request): JsonResponse
|
||||
{
|
||||
$switch = $request->input('switch');
|
||||
if ($switch) {
|
||||
$status = true;
|
||||
} else {
|
||||
$status = false;
|
||||
}
|
||||
Setting::query()->where('name', 'monitor')->update(['value' => $status]);
|
||||
return response()->json(['code' => 0, 'msg' => '修改成功']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改保存天数
|
||||
*/
|
||||
public function setMonitorSaveDays(Request $request): JsonResponse
|
||||
{
|
||||
$days = $request->input('days');
|
||||
Setting::query()->where('name', 'monitor_days')->update(['value' => $days]);
|
||||
return response()->json(['code' => 0, 'msg' => '修改成功']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空监控数据
|
||||
*/
|
||||
public function clearMonitorData(): JsonResponse
|
||||
{
|
||||
Monitor::query()->truncate();
|
||||
return response()->json(['code' => 0, 'msg' => '清空成功']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取监控开关和保存天数
|
||||
*/
|
||||
public function getMonitorSwitchAndDays(): JsonResponse
|
||||
{
|
||||
$monitor = Setting::query()->where('name', 'monitor')->first();
|
||||
$monitor_days = Setting::query()->where('name', 'monitor_days')->first();
|
||||
return response()->json([
|
||||
'code' => 0, 'msg' => '获取成功',
|
||||
'data' => ['monitor' => $monitor->value, 'monitor_days' => $monitor_days->value]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取监控数据
|
||||
*/
|
||||
public function getMonitorData(Request $request): JsonResponse
|
||||
{
|
||||
$start = $request->input('start') ?? now();
|
||||
$end = $request->input('end') ?? now();
|
||||
$start = Carbon::create($start);
|
||||
$end = Carbon::create($end);
|
||||
$data = Monitor::query()->where('created_at', '>=', $start)->where('created_at', '<=', $end)->get()->toArray();
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
if (empty($data)) {
|
||||
$res['code'] = 1;
|
||||
$res['msg'] = '暂无数据';
|
||||
return response()->json($res);
|
||||
}
|
||||
foreach ($data as $key => $value) {
|
||||
$info = json_decode($value['info'], true);
|
||||
$res['data']['times'][] = Carbon::create($value['created_at'])->tz(config('app.timezone',
|
||||
'PRC'))->isoFormat('MM-DD HH:mm');
|
||||
$res['data']['uptime']['uptime'][] = round($info['uptime'], 2);
|
||||
$res['data']['cpu']['use'][] = round($info['cpu_use'], 2);
|
||||
$res['data']['memory']['mem_use'][] = round($info['mem_use'], 2);
|
||||
$res['data']['memory']['mem_use_p'][] = round($info['mem_use_p'], 2);
|
||||
$res['data']['memory']['swap_use'][] = round($info['swap_use'], 2);
|
||||
$res['data']['memory']['swap_use_p'][] = round($info['swap_use_p'], 2);
|
||||
$res['data']['network']['tx_now'][] = round($info['tx_now'] / 1024, 2);
|
||||
$res['data']['network']['rx_now'][] = round($info['rx_now'] / 1024, 2);
|
||||
}
|
||||
// 插入总内存大小
|
||||
$result = explode("\n", shell_exec('free -m'));
|
||||
foreach ($result as $key => $val) {
|
||||
if (str_contains($val, 'Mem')) {
|
||||
$mem_list = preg_replace("/\s+/", " ", $val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$mem_arr = explode(' ', $mem_list);
|
||||
// 内存大小MB
|
||||
$mem_total = $mem_arr[1];
|
||||
$res['data']['mem_total'] = round($mem_total, 2);
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子Linux面板 - 插件控制器
|
||||
* @author 耗子
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\ProcessShell;
|
||||
use App\Models\Plugin;
|
||||
use App\Models\Task;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
|
||||
class PluginsController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* 面板插件列表
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getList(): JsonResponse
|
||||
{
|
||||
$data['code'] = 0;
|
||||
$data['msg'] = 'success';
|
||||
$data['data'] = $this->pluginList();
|
||||
// 获取首页显示状态
|
||||
$shows = Plugin::query()->pluck('show', 'slug');
|
||||
foreach ($data['data'] as $k => $v) {
|
||||
// 如果本地已安装,则显示本地名称
|
||||
$data['data'][$k]['name'] = PLUGINS[$v['slug']]['name'] ?? $data['data'][$k]['name'] ?? '无名';
|
||||
// 作者名称
|
||||
$data['data'][$k]['author'] = PLUGINS[$v['slug']]['author'] ?? $data['data'][$k]['author'] ?? '耗子';
|
||||
// 已装版本
|
||||
$data['data'][$k]['install_version'] = PLUGINS[$v['slug']]['version'] ?? '';
|
||||
// 首页显示
|
||||
$data['data'][$k]['show'] = $shows[$v['slug']] ?? 0;
|
||||
// 去除不需要的字段
|
||||
unset($data['data'][$k]['url']);
|
||||
unset($data['data'][$k]['install']);
|
||||
unset($data['data'][$k]['uninstall']);
|
||||
unset($data['data'][$k]['update']);
|
||||
if (isset(PLUGINS[$v['slug']])) {
|
||||
$data['data'][$k]['control']['installed'] = true;
|
||||
$data['data'][$k]['control']['allow_uninstall'] = true;
|
||||
// 判断是否有更新
|
||||
$data['data'][$k]['control']['update'] = version_compare($v['version'],
|
||||
$data['data'][$k]['install_version'], '>');
|
||||
if ($v['slug'] == 'openresty') {
|
||||
$data['data'][$k]['control']['allow_uninstall'] = false;
|
||||
}
|
||||
} else {
|
||||
$data['data'][$k]['control']['installed'] = false;
|
||||
$data['data'][$k]['control']['allow_uninstall'] = false;
|
||||
}
|
||||
}
|
||||
// 插入本地插件数据
|
||||
$slugs = array_column($data['data'], 'slug');
|
||||
foreach (PLUGINS as $v) {
|
||||
// 如果本地已安装,则不显示
|
||||
if (in_array($v['slug'], $slugs)) {
|
||||
continue;
|
||||
}
|
||||
// 插入插件数据
|
||||
$data['data'][] = [
|
||||
'name' => $v['name'],
|
||||
'author' => $v['author'],
|
||||
'slug' => $v['slug'],
|
||||
'version' => $v['version'],
|
||||
'install_version' => $v['version'],
|
||||
'describe' => $v['describe'],
|
||||
'show' => $shows[$v['slug']] ?? 0,
|
||||
'control' => [
|
||||
'installed' => true,
|
||||
'allow_uninstall' => false,
|
||||
],
|
||||
];
|
||||
|
||||
}
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装插件
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function install(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'slug' => 'required|max:255',
|
||||
]);
|
||||
$slug = $credentials['slug'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
// 通过slug获取插件信息
|
||||
$pluginList = $this->pluginList();
|
||||
// 循环插件列表查找对应的插件信息
|
||||
$pluginCheck = false;
|
||||
$pluginData = [];
|
||||
foreach ($pluginList as $v) {
|
||||
if ($v['slug'] == $slug) {
|
||||
$pluginData = $v;
|
||||
$pluginCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 判断插件是否存在
|
||||
if (!$pluginCheck) {
|
||||
return response()->json(['code' => 1, 'msg' => '插件不存在']);
|
||||
}
|
||||
|
||||
// 判断插件是否已经安装
|
||||
$installed = isset(PLUGINS[$slug]);
|
||||
if ($installed) {
|
||||
$data['code'] = 1;
|
||||
$data['msg'] = '请不要重复安装!';
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
// 入库等待安装
|
||||
$task = new Task();
|
||||
$task->name = '安装 '.$pluginData['name'];
|
||||
$task->shell = $pluginData['install'];
|
||||
$task->status = 'waiting';
|
||||
$task->log = '/tmp/'.$pluginData['slug'].'.log';
|
||||
$task->save();
|
||||
// 塞入队列
|
||||
ProcessShell::dispatch($task->id)->delay(1);
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = '任务添加成功';
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载插件
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function uninstall(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'slug' => 'required|max:255',
|
||||
]);
|
||||
$slug = $credentials['slug'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
// 通过slug获取插件信息
|
||||
$pluginList = $this->pluginList();
|
||||
|
||||
// 循环插件列表查找对应的插件信息
|
||||
$pluginCheck = false;
|
||||
$pluginData = [];
|
||||
foreach ($pluginList as $v) {
|
||||
if ($v['slug'] == $slug) {
|
||||
$pluginData = $v;
|
||||
$pluginCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 判断插件是否存在
|
||||
if (!$pluginCheck) {
|
||||
return response()->json(['code' => 1, 'msg' => '插件不存在']);
|
||||
}
|
||||
|
||||
// 判断是否是操作openresty
|
||||
if ($slug == 'openresty') {
|
||||
$data['code'] = 1;
|
||||
$data['msg'] = '请不要花样作死!';
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
// 入库等待卸载
|
||||
$task = new Task();
|
||||
$task->name = '卸载 '.$pluginData['name'];
|
||||
$task->shell = $pluginData['uninstall'];
|
||||
$task->status = 'waiting';
|
||||
$task->log = '/tmp/'.$pluginData['slug'].'.log';
|
||||
$task->save();
|
||||
// 塞入队列
|
||||
ProcessShell::dispatch($task->id)->delay(1);
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = '任务添加成功';
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新插件
|
||||
*/
|
||||
public function update(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'slug' => 'required|max:255',
|
||||
]);
|
||||
$slug = $credentials['slug'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
// 通过slug获取插件信息
|
||||
$pluginList = $this->pluginList();
|
||||
|
||||
// 循环插件列表查找对应的插件信息
|
||||
$pluginCheck = false;
|
||||
$pluginData = [];
|
||||
foreach ($pluginList as $v) {
|
||||
if ($v['slug'] == $slug) {
|
||||
$pluginData = $v;
|
||||
$pluginCheck = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 判断插件是否存在
|
||||
if (!$pluginCheck) {
|
||||
return response()->json(['code' => 1, 'msg' => '插件不存在']);
|
||||
}
|
||||
|
||||
// 判断插件是否已经安装
|
||||
$installed = isset(PLUGINS[$slug]);
|
||||
if (!$installed) {
|
||||
$data['code'] = 1;
|
||||
$data['msg'] = '插件未安装!';
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
// 入库等待更新
|
||||
$task = new Task();
|
||||
$task->name = '更新 '.$pluginData['name'];
|
||||
$task->shell = $pluginData['update'];
|
||||
$task->status = 'waiting';
|
||||
$task->log = '/tmp/'.$pluginData['slug'].'.log';
|
||||
$task->save();
|
||||
// 塞入队列
|
||||
ProcessShell::dispatch($task->id)->delay(1);
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = '任务添加成功';
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取插件列表
|
||||
*/
|
||||
public function pluginList($cache = true)
|
||||
{
|
||||
// 判断刷新缓存
|
||||
if (!$cache) {
|
||||
Cache::forget('pluginList');
|
||||
}
|
||||
if (!Cache::has('pluginList')) {
|
||||
return Cache::remember('pluginList', 3600, function () {
|
||||
$response = Http::get('https://api.panel.haozi.xyz/api/plugin/list');
|
||||
// 判断请求是否成功,如果不成功则抛出异常
|
||||
if ($response->failed()) {
|
||||
throw new Exception('获取插件列表失败,请求错误');
|
||||
}
|
||||
// 判断返回的JSON数据中code是否为0,如果不为0则抛出异常
|
||||
if (!$response->json('code') == 0) {
|
||||
throw new Exception('获取插件列表失败,服务器未返回正确的状态码');
|
||||
}
|
||||
|
||||
return $response->json('data');
|
||||
});
|
||||
} else {
|
||||
// 从缓存中获取access_token
|
||||
return Cache::get('pluginList');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置插件首页显示
|
||||
*/
|
||||
public function setShowHome(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'slug' => 'required|max:255',
|
||||
'show' => 'required|boolean',
|
||||
]);
|
||||
$slug = $credentials['slug'];
|
||||
$show = $credentials['show'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
// 判断插件是否已经安装
|
||||
$installed = isset(PLUGINS[$slug]);
|
||||
if (!$installed) {
|
||||
$data['code'] = 1;
|
||||
$data['msg'] = '插件未安装!';
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
Plugin::query()->where('slug', $slug)->updateOrInsert(
|
||||
['slug' => $slug],
|
||||
['show' => $show]
|
||||
);
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = '设置成功';
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子Linux面板 - 安全控制器
|
||||
* @author 耗子
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class SafesController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取防火墙状态
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getFirewallStatus(): JsonResponse
|
||||
{
|
||||
$firewallStatus = trim(shell_exec("systemctl status firewalld | grep Active | awk '{print $3}'"));
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
if ($firewallStatus == '(running)') {
|
||||
$res['data'] = 1;
|
||||
} else {
|
||||
$res['data'] = 0;
|
||||
}
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置防火墙状态
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function setFirewallStatus(Request $request): JsonResponse
|
||||
{
|
||||
$status = $request->input('status');
|
||||
if ($status) {
|
||||
shell_exec("systemctl enable firewalld");
|
||||
shell_exec("systemctl start firewalld");
|
||||
} else {
|
||||
shell_exec("systemctl stop firewalld");
|
||||
shell_exec("systemctl disable firewalld");
|
||||
}
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SSH状态
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getSshStatus(): JsonResponse
|
||||
{
|
||||
$sshStatus = trim(shell_exec("systemctl status sshd | grep Active | awk '{print $3}'"));
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
if ($sshStatus == '(running)') {
|
||||
$res['data'] = 1;
|
||||
} else {
|
||||
$res['data'] = 0;
|
||||
}
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SSH状态
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function setSshStatus(Request $request): JsonResponse
|
||||
{
|
||||
$status = $request->input('status');
|
||||
if ($status) {
|
||||
shell_exec("systemctl enable sshd");
|
||||
shell_exec("systemctl start sshd");
|
||||
} else {
|
||||
shell_exec("systemctl stop sshd");
|
||||
shell_exec("systemctl disable sshd");
|
||||
}
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SSH端口
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getSshPort(): JsonResponse
|
||||
{
|
||||
$sshPort = trim(shell_exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'"));
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = $sshPort;
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SSH端口
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function setSshPort(Request $request): JsonResponse
|
||||
{
|
||||
$port = $request->input('port');
|
||||
// 只能是数字
|
||||
if (!is_numeric($port)) {
|
||||
$res['code'] = 1;
|
||||
$res['msg'] = '端口只能是数字';
|
||||
return response()->json($res);
|
||||
}
|
||||
$oldPort = trim(shell_exec("cat /etc/ssh/sshd_config | grep 'Port ' | awk '{print $2}'"));
|
||||
shell_exec("sed -i 's/#Port ".$oldPort."/Port ".$port."/g' /etc/ssh/sshd_config");
|
||||
shell_exec("sed -i 's/Port ".$oldPort."/Port ".$port."/g' /etc/ssh/sshd_config");
|
||||
// 判断ssh是否开启
|
||||
$sshStatus = trim(shell_exec("systemctl status sshd | grep Active | awk '{print $3}'"));
|
||||
if ($sshStatus == '(running)') {
|
||||
shell_exec("systemctl restart sshd");
|
||||
}
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ping状态
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getPingStatus(): JsonResponse
|
||||
{
|
||||
$pingStatus = trim(shell_exec("firewall-cmd --query-rich-rule='rule protocol value=icmp drop' 2>&1"));
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
if ($pingStatus == 'yes') {
|
||||
$res['data'] = 0;
|
||||
} else {
|
||||
$res['data'] = 1;
|
||||
}
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置ping状态
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function setPingStatus(Request $request): JsonResponse
|
||||
{
|
||||
$status = $request->input('status');
|
||||
if ($status) {
|
||||
shell_exec("firewall-cmd --permanent --remove-rich-rule='rule protocol value=icmp drop' 2>&1");
|
||||
} else {
|
||||
shell_exec("firewall-cmd --permanent --add-rich-rule='rule protocol value=icmp drop' 2>&1");
|
||||
}
|
||||
shell_exec("firewall-cmd --reload");
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取防火墙规则
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getFirewallRules(): JsonResponse
|
||||
{
|
||||
$firewallRules = trim(shell_exec("firewall-cmd --list-all 2>&1"));
|
||||
// 判断是否开启
|
||||
if (str_contains($firewallRules, 'not running')) {
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = [];
|
||||
return response()->json($res);
|
||||
}
|
||||
// 正则匹配出ports
|
||||
preg_match('/ports: (.*)/', $firewallRules, $matches);
|
||||
$rawPorts = $matches[1];
|
||||
$ports = explode(' ', $rawPorts);
|
||||
// 对ports进行分割为port=>protocol形式
|
||||
$rules = [];
|
||||
foreach ($ports as $port) {
|
||||
$rule = explode('/', $port);
|
||||
$rules[] = [
|
||||
'port' => $rule[0],
|
||||
'protocol' => $rule[1],
|
||||
];
|
||||
}
|
||||
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = $rules;
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加防火墙规则
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function addFirewallRule(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$input = $this->validate($request, [
|
||||
'port' => ['required','regex:/^([0-9]+)(-([0-9]+))?$/'],
|
||||
'protocol' => 'required|in:tcp,udp',
|
||||
]);
|
||||
$port = $input['port'];
|
||||
$protocol = $input['protocol'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '参数错误:'.$e->getMessage(),
|
||||
'errors' => $e->errors()
|
||||
], 200);
|
||||
}
|
||||
// 判断是否开启
|
||||
$firewallStatus = trim(shell_exec("firewall-cmd --state 2>&1"));
|
||||
if ($firewallStatus != 'running') {
|
||||
$res['code'] = 1;
|
||||
$res['msg'] = '防火墙未开启';
|
||||
return response()->json($res);
|
||||
}
|
||||
// 清空当前规则
|
||||
shell_exec("firewall-cmd --remove-port=".$port."/".$protocol." --permanent");
|
||||
// 添加新的防火墙规则
|
||||
shell_exec("firewall-cmd --add-port=".$port."/".$protocol." --permanent");
|
||||
// 重启防火墙
|
||||
shell_exec("firewall-cmd --reload");
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除防火墙规则
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function deleteFirewallRule(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒
|
||||
try {
|
||||
$input = $this->validate($request, [
|
||||
'port' => ['required','regex:/^([0-9]+)(-([0-9]+))?$/'],
|
||||
'protocol' => 'required|in:tcp,udp',
|
||||
]);
|
||||
$port = $input['port'];
|
||||
$protocol = $input['protocol'];
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '参数错误:'.$e->getMessage(),
|
||||
'errors' => $e->errors()
|
||||
], 200);
|
||||
}
|
||||
// 判断是否开启
|
||||
$firewallStatus = trim(shell_exec("firewall-cmd --state 2>&1"));
|
||||
if ($firewallStatus != 'running') {
|
||||
$res['code'] = 1;
|
||||
$res['msg'] = '防火墙未开启';
|
||||
return response()->json($res);
|
||||
}
|
||||
// 清空当前规则
|
||||
shell_exec("firewall-cmd --remove-port=".$port."/".$protocol." --permanent");
|
||||
// 重启防火墙
|
||||
shell_exec("firewall-cmd --reload");
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子Linux面板 - 设置控制器
|
||||
* @author 耗子
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取面板设置
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function get(Request $request): JsonResponse
|
||||
{
|
||||
$settings = Setting::all();
|
||||
// 隐藏字段
|
||||
$settings->makeHidden('id');
|
||||
$settings->makeHidden('created_at');
|
||||
$settings->makeHidden('updated_at');
|
||||
$settingArr = $settings->pluck('value', 'name');
|
||||
|
||||
// 从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) {
|
||||
$api = 1;
|
||||
$apiToken = $settingArr['api_token'] ?? '';
|
||||
}
|
||||
|
||||
// 多设备登录
|
||||
$multiLogin = 0;
|
||||
if (isset($settingArr['multi_login']) && $settingArr['multi_login'] == 1) {
|
||||
$multiLogin = 1;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $settingArr['name'],
|
||||
'username' => $request->user()->username,
|
||||
'email' => $request->user()->email,
|
||||
'password' => '',
|
||||
'port' => $matches[1],
|
||||
'api' => $api,
|
||||
'api_token' => $apiToken,
|
||||
'multi_login' => $multiLogin,
|
||||
];
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = $data;
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存面板设置
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function save(Request $request): JsonResponse
|
||||
{
|
||||
// 获取前端传递过来的数据
|
||||
$settings = $request->all();
|
||||
// 将数据入库
|
||||
foreach ($settings as $key => $value) {
|
||||
if ($key == 'access_token' || $key == 'username' || $key == 'email' || $key == 'password' || $key == 'api_token' || $key == 'api' || $key == 'port') {
|
||||
continue;
|
||||
}
|
||||
// 创建或更新
|
||||
Setting::query()->updateOrCreate(['name' => $key], ['value' => $value]);
|
||||
}
|
||||
// 单独处理用户名、密码、邮箱
|
||||
if ($request->input('username') != $request->user()->username) {
|
||||
$request->user()->update(['username' => $request->input('username')]);
|
||||
}
|
||||
if ($request->input('password') != '') {
|
||||
$request->user()->update(['password' => Hash::make($request->input('password'))]);
|
||||
}
|
||||
if ($request->input('email') != $request->user()->email) {
|
||||
$request->user()->update(['email' => $request->input('email')]);
|
||||
}
|
||||
// 处理面板端口
|
||||
$port = $request->input('port');
|
||||
$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);
|
||||
}
|
||||
if ($port != $matches[1]) {
|
||||
$nginxConf = preg_replace('/listen\s+'.$matches[1].'/', 'listen '.$port, $nginxConf, 1);
|
||||
file_put_contents('/www/server/nginx/conf/nginx.conf', $nginxConf);
|
||||
// 重载nginx
|
||||
shell_exec('systemctl reload nginx');
|
||||
// 防火墙放行端口
|
||||
$port = escapeshellarg($port);
|
||||
shell_exec('firewall-cmd --permanent --zone=public --add-port='.$port.'/tcp >/dev/null 2>&1');
|
||||
shell_exec('firewall-cmd --reload');
|
||||
}
|
||||
// 处理api
|
||||
$api = $request->input('api', 0);
|
||||
$apiCheck = Setting::query()->where('name', 'api')->value('value');
|
||||
if (empty($apiCheck)) {
|
||||
$apiCheck = 0;
|
||||
}
|
||||
if ($api != $apiCheck) {
|
||||
if ($api) {
|
||||
Setting::query()->insert([
|
||||
'name' => 'api',
|
||||
'value' => 1,
|
||||
]);
|
||||
// 生成api用户
|
||||
$username = 'api_'.Str::random();
|
||||
$apiUser = User::query()->create([
|
||||
'username' => $username,
|
||||
'password' => Hash::make(Str::random()),
|
||||
'email' => 'panel_api@haozi.net',
|
||||
]);
|
||||
// 生成api token
|
||||
$apiToken = $apiUser->createToken('api')->plainTextToken;
|
||||
Setting::query()->insert([
|
||||
'name' => 'api_user',
|
||||
'value' => $username,
|
||||
]);
|
||||
Setting::query()->insert([
|
||||
'name' => 'api_token',
|
||||
'value' => $apiToken,
|
||||
]);
|
||||
} else {
|
||||
Setting::query()->where('name', 'api')->delete();
|
||||
Setting::query()->where('name', 'api_token')->delete();
|
||||
$username = Setting::query()->where('name', 'api_user')->value('value');
|
||||
Setting::query()->where('name', 'api_user')->delete();
|
||||
Setting::query()->where('name', 'api')->delete();
|
||||
$apiUser = User::query()->where('username', $username)->first();
|
||||
// 删除api用户的所有token
|
||||
$apiUser->tokens()->delete();
|
||||
// 删除api用户
|
||||
$apiUser->delete();
|
||||
}
|
||||
}
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子Linux面板 - 任务控制器
|
||||
* @author 耗子
|
||||
*/
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TasksController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取是否有进行中/未完成的任务
|
||||
* 前台会根据这个返回渲染顶部工具栏的图标
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getStatus(): JsonResponse
|
||||
{
|
||||
// 获取任务表中的等待中/进行中的任务
|
||||
$task = Task::query()->where('status', 'running')->orWhere('status', 'waiting')->first();
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
if (empty($task)) {
|
||||
$res['data'] = false;
|
||||
} else {
|
||||
$res['data'] = true;
|
||||
}
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进行中的那个任务
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getListRunning(): JsonResponse
|
||||
{
|
||||
// 获取进行中的任务列表
|
||||
$task = Task::query()->where('status', 'running')->get()->toArray();
|
||||
|
||||
// 判断任务是否存在
|
||||
if (empty($task)) {
|
||||
$task[0] = "";
|
||||
}
|
||||
// 构建返回数据
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = $task[0];
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取等待中的任务列表
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getListWaiting(): JsonResponse
|
||||
{
|
||||
$task_list = Task::query()->where('status', 'waiting')->get()->toArray();
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
// 判断任务是否存在
|
||||
if (empty($task_list)) {
|
||||
$res['code'] = 1;
|
||||
$res['msg'] = '无任务!';
|
||||
}
|
||||
$res['data'] = $task_list;
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已完成的任务列表
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getListFinished(): JsonResponse
|
||||
{
|
||||
$task_list = Task::query()->where('status', 'finished')->get()->toArray();
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
if (empty($task_list)) {
|
||||
$res['code'] = 1;
|
||||
$res['msg'] = '无任务!';
|
||||
}
|
||||
$res['data'] = $task_list;
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个任务的log
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function getTaskLog(Request $request): JsonResponse
|
||||
{
|
||||
$name = $request->get('name');
|
||||
$log_file = Task::query()->where('name', $name)->value('log');
|
||||
|
||||
$log = shell_exec('tail -n 30 '.$log_file);
|
||||
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = $log;
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function deleteTask(Request $request): JsonResponse
|
||||
{
|
||||
$name = $request->get('name');
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data'] = Task::query()->where('name', $name)->delete();
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子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;
|
||||
|
||||
class UsersController extends Controller
|
||||
{
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function login(Request $request): JsonResponse
|
||||
{
|
||||
// 消毒数据
|
||||
try {
|
||||
$credentials = $this->validate($request, [
|
||||
'username' => 'required|max:255',
|
||||
'password' => 'required|max:255',
|
||||
'remember' => 'required|boolean'
|
||||
]);
|
||||
} catch (ValidationException $e) {
|
||||
return response()->json([
|
||||
'code' => 1,
|
||||
'msg' => '参数错误:'.$e->getMessage(),
|
||||
'errors' => $e->errors()
|
||||
], 422);
|
||||
}
|
||||
if (auth()->attempt(['username' => $credentials['username'], 'password' => $credentials['password']],
|
||||
$credentials['remember'])) {
|
||||
$user = auth()->user();
|
||||
// 多设备登录
|
||||
$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();
|
||||
$res['code'] = 0;
|
||||
$res['msg'] = 'success';
|
||||
$res['data']['username'] = $user->username;
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array<int, class-string|string>
|
||||
*/
|
||||
protected $middleware = [
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\AddAccessToken::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array<string, array<int, class-string|string>>
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected $routeMiddleware = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
|
||||
class AddAccessToken
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*/
|
||||
public function handle($request, Closure $next, ...$guards)
|
||||
{
|
||||
// 获取请求头中的token
|
||||
$token = $request->header('access_token') ?? $request->input('access_token');
|
||||
// 将token放入请求中
|
||||
$request->headers->set('Authorization', 'Bearer '.$token);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class Authenticate extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function redirectTo($request)
|
||||
{
|
||||
abort(response()->json([
|
||||
'code' => 1001,
|
||||
'msg' => '登录状态失效'
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
|
||||
|
||||
class PreventRequestsDuringMaintenance extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be reachable while maintenance mode is enabled.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @param string|null ...$guards
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, ...$guards)
|
||||
{
|
||||
$guards = empty($guards) ? [null] : $guards;
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||
|
||||
class TrustHosts extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
public function hosts()
|
||||
{
|
||||
return [
|
||||
$this->allSubdomainsOfApplicationUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array<int, string>|string|null
|
||||
*/
|
||||
protected $proxies;
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||
|
||||
class ValidateSignature extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
// 'fbclid',
|
||||
// 'utm_campaign',
|
||||
// 'utm_content',
|
||||
// 'utm_medium',
|
||||
// 'utm_source',
|
||||
// 'utm_term',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||
|
||||
class VerifyCsrfToken extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Models\Task;
|
||||
|
||||
class ProcessShell implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* 任务最大尝试次数
|
||||
* @var int
|
||||
*/
|
||||
public int $tries = 1;
|
||||
/**
|
||||
* 任务运行的超时时间
|
||||
* @var int
|
||||
*/
|
||||
public int $timeout = 7200;
|
||||
|
||||
/**
|
||||
* 任务名
|
||||
*/
|
||||
public string $task_id;
|
||||
|
||||
/**
|
||||
* 任务脚本
|
||||
*/
|
||||
public string $shell;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($task_id)
|
||||
{
|
||||
$this->task_id = $task_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
// 检查当前是否有任务正在运行
|
||||
$taskCheck = Task::query()->where('status', 'running')->get();
|
||||
if ($taskCheck->isNotEmpty()) {
|
||||
$this->release(10);
|
||||
return;
|
||||
}
|
||||
// 查询任务
|
||||
$task = Task::query()->where('id', $this->task_id)->first();
|
||||
echo $task->name."开始执行".PHP_EOL;
|
||||
// 更新任务状态为running
|
||||
$task->job_id = $this->job->getJobId();
|
||||
$task->status = 'running';
|
||||
$task->save();
|
||||
shell_exec($task->shell);
|
||||
// 更新任务状态
|
||||
$task->status = 'finished';
|
||||
$task->save();
|
||||
echo $task->name."执行完毕".PHP_EOL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Cron extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// 白名单
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'status',
|
||||
'type',
|
||||
'time',
|
||||
'shell',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Database extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Monitor extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// 白名单
|
||||
protected $fillable = [
|
||||
'info',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Plugin extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// 白名单
|
||||
protected $fillable = ['slug', 'name', 'version', 'show', 'created_at', 'updated_at'];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Setting extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// 白名单
|
||||
protected $fillable = ['name', 'value', 'created_at', 'updated_at'];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Task extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// 白名单
|
||||
protected $fillable = [
|
||||
'job_id',
|
||||
'name',
|
||||
'shell',
|
||||
'log',
|
||||
'status',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'username',
|
||||
'password',
|
||||
'email'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Website extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// 白名单
|
||||
protected $fillable = ['name', 'status', 'path', 'php', 'ssl', 'ssl_date', 'note', 'created_at', 'updated_at'];
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
// use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The model to policy mappings for the application.
|
||||
*
|
||||
* @var array<class-string, class-string>
|
||||
*/
|
||||
protected $policies = [
|
||||
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any authentication / authorization services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class BroadcastServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Broadcast::routes();
|
||||
|
||||
require base_path('routes/channels.php');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event to listener mappings for the application.
|
||||
*
|
||||
* @var array<class-string, array<int, class-string>>
|
||||
*/
|
||||
protected $listen = [
|
||||
Registered::class => [
|
||||
SendEmailVerificationNotification::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if events and listeners should be automatically discovered.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldDiscoverEvents()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use App\Services\Plugin;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class PluginServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @param Plugin $plugins
|
||||
* @return void
|
||||
* @throws BindingResolutionException
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public function boot(Plugin $plugins): void
|
||||
{
|
||||
|
||||
$loader = $this->app->make('translation.loader');
|
||||
// Make view instead of view.finder since the finder is defined as not a singleton
|
||||
$finder = $this->app->make('view');
|
||||
|
||||
foreach ($plugins->getPlugins() as $plugin) {
|
||||
|
||||
// 加载视图路径
|
||||
$finder->addNamespace($plugin['slug'], $plugin['path']."/views");
|
||||
|
||||
// 加载语言包
|
||||
$loader->addNamespace($plugin['slug'], $plugin['path']."/lang");
|
||||
}
|
||||
|
||||
// 加载插件Composer装载文件
|
||||
try {
|
||||
foreach ($plugins->getComposerLoaders() as $autoloader) {
|
||||
require $autoloader;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new BindingResolutionException($e->getMessage());
|
||||
}
|
||||
|
||||
// 加载插件主文件
|
||||
try {
|
||||
foreach ($plugins->getPluginMainFiles() as $file) {
|
||||
require_once $file;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new BindingResolutionException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册插件服务
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton('plugins', Plugin::class);
|
||||
// 设置面板名称
|
||||
$name = @DB::table('settings')->where('name', 'name')->value('value');
|
||||
$this->app['config']['panel.name'] = !empty($name) ? $name : config('panel.name');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The path to the "home" route for your application.
|
||||
*
|
||||
* Typically, users are redirected here after authentication.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const HOME = '/';
|
||||
|
||||
/**
|
||||
* Define your route model bindings, pattern filters, and other route configuration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->configureRateLimiting();
|
||||
|
||||
$this->routes(function () {
|
||||
Route::middleware('api')
|
||||
->prefix('api')
|
||||
->group(base_path('routes/api.php'));
|
||||
|
||||
Route::middleware('web')
|
||||
->group(base_path('routes/web.php'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the rate limiters for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configureRateLimiting()
|
||||
{
|
||||
RateLimiter::for('api', function (Request $request) {
|
||||
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
|
||||
class Plugin
|
||||
{
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected Application $app;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected Dispatcher $dispatcher;
|
||||
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
protected Filesystem $filesystem;
|
||||
|
||||
/**
|
||||
* @var Collection|null
|
||||
*/
|
||||
protected ?Collection $plugins;
|
||||
|
||||
public function __construct(
|
||||
Application $app,
|
||||
Dispatcher $dispatcher,
|
||||
Filesystem $filesystem,
|
||||
Collection $plugins = new Collection()
|
||||
) {
|
||||
$this->app = $app;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->filesystem = $filesystem;
|
||||
$this->plugins = $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取所有插件
|
||||
*
|
||||
* @return Collection|null
|
||||
* @throws FileNotFoundException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getPlugins(): ?Collection
|
||||
{
|
||||
if ($this->plugins->isEmpty()) {
|
||||
$plugins = new Collection();
|
||||
|
||||
$installed = [];
|
||||
|
||||
try {
|
||||
$resource = opendir($this->getPluginsDir());
|
||||
} catch (Exception $e) {
|
||||
// 输出错误信息
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
|
||||
// 读取插件目录
|
||||
while ($filename = @readdir($resource)) {
|
||||
if ($filename == '.' || $filename == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $this->getPluginsDir().DIRECTORY_SEPARATOR.$filename;
|
||||
|
||||
if (is_dir($path)) {
|
||||
$pluginJsonPath = $path.DIRECTORY_SEPARATOR.'plugin.json';
|
||||
|
||||
if (file_exists($pluginJsonPath)) {
|
||||
// 读取插件配置文件
|
||||
$installed[$filename] = json_decode($this->filesystem->get($pluginJsonPath), true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
closedir($resource);
|
||||
|
||||
foreach ($installed as $dirname => $package) {
|
||||
|
||||
// 初始化插件信息
|
||||
$plugin = [];
|
||||
$plugin['name'] = (Arr::get($package, 'name'));
|
||||
$plugin['author'] = (Arr::get($package, 'author'));
|
||||
$plugin['describe'] = (Arr::get($package, 'describe'));
|
||||
$plugin['slug'] = (Arr::get($package, 'slug'));
|
||||
$plugin['version'] = (Arr::get($package, 'version'));
|
||||
$plugin['path'] = $this->getPluginsDir().DIRECTORY_SEPARATOR.$dirname;
|
||||
|
||||
if ($plugins->has($plugin['slug'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugins->put($plugin['slug'], $plugin);
|
||||
}
|
||||
|
||||
define('PLUGINS', $plugins->toArray());
|
||||
$this->plugins = $plugins;
|
||||
}
|
||||
|
||||
return $this->plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部插件的主文件
|
||||
*
|
||||
* @return Collection
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getPluginMainFiles(): Collection
|
||||
{
|
||||
$pluginMainFiles = new Collection;
|
||||
|
||||
try {
|
||||
foreach ($this->getPlugins() as $plugin) {
|
||||
if ($this->filesystem->exists($file = $plugin['path'].'/plugin.php')) {
|
||||
$pluginMainFiles->push($file);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// 输出错误信息
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
|
||||
return $pluginMainFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部插件的Composer装载文件
|
||||
*
|
||||
* @return Collection
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getComposerLoaders(): Collection
|
||||
{
|
||||
$composerLoaders = new Collection;
|
||||
|
||||
foreach ($this->getPlugins() as $plugin) {
|
||||
if ($this->filesystem->exists($file = $plugin['path'].'/vendor/autoload.php')) {
|
||||
$composerLoaders->push($file);
|
||||
}
|
||||
}
|
||||
|
||||
return $composerLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件目录
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPluginsDir(): string
|
||||
{
|
||||
return config('plugins.directory') ?: base_path('plugins');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* 耗子Linux面板 - 帮助函数
|
||||
* @author 耗子
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取网络统计信息
|
||||
* @return array
|
||||
*/
|
||||
function getNetInfo(): array
|
||||
{
|
||||
$networkRaw = file_get_contents('/proc/net/dev');
|
||||
$networkArr = explode("\n", $networkRaw);
|
||||
foreach ($networkArr as $key => $val) {
|
||||
if ($key < 2) {
|
||||
continue;
|
||||
}
|
||||
$val = str_replace(':', ' ', trim($val));
|
||||
$val = preg_replace("/[ ]+/", " ", $val);
|
||||
$arr = explode(' ', $val);
|
||||
if (!empty($arr[0])) {
|
||||
$arr = array($arr[0], $arr[1], $arr[9]);
|
||||
$allRs[$arr[0].$key] = $arr;
|
||||
}
|
||||
}
|
||||
ksort($allRs);
|
||||
$tx = 0;
|
||||
$rx = 0;
|
||||
foreach ($allRs as $key => $val) {
|
||||
// 排除本地lo
|
||||
if (str_contains($key, 'lo')) {
|
||||
continue;
|
||||
}
|
||||
$tx += $val[2];
|
||||
$rx += $val[1];
|
||||
}
|
||||
$res['tx'] = $tx;
|
||||
$res['rx'] = $rx;
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化bytes
|
||||
* @param $size
|
||||
* @return string
|
||||
*/
|
||||
function formatBytes($size): string
|
||||
{
|
||||
$size = is_numeric($size) ? $size : 0;
|
||||
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
|
||||
for ($i = 0; $size >= 1024 && $i < 4; $i++) {
|
||||
$size /= 1024;
|
||||
}
|
||||
return round($size, 2).$units[$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪字符串
|
||||
* @param $begin
|
||||
* @param $end
|
||||
* @param $str
|
||||
* @return string
|
||||
*/
|
||||
function cut($begin, $end, $str): string
|
||||
{
|
||||
$b = mb_strpos($str, $begin) + mb_strlen($begin);
|
||||
$e = mb_strpos($str, $end) - $b;
|
||||
return mb_substr($str, $b, $e);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register The Auto Loader
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Composer provides a convenient, automatically generated class loader
|
||||
| for our application. We just need to utilize it! We'll require it
|
||||
| into the script here so that we do not have to worry about the
|
||||
| loading of any of our classes manually. It's great to relax.
|
||||
|
|
||||
*/
|
||||
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Run The Artisan Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When we run the console application, the current CLI command will be
|
||||
| executed in this console and the response sent back to a terminal
|
||||
| or another output device for the developers. Here goes nothing!
|
||||
|
|
||||
*/
|
||||
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
|
||||
$status = $kernel->handle(
|
||||
$input = new Symfony\Component\Console\Input\ArgvInput,
|
||||
new Symfony\Component\Console\Output\ConsoleOutput
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Shutdown The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Once Artisan has finished running, we will fire off the shutdown events
|
||||
| so that any final work may be done by the application before we shut
|
||||
| down the process. This is the last thing to happen to the request.
|
||||
|
|
||||
*/
|
||||
|
||||
$kernel->terminate($input, $status);
|
||||
|
||||
exit($status);
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Create The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The first thing we will do is create a new Laravel application instance
|
||||
| which serves as the "glue" for all the components of Laravel, and is
|
||||
| the IoC container for the system binding all of the various parts.
|
||||
|
|
||||
*/
|
||||
|
||||
$app = new Illuminate\Foundation\Application(
|
||||
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bind Important Interfaces
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, we need to bind some important interfaces into the container so
|
||||
| we will be able to resolve them when needed. The kernels serve the
|
||||
| incoming requests to this application from both the web and CLI.
|
||||
|
|
||||
*/
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Http\Kernel::class,
|
||||
App\Http\Kernel::class
|
||||
);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Console\Kernel::class,
|
||||
App\Console\Kernel::class
|
||||
);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
||||
App\Exceptions\Handler::class
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Return The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This script returns the application instance. The instance is given to
|
||||
| the calling script so we can separate the building of the instances
|
||||
| from the actual running of the application and sending responses.
|
||||
|
|
||||
*/
|
||||
|
||||
return $app;
|
||||
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/debug"
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if os.Geteuid() != 0 {
|
||||
panic("panel must run as root")
|
||||
}
|
||||
|
||||
debug.SetGCPercent(10)
|
||||
debug.SetMemoryLimit(256 << 20)
|
||||
|
||||
ace, err := initAce()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = ace.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
//go:build wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/apps"
|
||||
"github.com/acepanel/panel/internal/bootstrap"
|
||||
"github.com/acepanel/panel/internal/data"
|
||||
"github.com/acepanel/panel/internal/http/middleware"
|
||||
"github.com/acepanel/panel/internal/job"
|
||||
"github.com/acepanel/panel/internal/route"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
)
|
||||
|
||||
// initAce init application.
|
||||
func initAce() (*app.Ace, error) {
|
||||
panic(wire.Build(bootstrap.ProviderSet, middleware.ProviderSet, route.ProviderSet, service.ProviderSet, data.ProviderSet, apps.ProviderSet, job.ProviderSet, app.NewAce))
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
|
||||
//go:build !wireinject
|
||||
// +build !wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/apps/apache"
|
||||
"github.com/acepanel/panel/internal/apps/codeserver"
|
||||
"github.com/acepanel/panel/internal/apps/docker"
|
||||
"github.com/acepanel/panel/internal/apps/fail2ban"
|
||||
"github.com/acepanel/panel/internal/apps/frp"
|
||||
"github.com/acepanel/panel/internal/apps/gitea"
|
||||
"github.com/acepanel/panel/internal/apps/mariadb"
|
||||
"github.com/acepanel/panel/internal/apps/memcached"
|
||||
"github.com/acepanel/panel/internal/apps/minio"
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
"github.com/acepanel/panel/internal/apps/openresty"
|
||||
"github.com/acepanel/panel/internal/apps/percona"
|
||||
"github.com/acepanel/panel/internal/apps/phpmyadmin"
|
||||
"github.com/acepanel/panel/internal/apps/podman"
|
||||
"github.com/acepanel/panel/internal/apps/postgresql"
|
||||
"github.com/acepanel/panel/internal/apps/pureftpd"
|
||||
"github.com/acepanel/panel/internal/apps/redis"
|
||||
"github.com/acepanel/panel/internal/apps/rsync"
|
||||
"github.com/acepanel/panel/internal/apps/s3fs"
|
||||
"github.com/acepanel/panel/internal/apps/supervisor"
|
||||
"github.com/acepanel/panel/internal/bootstrap"
|
||||
"github.com/acepanel/panel/internal/data"
|
||||
"github.com/acepanel/panel/internal/http/middleware"
|
||||
"github.com/acepanel/panel/internal/job"
|
||||
"github.com/acepanel/panel/internal/route"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
)
|
||||
|
||||
import (
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
// Injectors from wire.go:
|
||||
|
||||
// initAce init application.
|
||||
func initAce() (*app.Ace, error) {
|
||||
config, err := bootstrap.NewConf()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locale, err := bootstrap.NewT(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db, err := bootstrap.NewDB(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manager, err := bootstrap.NewSession(config, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := bootstrap.NewLog(config)
|
||||
cacheRepo := data.NewCacheRepo(db)
|
||||
queue := bootstrap.NewQueue()
|
||||
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
|
||||
appRepo := data.NewAppRepo(locale, config, db, logger, cacheRepo, taskRepo)
|
||||
userTokenRepo := data.NewUserTokenRepo(locale, config, db)
|
||||
middlewares := middleware.NewMiddlewares(config, manager, appRepo, userTokenRepo)
|
||||
userRepo := data.NewUserRepo(locale, db, logger)
|
||||
userService := service.NewUserService(locale, config, manager, userRepo)
|
||||
userTokenService := service.NewUserTokenService(locale, userTokenRepo)
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, logger, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(locale, db, logger, databaseServerRepo, databaseUserRepo)
|
||||
settingRepo := data.NewSettingRepo(locale, db, logger, config, taskRepo)
|
||||
certRepo := data.NewCertRepo(locale, db, logger, settingRepo)
|
||||
certAccountRepo := data.NewCertAccountRepo(locale, db, userRepo, logger)
|
||||
websiteRepo := data.NewWebsiteRepo(locale, db, logger, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo, settingRepo)
|
||||
projectRepo := data.NewProjectRepo(locale, db, logger)
|
||||
environmentRepo := data.NewEnvironmentRepo(locale, config, cacheRepo, taskRepo)
|
||||
cronRepo := data.NewCronRepo(locale, db, logger)
|
||||
backupRepo := data.NewBackupRepo(locale, config, db, logger, settingRepo, websiteRepo)
|
||||
homeService := service.NewHomeService(locale, config, taskRepo, websiteRepo, projectRepo, appRepo, environmentRepo, settingRepo, cronRepo, backupRepo)
|
||||
taskService := service.NewTaskService(taskRepo)
|
||||
websiteService := service.NewWebsiteService(websiteRepo, settingRepo)
|
||||
projectService := service.NewProjectService(projectRepo, settingRepo)
|
||||
databaseService := service.NewDatabaseService(databaseRepo)
|
||||
databaseServerService := service.NewDatabaseServerService(databaseServerRepo)
|
||||
databaseUserService := service.NewDatabaseUserService(databaseUserRepo)
|
||||
backupService := service.NewBackupService(locale, backupRepo)
|
||||
backupAccountRepo := data.NewBackupAccountRepo(locale, db, logger, settingRepo)
|
||||
backupStorageService := service.NewBackupStorageService(locale, backupAccountRepo)
|
||||
certService := service.NewCertService(locale, certRepo)
|
||||
certDNSRepo := data.NewCertDNSRepo(db, logger)
|
||||
certDNSService := service.NewCertDNSService(certDNSRepo)
|
||||
certAccountService := service.NewCertAccountService(certAccountRepo)
|
||||
appService := service.NewAppService(locale, appRepo, cacheRepo, settingRepo)
|
||||
environmentService := service.NewEnvironmentService(locale, environmentRepo, taskRepo)
|
||||
environmentGoService := service.NewEnvironmentGoService(locale, environmentRepo)
|
||||
environmentJavaService := service.NewEnvironmentJavaService(locale, environmentRepo)
|
||||
environmentNodejsService := service.NewEnvironmentNodejsService(locale, environmentRepo)
|
||||
environmentPHPService := service.NewEnvironmentPHPService(locale, config, environmentRepo, taskRepo)
|
||||
environmentPythonService := service.NewEnvironmentPythonService(locale, environmentRepo)
|
||||
cronService := service.NewCronService(cronRepo)
|
||||
processService := service.NewProcessService()
|
||||
safeRepo := data.NewSafeRepo(logger)
|
||||
safeService := service.NewSafeService(safeRepo)
|
||||
firewallService := service.NewFirewallService()
|
||||
sshRepo := data.NewSSHRepo(locale, db, logger)
|
||||
sshService := service.NewSSHService(sshRepo)
|
||||
containerRepo := data.NewContainerRepo()
|
||||
containerService := service.NewContainerService(containerRepo)
|
||||
containerComposeRepo := data.NewContainerComposeRepo()
|
||||
containerComposeService := service.NewContainerComposeService(containerComposeRepo)
|
||||
containerNetworkRepo := data.NewContainerNetworkRepo()
|
||||
containerNetworkService := service.NewContainerNetworkService(containerNetworkRepo)
|
||||
containerImageRepo := data.NewContainerImageRepo()
|
||||
containerImageService := service.NewContainerImageService(containerImageRepo)
|
||||
containerVolumeRepo := data.NewContainerVolumeRepo()
|
||||
containerVolumeService := service.NewContainerVolumeService(containerVolumeRepo)
|
||||
fileService := service.NewFileService(locale, taskRepo)
|
||||
logRepo := data.NewLogRepo(db)
|
||||
logService := service.NewLogService(logRepo)
|
||||
monitorRepo := data.NewMonitorRepo(db, settingRepo)
|
||||
monitorService := service.NewMonitorService(settingRepo, monitorRepo)
|
||||
settingService := service.NewSettingService(locale, db, settingRepo, certRepo, certAccountRepo)
|
||||
systemctlService := service.NewSystemctlService(locale)
|
||||
toolboxSystemService := service.NewToolboxSystemService(locale)
|
||||
toolboxBenchmarkService := service.NewToolboxBenchmarkService(locale)
|
||||
toolboxSSHService := service.NewToolboxSSHService(locale)
|
||||
toolboxDiskService := service.NewToolboxDiskService(locale)
|
||||
toolboxLogService := service.NewToolboxLogService(locale, db, containerImageRepo, settingRepo)
|
||||
webHookRepo := data.NewWebHookRepo(locale, db, logger)
|
||||
webHookService := service.NewWebHookService(webHookRepo)
|
||||
templateRepo := data.NewTemplateRepo(locale, cacheRepo)
|
||||
templateService := service.NewTemplateService(locale, templateRepo, settingRepo)
|
||||
apacheApp := apache.NewApp(locale)
|
||||
codeserverApp := codeserver.NewApp()
|
||||
dockerApp := docker.NewApp()
|
||||
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
mariadbApp := mariadb.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
memcachedApp := memcached.NewApp(locale)
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
nginxApp := nginx.NewApp(locale)
|
||||
openrestyApp := openresty.NewApp(locale)
|
||||
perconaApp := percona.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp(locale)
|
||||
podmanApp := podman.NewApp()
|
||||
postgresqlApp := postgresql.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
pureftpdApp := pureftpd.NewApp(locale)
|
||||
redisApp := redis.NewApp(locale)
|
||||
rsyncApp := rsync.NewApp(locale)
|
||||
s3fsApp := s3fs.NewApp(locale)
|
||||
supervisorApp := supervisor.NewApp(locale)
|
||||
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
|
||||
http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseUserService, backupService, backupStorageService, certService, certDNSService, certAccountService, appService, environmentService, environmentGoService, environmentJavaService, environmentNodejsService, environmentPHPService, environmentPythonService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, webHookService, templateService, loader)
|
||||
wsService := service.NewWsService(locale, config, logger, sshRepo)
|
||||
ws := route.NewWs(wsService)
|
||||
mux, err := bootstrap.NewRouter(locale, middlewares, http, ws)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server, err := bootstrap.NewHttp(config, mux)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gormigrate := bootstrap.NewMigrate(db)
|
||||
jobs := job.NewJobs(config, db, logger, settingRepo, certRepo, certAccountRepo, backupRepo, cacheRepo, taskRepo)
|
||||
cron, err := bootstrap.NewCron(config, logger, jobs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validation := bootstrap.NewValidator(config, db)
|
||||
ace := app.NewAce(config, mux, server, gormigrate, cron, queue, validation)
|
||||
return ace, nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if os.Geteuid() != 0 {
|
||||
panic("panel must run as root")
|
||||
}
|
||||
|
||||
cli, err := initCli()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = cli.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
//go:build wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/apps"
|
||||
"github.com/acepanel/panel/internal/bootstrap"
|
||||
"github.com/acepanel/panel/internal/data"
|
||||
"github.com/acepanel/panel/internal/route"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
)
|
||||
|
||||
// initCli init command line.
|
||||
func initCli() (*app.Cli, error) {
|
||||
panic(wire.Build(bootstrap.ProviderSet, route.ProviderSet, service.ProviderSet, data.ProviderSet, apps.ProviderSet, app.NewCli))
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
|
||||
//go:build !wireinject
|
||||
// +build !wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/apps/apache"
|
||||
"github.com/acepanel/panel/internal/apps/codeserver"
|
||||
"github.com/acepanel/panel/internal/apps/docker"
|
||||
"github.com/acepanel/panel/internal/apps/fail2ban"
|
||||
"github.com/acepanel/panel/internal/apps/frp"
|
||||
"github.com/acepanel/panel/internal/apps/gitea"
|
||||
"github.com/acepanel/panel/internal/apps/mariadb"
|
||||
"github.com/acepanel/panel/internal/apps/memcached"
|
||||
"github.com/acepanel/panel/internal/apps/minio"
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
"github.com/acepanel/panel/internal/apps/openresty"
|
||||
"github.com/acepanel/panel/internal/apps/percona"
|
||||
"github.com/acepanel/panel/internal/apps/phpmyadmin"
|
||||
"github.com/acepanel/panel/internal/apps/podman"
|
||||
"github.com/acepanel/panel/internal/apps/postgresql"
|
||||
"github.com/acepanel/panel/internal/apps/pureftpd"
|
||||
"github.com/acepanel/panel/internal/apps/redis"
|
||||
"github.com/acepanel/panel/internal/apps/rsync"
|
||||
"github.com/acepanel/panel/internal/apps/s3fs"
|
||||
"github.com/acepanel/panel/internal/apps/supervisor"
|
||||
"github.com/acepanel/panel/internal/bootstrap"
|
||||
"github.com/acepanel/panel/internal/data"
|
||||
"github.com/acepanel/panel/internal/route"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
)
|
||||
|
||||
import (
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
// Injectors from wire.go:
|
||||
|
||||
// initCli init command line.
|
||||
func initCli() (*app.Cli, error) {
|
||||
config, err := bootstrap.NewConf()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locale, err := bootstrap.NewT(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db, err := bootstrap.NewDB(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := bootstrap.NewLog(config)
|
||||
cacheRepo := data.NewCacheRepo(db)
|
||||
queue := bootstrap.NewQueue()
|
||||
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
|
||||
appRepo := data.NewAppRepo(locale, config, db, logger, cacheRepo, taskRepo)
|
||||
userRepo := data.NewUserRepo(locale, db, logger)
|
||||
settingRepo := data.NewSettingRepo(locale, db, logger, config, taskRepo)
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, logger, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(locale, db, logger, databaseServerRepo, databaseUserRepo)
|
||||
certRepo := data.NewCertRepo(locale, db, logger, settingRepo)
|
||||
certAccountRepo := data.NewCertAccountRepo(locale, db, userRepo, logger)
|
||||
websiteRepo := data.NewWebsiteRepo(locale, db, logger, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo, settingRepo)
|
||||
backupRepo := data.NewBackupRepo(locale, config, db, logger, settingRepo, websiteRepo)
|
||||
cliService := service.NewCliService(locale, config, db, appRepo, cacheRepo, userRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo, certRepo, certAccountRepo)
|
||||
cli := route.NewCli(locale, cliService)
|
||||
command := bootstrap.NewCli(locale, cli)
|
||||
gormigrate := bootstrap.NewMigrate(db)
|
||||
apacheApp := apache.NewApp(locale)
|
||||
codeserverApp := codeserver.NewApp()
|
||||
dockerApp := docker.NewApp()
|
||||
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
mariadbApp := mariadb.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
memcachedApp := memcached.NewApp(locale)
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
nginxApp := nginx.NewApp(locale)
|
||||
openrestyApp := openresty.NewApp(locale)
|
||||
perconaApp := percona.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp(locale)
|
||||
podmanApp := podman.NewApp()
|
||||
postgresqlApp := postgresql.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
pureftpdApp := pureftpd.NewApp(locale)
|
||||
redisApp := redis.NewApp(locale)
|
||||
rsyncApp := rsync.NewApp(locale)
|
||||
s3fsApp := s3fs.NewApp(locale)
|
||||
supervisorApp := supervisor.NewApp(locale)
|
||||
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
|
||||
appCli := app.NewCli(command, gormigrate, loader)
|
||||
return appCli, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The Laravel Framework.",
|
||||
"keywords": ["framework", "laravel"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.1.9",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"laravel/framework": "^9.19",
|
||||
"laravel/sanctum": "^3.0",
|
||||
"laravel/tinker": "^2.7",
|
||||
"overtrue/laravel-lang": "^6.0",
|
||||
"skoerfgen/acmecert": "^3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel/pint": "^1.0",
|
||||
"laravel/sail": "^1.0.1",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^6.1",
|
||||
"phpunit/phpunit": "^9.5.10",
|
||||
"spatie/laravel-ignition": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
},
|
||||
"files": [
|
||||
"app/helpers.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
],
|
||||
"post-create-project-cmd": [
|
||||
"@php artisan key:generate --ansi"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
app:
|
||||
debug: false
|
||||
key: a-long-string-with-32-characters
|
||||
locale: zh_CN
|
||||
timezone: Asia/Shanghai
|
||||
root: /opt/ace
|
||||
api_endpoint: api.acepanel.net
|
||||
download_endpoint: dl.acepanel.net
|
||||
http:
|
||||
debug: false
|
||||
port: 8888
|
||||
entrance: /
|
||||
entrance_error: "418"
|
||||
tls: true
|
||||
acme: true
|
||||
login_captcha: true
|
||||
ip_header: ""
|
||||
bind_domain: [ ]
|
||||
bind_ip: [ ]
|
||||
bind_ua: [ ]
|
||||
database:
|
||||
debug: false
|
||||
session:
|
||||
lifetime: 120
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the name of your application. This value is used when the
|
||||
| framework needs to place the application's name in a notification or
|
||||
| any other location as required by the application or its packages.
|
||||
|
|
||||
*/
|
||||
|
||||
'name' => '耗子Linux面板',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Environment
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines the "environment" your application is currently
|
||||
| running in. This may determine how you prefer to configure various
|
||||
| services the application utilizes. Set this in your ".env" file.
|
||||
|
|
||||
*/
|
||||
|
||||
'env' => 'local',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Debug Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When your application is in debug mode, detailed error messages with
|
||||
| stack traces will be shown on every error that occurs within your
|
||||
| application. If disabled, a simple generic error page is shown.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This URL is used by the console to properly generate URLs when using
|
||||
| the Artisan command line tool. You should set this to the root of
|
||||
| your application so that it is used when running Artisan tasks.
|
||||
|
|
||||
*/
|
||||
|
||||
'url' => '.',
|
||||
|
||||
'asset_url' => '.',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default timezone for your application, which
|
||||
| will be used by the PHP date and date-time functions. We have gone
|
||||
| ahead and set this to a sensible default for you out of the box.
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'PRC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Locale Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The application locale determines the default locale that will be used
|
||||
| by the translation service provider. You are free to set this value
|
||||
| to any of the locales which will be supported by the application.
|
||||
|
|
||||
*/
|
||||
|
||||
'locale' => 'zh_CN',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Fallback Locale
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The fallback locale determines the locale to use when the current one
|
||||
| is not available. You may change the value to correspond to any of
|
||||
| the language folders that are provided through your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'fallback_locale' => 'zh_CN',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Faker Locale
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This locale will be used by the Faker PHP library when generating fake
|
||||
| data for your database seeds. For example, this will be used to get
|
||||
| localized telephone numbers, street address information and more.
|
||||
|
|
||||
*/
|
||||
|
||||
'faker_locale' => 'zh_CN',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Encryption Key
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This key is used by the Illuminate encrypter service and should be set
|
||||
| to a random, 32 character string, otherwise these encrypted strings
|
||||
| will not be safe. Please do this before deploying an application!
|
||||
|
|
||||
*/
|
||||
|
||||
'key' => 'ctrlniganmaaiyouniganmaaiyouctrl',
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Maintenance Mode Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These configuration options determine the driver used to determine and
|
||||
| manage Laravel's "maintenance mode" status. The "cache" driver will
|
||||
| allow maintenance mode to be controlled across multiple machines.
|
||||
|
|
||||
| Supported drivers: "file", "cache"
|
||||
|
|
||||
*/
|
||||
|
||||
'maintenance' => [
|
||||
'driver' => 'file',
|
||||
// 'store' => 'redis',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Autoloaded Service Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The service providers listed here will be automatically loaded on the
|
||||
| request to your application. Feel free to add your own services to
|
||||
| this array to grant expanded functionality to your applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
|
||||
/*
|
||||
* Laravel Framework Service Providers...
|
||||
*/
|
||||
Illuminate\Auth\AuthServiceProvider::class,
|
||||
Illuminate\Broadcasting\BroadcastServiceProvider::class,
|
||||
Illuminate\Bus\BusServiceProvider::class,
|
||||
Illuminate\Cache\CacheServiceProvider::class,
|
||||
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
|
||||
Illuminate\Cookie\CookieServiceProvider::class,
|
||||
Illuminate\Database\DatabaseServiceProvider::class,
|
||||
Illuminate\Encryption\EncryptionServiceProvider::class,
|
||||
Illuminate\Filesystem\FilesystemServiceProvider::class,
|
||||
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
|
||||
Illuminate\Hashing\HashServiceProvider::class,
|
||||
Illuminate\Mail\MailServiceProvider::class,
|
||||
Illuminate\Notifications\NotificationServiceProvider::class,
|
||||
Illuminate\Pagination\PaginationServiceProvider::class,
|
||||
Illuminate\Pipeline\PipelineServiceProvider::class,
|
||||
Illuminate\Queue\QueueServiceProvider::class,
|
||||
Illuminate\Redis\RedisServiceProvider::class,
|
||||
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
|
||||
Illuminate\Session\SessionServiceProvider::class,
|
||||
Illuminate\Translation\TranslationServiceProvider::class,
|
||||
Illuminate\Validation\ValidationServiceProvider::class,
|
||||
Illuminate\View\ViewServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Package Service Providers...
|
||||
*/
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
*/
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\PluginServiceProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Class Aliases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array of class aliases will be registered when this application
|
||||
| is started. However, feel free to register as many as you wish as
|
||||
| the aliases are "lazy" loaded so they don't hinder performance.
|
||||
|
|
||||
*/
|
||||
|
||||
'aliases' => Facade::defaultAliases()->merge([
|
||||
// 'ExampleClass' => App\Example\ExampleClass::class,
|
||||
])->toArray(),
|
||||
|
||||
];
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default authentication "guard" and password
|
||||
| reset options for your application. You may change these defaults
|
||||
| as required, but they're a perfect start for most applications.
|
||||
|
|
||||
*/
|
||||
|
||||
'defaults' => [
|
||||
'guard' => 'web',
|
||||
'passwords' => 'users',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, you may define every authentication guard for your application.
|
||||
| Of course, a great default configuration has been defined for you
|
||||
| here which uses session storage and the Eloquent user provider.
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| Supported: "session"
|
||||
|
|
||||
*/
|
||||
|
||||
'guards' => [
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| All authentication drivers have a user provider. This defines how the
|
||||
| users are actually retrieved out of your database or other storage
|
||||
| mechanisms used by this application to persist your user's data.
|
||||
|
|
||||
| If you have multiple user tables or models you may configure multiple
|
||||
| sources which represent each model / table. These sources may then
|
||||
| be assigned to any extra authentication guards you have defined.
|
||||
|
|
||||
| Supported: "database", "eloquent"
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => App\Models\User::class,
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
// 'driver' => 'database',
|
||||
// 'table' => 'users',
|
||||
// ],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Resetting Passwords
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may specify multiple password reset configurations if you have more
|
||||
| than one user table or model in the application and you want to have
|
||||
| separate password reset settings based on the specific user types.
|
||||
|
|
||||
| The expire time is the number of minutes that each reset token will be
|
||||
| considered valid. This security feature keeps tokens short-lived so
|
||||
| they have less time to be guessed. You may change this as needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'passwords' => [
|
||||
'users' => [
|
||||
'provider' => 'users',
|
||||
'table' => 'password_resets',
|
||||
'expire' => 60,
|
||||
'throttle' => 60,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Confirmation Timeout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the amount of seconds before a password confirmation
|
||||
| times out and the user is prompted to re-enter their password via the
|
||||
| confirmation screen. By default, the timeout lasts for three hours.
|
||||
|
|
||||
*/
|
||||
|
||||
'password_timeout' => 10800,
|
||||
|
||||
];
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Broadcaster
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default broadcaster that will be used by the
|
||||
| framework when an event needs to be broadcast. You may set this to
|
||||
| any of the connections defined in the "connections" array below.
|
||||
|
|
||||
| Supported: "pusher", "ably", "redis", "log", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => 'log',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Broadcast Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the broadcast connections that will be used
|
||||
| to broadcast events to other systems or over websockets. Samples of
|
||||
| each available type of connection are provided inside this array.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'ably' => [
|
||||
'driver' => 'ably',
|
||||
'key' => env('ABLY_KEY'),
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'driver' => 'log',
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'null',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default cache connection that gets used while
|
||||
| using this caching library. This connection is used when another is
|
||||
| not explicitly specified when executing a given caching function.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => 'file',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Stores
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define all of the cache "stores" for your application as
|
||||
| well as their drivers. You may even define multiple stores for the
|
||||
| same cache driver to group types of items stored in your caches.
|
||||
|
|
||||
| Supported drivers: "apc", "array", "database", "file",
|
||||
| "memcached", "redis", "dynamodb", "octane", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'stores' => [
|
||||
|
||||
'apc' => [
|
||||
'driver' => 'apc',
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'driver' => 'array',
|
||||
'serialize' => false,
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'table' => 'cache',
|
||||
'connection' => null,
|
||||
'lock_connection' => null,
|
||||
],
|
||||
|
||||
'file' => [
|
||||
'driver' => 'file',
|
||||
'path' => storage_path('framework/cache/data'),
|
||||
],
|
||||
|
||||
'octane' => [
|
||||
'driver' => 'octane',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Key Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
|
||||
| stores there might be other applications using the same cache. For
|
||||
| that reason, you may prefix every cache key to avoid collisions.
|
||||
|
|
||||
*/
|
||||
|
||||
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
|
||||
|
||||
];
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cross-Origin Resource Sharing (CORS) Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure your settings for cross-origin resource sharing
|
||||
| or "CORS". This determines what cross-origin operations may execute
|
||||
| in web browsers. You are free to adjust these settings as needed.
|
||||
|
|
||||
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
||||
|
|
||||
*/
|
||||
|
||||
'paths' => ['api/*', 'sanctum/csrf-cookie'],
|
||||
|
||||
'allowed_methods' => ['*'],
|
||||
|
||||
'allowed_origins' => ['*'],
|
||||
|
||||
'allowed_origins_patterns' => [],
|
||||
|
||||
'allowed_headers' => ['*'],
|
||||
|
||||
'exposed_headers' => [],
|
||||
|
||||
'max_age' => 0,
|
||||
|
||||
'supports_credentials' => false,
|
||||
|
||||
];
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for all database work. Of course
|
||||
| you may use many connections at once using the Database library.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => 'sqlite',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here are each of the database connections setup for your application.
|
||||
| Of course, examples of configuring each database platform that is
|
||||
| supported by Laravel is shown below to make development simple.
|
||||
|
|
||||
|
|
||||
| All database work in Laravel is done through the PHP PDO facilities
|
||||
| so make sure you have the driver for your particular database of
|
||||
| choice installed on your machine before you begin development.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'url' => '',
|
||||
'database' => database_path('database.sqlite'),
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => true,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migration Repository Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This table keeps track of all the migrations that have already run for
|
||||
| your application. Using this information, we can determine which of
|
||||
| the migrations on disk haven't actually been run in the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => 'migrations',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redis Databases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Redis is an open source, fast, and advanced key-value store that also
|
||||
| provides a richer body of commands than a typical key-value system
|
||||
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
||||
|
|
||||
*/
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => '127.0.0.1',
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => '6379',
|
||||
'database' => '0',
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'username' => env('REDIS_USERNAME'),
|
||||
'password' => env('REDIS_PASSWORD'),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_CACHE_DB', '1'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default filesystem disk that should be used
|
||||
| by the framework. The "local" disk, as well as a variety of cloud
|
||||
| based disks are available to your application. Just store away!
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => 'local',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filesystem Disks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure as many filesystem "disks" as you wish, and you
|
||||
| may even configure multiple disks of the same driver. Defaults have
|
||||
| been set up for each driver as an example of the required values.
|
||||
|
|
||||
| Supported Drivers: "local", "ftp", "sftp", "s3"
|
||||
|
|
||||
*/
|
||||
/**
|
||||
* TODO 这里需要做点骚操作,直接获取分区信息进行填充
|
||||
*/
|
||||
|
||||
'disks' => [
|
||||
|
||||
'local' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app'),
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'www' => [
|
||||
'driver' => 'local',
|
||||
'root' => '/www',
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'public' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/public'),
|
||||
'url' => '/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||
'region' => env('AWS_DEFAULT_REGION'),
|
||||
'bucket' => env('AWS_BUCKET'),
|
||||
'url' => env('AWS_URL'),
|
||||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Symbolic Links
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the symbolic links that will be created when the
|
||||
| `storage:link` Artisan command is executed. The array keys should be
|
||||
| the locations of the links and the values should be their targets.
|
||||
|
|
||||
*/
|
||||
|
||||
'links' => [
|
||||
public_path('storage') => storage_path('app/public'),
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Hash Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default hash driver that will be used to hash
|
||||
| passwords for your application. By default, the bcrypt algorithm is
|
||||
| used; however, you remain free to modify this option if you wish.
|
||||
|
|
||||
| Supported: "bcrypt", "argon", "argon2id"
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => 'argon2id',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bcrypt Options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the configuration options that should be used when
|
||||
| passwords are hashed using the Bcrypt algorithm. This will allow you
|
||||
| to control the amount of time it takes to hash the given password.
|
||||
|
|
||||
*/
|
||||
|
||||
'bcrypt' => [
|
||||
'rounds' => env('BCRYPT_ROUNDS', 10),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Argon Options
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the configuration options that should be used when
|
||||
| passwords are hashed using the Argon algorithm. These will allow you
|
||||
| to control the amount of time it takes to hash the given password.
|
||||
|
|
||||
*/
|
||||
|
||||
'argon' => [
|
||||
'memory' => 65536,
|
||||
'threads' => 1,
|
||||
'time' => 4,
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default log channel that gets used when writing
|
||||
| messages to the logs. The name specified in this option should match
|
||||
| one of the channels defined in the "channels" configuration array.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => 'stack',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Deprecations Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the log channel that should be used to log warnings
|
||||
| regarding deprecated PHP and library features. This allows you to get
|
||||
| your application ready for upcoming major versions of dependencies.
|
||||
|
|
||||
*/
|
||||
|
||||
'deprecations' => [
|
||||
'channel' => null,
|
||||
'trace' => false,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log channels for your application. Out of
|
||||
| the box, Laravel uses the Monolog PHP logging library. This gives
|
||||
| you a variety of powerful log handlers / formatters to utilize.
|
||||
|
|
||||
| Available Drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog",
|
||||
| "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => ['single'],
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
'single' => [
|
||||
'driver' => 'single',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => 'debug',
|
||||
],
|
||||
|
||||
'daily' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => 'debug',
|
||||
'days' => 14,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => 'Laravel Log',
|
||||
'emoji' => ':boom:',
|
||||
'level' => 'debug',
|
||||
],
|
||||
|
||||
'papertrail' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => 'debug',
|
||||
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
|
||||
'handler_with' => [
|
||||
'host' => env('PAPERTRAIL_URL'),
|
||||
'port' => env('PAPERTRAIL_PORT'),
|
||||
'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
|
||||
],
|
||||
],
|
||||
|
||||
'stderr' => [
|
||||
'driver' => 'monolog',
|
||||
'level' => 'debug',
|
||||
'handler' => StreamHandler::class,
|
||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
||||
'with' => [
|
||||
'stream' => 'php://stderr',
|
||||
],
|
||||
],
|
||||
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => 'debug',
|
||||
],
|
||||
|
||||
'errorlog' => [
|
||||
'driver' => 'errorlog',
|
||||
'level' => 'debug',
|
||||
],
|
||||
|
||||
'null' => [
|
||||
'driver' => 'monolog',
|
||||
'handler' => NullHandler::class,
|
||||
],
|
||||
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default mailer that is used to send any email
|
||||
| messages sent by your application. Alternative mailers may be setup
|
||||
| and used as needed; however, this mailer will be used by default.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'smtp'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure all of the mailers used by your application plus
|
||||
| their respective settings. Several examples have been configured for
|
||||
| you and you are free to add your own as your application requires.
|
||||
|
|
||||
| Laravel supports a variety of mail "transport" drivers to be used while
|
||||
| sending an e-mail. You will specify which one you are using for your
|
||||
| mailers below. You are free to add additional mailers as required.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses",
|
||||
| "postmark", "log", "array", "failover"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN'),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'mailgun' => [
|
||||
'transport' => 'mailgun',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may wish for all e-mails sent by your application to be sent from
|
||||
| the same address. Here, you may specify a name and address that is
|
||||
| used globally for all e-mails that are sent by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
|
||||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Markdown Mail Settings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you are using Markdown based email rendering, you may configure your
|
||||
| theme and component paths here, allowing you to customize the design
|
||||
| of the emails. Or, you may simply stick with the Laravel defaults!
|
||||
|
|
||||
*/
|
||||
|
||||
'markdown' => [
|
||||
'theme' => 'default',
|
||||
|
||||
'paths' => [
|
||||
resource_path('views/vendor/mail'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
return [
|
||||
'name' => '耗子Linux面板',
|
||||
'version' => '20230209',
|
||||
'plugin_dir' => '/www/panel/plugins',
|
||||
];
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Queue Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Laravel's queue API supports an assortment of back-ends via a single
|
||||
| API, giving you convenient access to each back-end using the same
|
||||
| syntax for every one. Here you may define a default connection.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => 'database',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queue Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the connection information for each server that
|
||||
| is used by your application. A default configuration has been added
|
||||
| for each back-end shipped with Laravel. You are free to add more.
|
||||
|
|
||||
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'table' => 'jobs',
|
||||
'queue' => 'default',
|
||||
'retry_after' => 90,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'beanstalkd' => [
|
||||
'driver' => 'beanstalkd',
|
||||
'host' => 'localhost',
|
||||
'queue' => 'default',
|
||||
'retry_after' => 90,
|
||||
'block_for' => 0,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => 90,
|
||||
'block_for' => null,
|
||||
'after_commit' => false,
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Failed Queue Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| These options configure the behavior of failed queue job logging so you
|
||||
| can control which database and table are used to store the jobs that
|
||||
| have failed. You may change them to any database / table you wish.
|
||||
|
|
||||
*/
|
||||
|
||||
'failed' => [
|
||||
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
|
||||
'database' => env('DB_CONNECTION', 'mysql'),
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
|
||||
];
|
||||