mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 08:00:27 +00:00
Compare commits
103
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d548c7df9 | ||
|
|
8ec5a5bd4b | ||
|
|
2e8bcc09e6 | ||
|
|
29a304e2c8 | ||
|
|
b1adc37920 | ||
|
|
03fac3ec06 | ||
|
|
6e465ac215 | ||
|
|
21bbadc37b | ||
|
|
3061d640ce | ||
|
|
874717cbcd | ||
|
|
6850acabae | ||
|
|
890ea42749 | ||
|
|
6aaf61cde4 | ||
|
|
73930c5e29 | ||
|
|
8f8d280e90 | ||
|
|
b46192024f | ||
|
|
8f939ad5be | ||
|
|
a6363ce4e5 | ||
|
|
fd490b2642 | ||
|
|
7624670ee0 | ||
|
|
8c3145e1c6 | ||
|
|
bec7b6989f | ||
|
|
326f2654e6 | ||
|
|
708499a335 | ||
|
|
bdeb842939 | ||
|
|
edefdce3ff | ||
|
|
b348c36571 | ||
|
|
6ae150b64e | ||
|
|
3687b9bea2 | ||
|
|
711431da82 | ||
|
|
e93afc33aa | ||
|
|
82dd993751 | ||
|
|
9ba293e154 | ||
|
|
8fa9f128d8 | ||
|
|
ed9f1599e6 | ||
|
|
787a29769d | ||
|
|
6b379389d3 | ||
|
|
5bcbf32823 | ||
|
|
0aa90e1b2d | ||
|
|
a7b87211b4 | ||
|
|
2c666933a5 | ||
|
|
f280b533c6 | ||
|
|
47a16bc9a9 | ||
|
|
713202b6eb | ||
|
|
79020abb1c | ||
|
|
83ef41cf1a | ||
|
|
91675c69e9 | ||
|
|
0e68065b40 | ||
|
|
600b769083 | ||
|
|
e7670f96de | ||
|
|
342c04b30a | ||
|
|
5ab8377ac1 | ||
|
|
74eb2c8dd6 | ||
|
|
f136d97941 | ||
|
|
a5629e3f42 | ||
|
|
515d6ca500 | ||
|
|
c0ccc0318d | ||
|
|
8196c11010 | ||
|
|
170bd22062 | ||
|
|
097a33c68f | ||
|
|
3be3bf9926 | ||
|
|
038f2b4f54 | ||
|
|
f84e284c20 | ||
|
|
48e75b5916 | ||
|
|
a12ceaee6e | ||
|
|
9050680115 | ||
|
|
0022a9b508 | ||
|
|
3437d62f31 | ||
|
|
dc880835d0 | ||
|
|
1663bf8677 | ||
|
|
aed236cbc9 | ||
|
|
90be74504e | ||
|
|
bba8aab18c | ||
|
|
47d135ecca | ||
|
|
d96fe49993 | ||
|
|
ac396b61ce | ||
|
|
6798d0c7ee | ||
|
|
1490ed7541 | ||
|
|
48b3032de6 | ||
|
|
ed12bec460 | ||
|
|
3c4a1ea614 | ||
|
|
79eb0288b4 | ||
|
|
99775e1eb4 | ||
|
|
db848b41d2 | ||
|
|
b5b11dff86 | ||
|
|
9cf4bfc606 | ||
|
|
221162d44f | ||
|
|
617970a1f8 | ||
|
|
5610c73d67 | ||
|
|
51fe02661f | ||
|
|
8f491b2933 | ||
|
|
b7e16bd6d5 | ||
|
|
38ca2ddd01 | ||
|
|
402b41100b | ||
|
|
766beae4e1 | ||
|
|
05f72e9dcf | ||
|
|
23553c68ae | ||
|
|
4dd1eb7a1e | ||
|
|
db307732c6 | ||
|
|
33d4ade1d4 | ||
|
|
186075ff9a | ||
|
|
34df745d1c | ||
|
|
ec363ac157 |
+6
-4
@@ -10,7 +10,7 @@ before:
|
||||
- ./ci/script.sh
|
||||
- sed -i 's@ORIGINAL_VERSION=.*@ORIGINAL_VERSION=v{{ .Version }}@g' 1pctl
|
||||
- go mod tidy
|
||||
|
||||
|
||||
builds:
|
||||
- main: ./cmd/server/main.go
|
||||
binary: 1panel
|
||||
@@ -31,17 +31,19 @@ builds:
|
||||
- arm
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
- formats: [ 'tar.gz' ]
|
||||
name_template: "1panel-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
wrap_in_directory: true
|
||||
files:
|
||||
- 1pctl
|
||||
- 1panel.service
|
||||
- install.sh
|
||||
- GeoIP.mmdb
|
||||
- 1panel.service
|
||||
- initscript/*
|
||||
- lang/*
|
||||
- GeoIP.mmdb
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
@@ -23,7 +23,7 @@ build_frontend:
|
||||
|
||||
build_backend_on_linux:
|
||||
cd $(SERVER_PATH) \
|
||||
&& GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
&& CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
|
||||
|
||||
build_backend_on_darwin:
|
||||
cd $(SERVER_PATH) \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center"><a href="https://1panel.pro"><img src="https://resource.1panel.pro/img/1panel-logo.png" alt="1Panel" width="300" /></a></p>
|
||||
<p align="center"><b>Top-Rated Web-based Linux Server Management Tool</b><br>Best VPS control panel<br>新一代的 Linux 服务器运维管理面板</p>
|
||||
<p align="center"><b>Simplify Linux Server Management</b></p>
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/2462" target="_blank"><img src="https://trendshift.io/api/badge/repositories/2462" alt="1Panel-dev%2F1Panel | Trendshift" style="width: 240px; height: auto;" /></a>
|
||||
</p>
|
||||
@@ -31,13 +31,14 @@
|
||||
|
||||
------------------------------
|
||||
|
||||
1Panel is an open-source, modern web-based control panel for Linux server management.
|
||||
1Panel provides an intuitive web interface and MCP Server to manage websites, files, containers, databases, and LLMs on a Linux server.
|
||||
|
||||
- **Efficient Management**: Through a user-friendly web graphical interface, 1Panel enables users to effortlessly manage their Linux servers. Key features include host monitoring, file management, database administration, container management, LLMs management.
|
||||
- **Rapid Website Deployment**: With deep integration of the popular open-source website building software WordPress, 1Panel streamlines the process of domain binding and SSL certificate configuration, all achievable with just one click.
|
||||
- **Application Store**: 1Panel curates a wide range of high-quality open-source tools and applications, facilitating easy installation and updates for its users.
|
||||
- **Security and Reliability**: By leveraging containerization and secure application deployment practices, 1Panel minimizes vulnerability exposure. It further enhances security through integrated firewall management and log auditing capabilities.
|
||||
- **One-Click Backup & Restore**: Data protection is made simple with 1Panel's one-click backup and restore functionality, supporting various cloud storage solutions to ensure data integrity and availability.
|
||||
- **MCP Server**: [1Panel MCP Server](https://github.com/1Panel-dev/mcp-1panel) allow user to execute server operations via natural language.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -49,7 +50,7 @@ curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash q
|
||||
|
||||
Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for more details.
|
||||
|
||||
中国用户请使用这个 [安装脚本](https://1panel.cn/docs/installation/online_installation/),其应用数量比国际版本更丰富。
|
||||
中国用户请使用此 [安装脚本](https://1panel.cn/docs/v2/installation/online_installation/),默认安装 V2 版本,应用数量相较国际版本更丰富。
|
||||
|
||||
## Screenshot
|
||||
|
||||
@@ -61,12 +62,18 @@ Please refer to our [documentation](https://docs.1panel.pro/quick_start/) for mo
|
||||
|
||||
## Pro Edition
|
||||
|
||||
Compared to the OSS Edition, 1Panel Pro Edition provides users with a wealth of enhanced features and technical support services. Enhanced features include WAF enhancement, website tamper protection, website monitoring, GPU monitoring, custom logo and theme color, etc. [Click to view the detailed introduction of the Pro Edition](https://1panel.pro/pricing).
|
||||
Compared to the OSS Edition, 1Panel Pro Edition provides users with a wealth of enhanced features and technical support services. Enhanced features include WAF enhancement, Website monitoring, Mobile APP, custom logo and theme, etc.
|
||||
|
||||
[Click to see Pro Edition details](https://1panel.pro/pricing).
|
||||
|
||||
## Security Information
|
||||
|
||||
If you discover any security issues, please refer to [SECURITY.md](/SECURITY.md).
|
||||
|
||||
## GitAds Sponsored
|
||||
|
||||
[](https://gitads.dev/v1/ad-track?source=1panel-dev/1panel@github)
|
||||
|
||||
## License
|
||||
|
||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
@@ -74,3 +81,7 @@ Licensed under The GNU General Public License version 3 (GPLv3) (the "License")
|
||||
<https://www.gnu.org/licenses/gpl-3.0.html>
|
||||
|
||||
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.
|
||||
|
||||
<!-- GitAds-Verify: 9M8AMBB11GYJ3RUNXGB5R1MR71KPATU9 -->
|
||||
|
||||
|
||||
|
||||
@@ -68,4 +68,6 @@ var (
|
||||
favoriteService = service.NewIFavoriteService()
|
||||
|
||||
websiteCAService = service.NewIWebsiteCAService()
|
||||
|
||||
mcpServerService = service.NewIMcpServerService()
|
||||
)
|
||||
|
||||
@@ -410,11 +410,20 @@ func (b *BaseApi) CheckFile(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
helper.SuccessWithData(c, false)
|
||||
fileOp := files.NewFileOp()
|
||||
if fileOp.Stat(req.Path) {
|
||||
helper.SuccessWithData(c, true)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, true)
|
||||
if req.WithInit {
|
||||
if err := fileOp.CreateDir(req.Path, 0644); err != nil {
|
||||
helper.SuccessWithData(c, false)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, true)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, false)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// @Success 200 {object} response.HostToolRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool [post]
|
||||
// @Router /hosts/tool [post]
|
||||
func (b *BaseApi) GetToolStatus(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -36,7 +36,7 @@ func (b *BaseApi) GetToolStatus(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/create [post]
|
||||
// @Router /hosts/tool/create [post]
|
||||
// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"}
|
||||
func (b *BaseApi) InitToolConfig(c *gin.Context) {
|
||||
var req request.HostToolCreate
|
||||
@@ -58,7 +58,7 @@ func (b *BaseApi) InitToolConfig(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/operate [post]
|
||||
// @Router /hosts/tool/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"}
|
||||
func (b *BaseApi) OperateTool(c *gin.Context) {
|
||||
var req request.HostToolReq
|
||||
@@ -80,7 +80,7 @@ func (b *BaseApi) OperateTool(c *gin.Context) {
|
||||
// @Success 200 {object} response.HostToolConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/config [post]
|
||||
// @Router /hosts/tool/config [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"}
|
||||
func (b *BaseApi) OperateToolConfig(c *gin.Context) {
|
||||
var req request.HostToolConfig
|
||||
@@ -103,7 +103,7 @@ func (b *BaseApi) OperateToolConfig(c *gin.Context) {
|
||||
// @Success 200 {string} logContent
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/log [post]
|
||||
// @Router /hosts/tool/log [post]
|
||||
func (b *BaseApi) GetToolLog(c *gin.Context) {
|
||||
var req request.HostToolLogReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -125,7 +125,7 @@ func (b *BaseApi) GetToolLog(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/supervisor/process [post]
|
||||
// @Router /hosts/tool/supervisor/process [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 守护进程 ","formatEN":"[operate] process"}
|
||||
func (b *BaseApi) OperateProcess(c *gin.Context) {
|
||||
var req request.SupervisorProcessConfig
|
||||
@@ -147,7 +147,7 @@ func (b *BaseApi) OperateProcess(c *gin.Context) {
|
||||
// @Success 200 {object} response.SupervisorProcessConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/supervisor/process [get]
|
||||
// @Router /hosts/tool/supervisor/process [get]
|
||||
func (b *BaseApi) GetProcess(c *gin.Context) {
|
||||
configs, err := hostToolService.GetSupervisorProcessConfig()
|
||||
if err != nil {
|
||||
@@ -164,7 +164,7 @@ func (b *BaseApi) GetProcess(c *gin.Context) {
|
||||
// @Success 200 {string} content
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/tool/supervisor/process/file [post]
|
||||
// @Router /hosts/tool/supervisor/process/file [post]
|
||||
// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"}
|
||||
func (b *BaseApi) GetProcessFile(c *gin.Context) {
|
||||
var req request.SupervisorProcessFileReq
|
||||
|
||||
@@ -76,7 +76,7 @@ func (b *BaseApi) CheckRepoStatus(c *gin.Context) {
|
||||
// @Tags Container Image-repo
|
||||
// @Summary Create image repo
|
||||
// @Accept json
|
||||
// @Param request body dto.ImageRepoDelete true "request"
|
||||
// @Param request body dto.ImageRepoCreate true "request"
|
||||
// @Produce json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
@@ -99,20 +99,20 @@ func (b *BaseApi) CreateRepo(c *gin.Context) {
|
||||
// @Tags Container Image-repo
|
||||
// @Summary Delete image repo
|
||||
// @Accept json
|
||||
// @Param request body dto.ImageRepoDelete true "request"
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Produce json
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/repo/del [post]
|
||||
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"image_repos","output_column":"name","output_value":"names"}],"formatZH":"删除镜像仓库 [names]","formatEN":"delete image repo [names]"}
|
||||
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_column":"name","output_value":"name"}],"formatZH":"删除镜像仓库 [name]","formatEN":"delete image repo [name]"}
|
||||
func (b *BaseApi) DeleteRepo(c *gin.Context) {
|
||||
var req dto.ImageRepoDelete
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := imageRepoService.BatchDelete(req); err != nil {
|
||||
if err := imageRepoService.Delete(req); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary List mcp servers
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerSearch true "request"
|
||||
// @Success 200 {object} response.McpServersRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/search [post]
|
||||
func (b *BaseApi) PageMcpServers(c *gin.Context) {
|
||||
var req request.McpServerSearch
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list := mcpServerService.Page(req)
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Create mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server [post]
|
||||
func (b *BaseApi) CreateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Create(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Update mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/update [post]
|
||||
func (b *BaseApi) UpdateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Update(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Delete mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/del [post]
|
||||
func (b *BaseApi) DeleteMcpServer(c *gin.Context) {
|
||||
var req request.McpServerDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Delete(req.ID)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Operate mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpServerOperate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/server/op [post]
|
||||
func (b *BaseApi) OperateMcpServer(c *gin.Context) {
|
||||
var req request.McpServerOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.Operate(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Bind Domain for mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpBindDomain true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/bind [post]
|
||||
func (b *BaseApi) BindMcpDomain(c *gin.Context) {
|
||||
var req request.McpBindDomain
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.BindDomain(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Update bind Domain for mcp server
|
||||
// @Accept json
|
||||
// @Param request body request.McpBindDomainUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/update [post]
|
||||
func (b *BaseApi) UpdateMcpBindDomain(c *gin.Context) {
|
||||
var req request.McpBindDomainUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
err := mcpServerService.UpdateBindDomain(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithOutData(c)
|
||||
}
|
||||
|
||||
// @Tags McpServer
|
||||
// @Summary Get bin Domain for mcp server
|
||||
// @Accept json
|
||||
// @Success 200 {object} response.McpBindDomainRes
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /mcp/domain/get [get]
|
||||
func (b *BaseApi) GetMcpBindDomain(c *gin.Context) {
|
||||
res, err := mcpServerService.GetBindDomain()
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// @Success 200 {object} dto.SSHInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/search [post]
|
||||
// @Router /hosts/ssh/search [post]
|
||||
func (b *BaseApi) GetSSHInfo(c *gin.Context) {
|
||||
info, err := sshService.GetSSHInfo()
|
||||
if err != nil {
|
||||
@@ -29,7 +29,7 @@ func (b *BaseApi) GetSSHInfo(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/operate [post]
|
||||
// @Router /hosts/ssh/operate [post]
|
||||
// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] SSH ","formatEN":"[operation] SSH"}
|
||||
func (b *BaseApi) OperateSSH(c *gin.Context) {
|
||||
var req dto.Operate
|
||||
@@ -51,7 +51,7 @@ func (b *BaseApi) OperateSSH(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/update [post]
|
||||
// @Router /hosts/ssh/update [post]
|
||||
// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置 [key] => [value]","formatEN":"update SSH setting [key] => [value]"}
|
||||
func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||
var req dto.SSHUpdate
|
||||
@@ -73,7 +73,7 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/conffile/update [post]
|
||||
// @Router /hosts/conffile/update [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"}
|
||||
func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
|
||||
var req dto.SSHConf
|
||||
@@ -95,7 +95,7 @@ func (b *BaseApi) UpdateSSHByfile(c *gin.Context) {
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/generate [post]
|
||||
// @Router /hosts/ssh/generate [post]
|
||||
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"}
|
||||
func (b *BaseApi) GenerateSSH(c *gin.Context) {
|
||||
var req dto.GenerateSSH
|
||||
@@ -117,7 +117,7 @@ func (b *BaseApi) GenerateSSH(c *gin.Context) {
|
||||
// @Success 200 {string} data
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/secret [post]
|
||||
// @Router /hosts/ssh/secret [post]
|
||||
func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
|
||||
var req dto.GenerateLoad
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -139,7 +139,7 @@ func (b *BaseApi) LoadSSHSecret(c *gin.Context) {
|
||||
// @Success 200 {object} dto.SSHLog
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/log [post]
|
||||
// @Router /hosts/ssh/log [post]
|
||||
func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
|
||||
var req dto.SearchSSHLog
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
@@ -159,7 +159,7 @@ func (b *BaseApi) LoadSSHLogs(c *gin.Context) {
|
||||
// @Success 200 {string} data
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /host/ssh/conf [get]
|
||||
// @Router /hosts/ssh/conf [get]
|
||||
func (b *BaseApi) LoadSSHConf(c *gin.Context) {
|
||||
data, err := sshService.LoadSSHConf()
|
||||
if err != nil {
|
||||
|
||||
+71
-160
@@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/service"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
@@ -69,12 +68,11 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
|
||||
<-quitChan
|
||||
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
}
|
||||
|
||||
func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
@@ -96,193 +94,106 @@ func (b *BaseApi) RedisWsSsh(c *gin.Context) {
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
source := c.Query("source")
|
||||
var containerID string
|
||||
var initCmd []string
|
||||
switch source {
|
||||
case "redis":
|
||||
containerID, initCmd, err = loadRedisInitCmd(c)
|
||||
case "ollama":
|
||||
containerID, initCmd, err = loadOllamaInitCmd(c)
|
||||
case "container":
|
||||
containerID, initCmd, err = loadContainerInitCmd(c)
|
||||
default:
|
||||
if wshandleError(wsConn, fmt.Errorf("not support such source %s", source)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
pidMap := loadMapFromDockerTop(containerID)
|
||||
slave, err := terminal.NewCommand(initCmd)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(containerID, strings.ReplaceAll(strings.Join(initCmd, " "), fmt.Sprintf("exec -it %s ", containerID), ""), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
|
||||
}
|
||||
|
||||
func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
|
||||
name := c.Query("name")
|
||||
from := c.Query("from")
|
||||
commands := []string{"redis-cli"}
|
||||
commands := []string{"exec", "-it"}
|
||||
database, err := databaseService.Get(name)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
|
||||
return
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("no such database in db, err: %v", err)
|
||||
}
|
||||
if from == "local" {
|
||||
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "no such database in db")) {
|
||||
return
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
name = redisInfo.ContainerName
|
||||
commands = append(commands, []string{name, "redis-cli"}...)
|
||||
if len(database.Password) != 0 {
|
||||
commands = []string{"redis-cli", "-a", database.Password, "--no-auth-warning"}
|
||||
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
|
||||
}
|
||||
} else {
|
||||
itemPort := fmt.Sprintf("%v", database.Port)
|
||||
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort}
|
||||
if len(database.Password) != 0 {
|
||||
commands = []string{"redis-cli", "-h", database.Address, "-p", itemPort, "-a", database.Password, "--no-auth-warning"}
|
||||
}
|
||||
name = "1Panel-redis-cli-tools"
|
||||
commands = append(commands, []string{name, "redis-cli", "-h", database.Address, "-p", fmt.Sprintf("%v", database.Port)}...)
|
||||
if len(database.Password) != 0 {
|
||||
commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...)
|
||||
}
|
||||
}
|
||||
|
||||
pidMap := loadMapFromDockerTop(name)
|
||||
itemCmds := append([]string{"exec", "-it", name}, commands...)
|
||||
slave, err := terminal.NewCommand(itemCmds)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(name, strings.Join(commands, " "), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
return name, commands, nil
|
||||
}
|
||||
|
||||
func (b *BaseApi) OllamaWsSsh(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if global.CONF.System.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
func loadOllamaInitCmd(c *gin.Context) (string, []string, error) {
|
||||
name := c.Query("name")
|
||||
if cmd.CheckIllegal(name) {
|
||||
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
|
||||
return
|
||||
}
|
||||
return "", nil, fmt.Errorf("ollama model %s contains illegal characters", name)
|
||||
}
|
||||
container, err := service.LoadContainerName()
|
||||
if wshandleError(wsConn, errors.WithMessage(err, " load container name for ollama failed")) {
|
||||
return
|
||||
}
|
||||
commands := []string{"ollama", "run", name}
|
||||
|
||||
pidMap := loadMapFromDockerTop(container)
|
||||
fmt.Println("pidMap")
|
||||
for k, v := range pidMap {
|
||||
fmt.Println(k, v)
|
||||
}
|
||||
itemCmds := append([]string{"exec", "-it", container}, commands...)
|
||||
slave, err := terminal.NewCommand(itemCmds)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(container, strings.Join(commands, " "), pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
ollamaInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: "", Type: "ollama"})
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
|
||||
}
|
||||
containerName := ollamaInfo.ContainerName
|
||||
return containerName, []string{"exec", "-it", containerName, "ollama", "run", name}, nil
|
||||
}
|
||||
|
||||
func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if global.CONF.System.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func loadContainerInitCmd(c *gin.Context) (string, []string, error) {
|
||||
containerID := c.Query("containerid")
|
||||
command := c.Query("command")
|
||||
user := c.Query("user")
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
if wshandleError(wsConn, errors.New("error param of command or containerID")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
|
||||
cmds := []string{"exec", containerID, command}
|
||||
if len(user) != 0 {
|
||||
cmds = []string{"exec", "-u", user, containerID, command}
|
||||
}
|
||||
if cmd.CheckIllegal(user, containerID, command) {
|
||||
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
|
||||
return
|
||||
}
|
||||
return "", nil, fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID)
|
||||
}
|
||||
stdout, err := cmd.ExecWithCheck("docker", cmds...)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, stdout)) {
|
||||
return
|
||||
if len(command) == 0 || len(containerID) == 0 {
|
||||
return "", nil, fmt.Errorf("error param of command: %s or containerID: %s", command, containerID)
|
||||
}
|
||||
|
||||
commands := []string{"exec", "-it", containerID, command}
|
||||
if len(user) != 0 {
|
||||
commands = []string{"exec", "-it", "-u", user, containerID, command}
|
||||
}
|
||||
pidMap := loadMapFromDockerTop(containerID)
|
||||
slave, err := terminal.NewCommand(commands)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer killBash(containerID, command, pidMap)
|
||||
defer slave.Close()
|
||||
|
||||
tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
tty.Start(quitChan)
|
||||
go slave.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
global.LOG.Info("websocket finished")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
return containerID, commands, nil
|
||||
}
|
||||
|
||||
func wshandleError(ws *websocket.Conn, err error) bool {
|
||||
|
||||
@@ -71,14 +71,16 @@ type CronjobDownload struct {
|
||||
}
|
||||
|
||||
type CronjobClean struct {
|
||||
IsDelete bool `json:"isDelete"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
IsDelete bool `json:"isDelete"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CleanRemoteData bool `json:"cleanRemoteData"`
|
||||
CronjobID uint `json:"cronjobID" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobBatchDelete struct {
|
||||
CleanData bool `json:"cleanData"`
|
||||
IDs []uint `json:"ids" validate:"required"`
|
||||
CleanData bool `json:"cleanData"`
|
||||
CleanRemoteData bool `json:"cleanRemoteData"`
|
||||
IDs []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
||||
type CronjobInfo struct {
|
||||
|
||||
@@ -6,8 +6,8 @@ type ImageRepoCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
Protocol string `json:"protocol"`
|
||||
Username string `json:"username" validate:"max=256"`
|
||||
Password string `json:"password" validate:"max=256"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Auth bool `json:"auth"`
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ type ImageRepoUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
DownloadUrl string `json:"downloadUrl"`
|
||||
Protocol string `json:"protocol"`
|
||||
Username string `json:"username" validate:"max=256"`
|
||||
Password string `json:"password" validate:"max=256"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Auth bool `json:"auth"`
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package dto
|
||||
|
||||
type DockerComposeService struct {
|
||||
Image string `yaml:"image"`
|
||||
ContainerName string `yaml:"container_name"`
|
||||
Restart string `yaml:"restart"`
|
||||
Ports []string `yaml:"ports"`
|
||||
Environment []string `yaml:"environment"`
|
||||
Command []string `yaml:"command"`
|
||||
}
|
||||
@@ -76,7 +76,8 @@ type FileRename struct {
|
||||
}
|
||||
|
||||
type FilePathCheck struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
WithInit bool `json:"withInit"`
|
||||
}
|
||||
|
||||
type FilePathsCheck struct {
|
||||
@@ -91,11 +92,12 @@ type FileWget struct {
|
||||
}
|
||||
|
||||
type FileMove struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
OldPaths []string `json:"oldPaths" validate:"required"`
|
||||
NewPath string `json:"newPath" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
Cover bool `json:"cover"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
OldPaths []string `json:"oldPaths" validate:"required"`
|
||||
NewPath string `json:"newPath" validate:"required"`
|
||||
Name string `json:"name"`
|
||||
Cover bool `json:"cover"`
|
||||
CoverPaths []string `json:"coverPaths"`
|
||||
}
|
||||
|
||||
type FileDownload struct {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package request
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
|
||||
type McpServerSearch struct {
|
||||
dto.PageInfo
|
||||
Name string `json:"name"`
|
||||
Sync bool `json:"sync"`
|
||||
}
|
||||
|
||||
type McpServerCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Command string `json:"command" validate:"required"`
|
||||
Environments []Environment `json:"environments"`
|
||||
Volumes []Volume `json:"volumes"`
|
||||
Port int `json:"port" validate:"required"`
|
||||
ContainerName string `json:"containerName"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
SsePath string `json:"ssePath"`
|
||||
HostIP string `json:"hostIP"`
|
||||
}
|
||||
|
||||
type McpServerUpdate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
McpServerCreate
|
||||
}
|
||||
|
||||
type Environment struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Volume struct {
|
||||
Source string `json:"source"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
type McpServerDelete struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type McpServerOperate struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
}
|
||||
|
||||
type McpBindDomain struct {
|
||||
Domain string `json:"domain" validate:"required"`
|
||||
SSLID uint `json:"sslID"`
|
||||
IPList string `json:"ipList"`
|
||||
}
|
||||
|
||||
type McpBindDomainUpdate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
SSLID uint `json:"sslID"`
|
||||
IPList string `json:"ipList"`
|
||||
}
|
||||
@@ -53,4 +53,5 @@ type ExistFileInfo struct {
|
||||
Path string `json:"path"`
|
||||
Size float64 `json:"size"`
|
||||
ModTime time.Time `json:"modTime"`
|
||||
IsDir bool `json:"isDir"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
)
|
||||
|
||||
type McpServersRes struct {
|
||||
Items []McpServerDTO `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type McpServerDTO struct {
|
||||
model.McpServer
|
||||
Environments []request.Environment `json:"environments"`
|
||||
Volumes []request.Volume `json:"volumes"`
|
||||
}
|
||||
|
||||
type McpBindDomainRes struct {
|
||||
Domain string `json:"domain"`
|
||||
SSLID uint `json:"sslID"`
|
||||
AcmeAccountID uint `json:"acmeAccountID"`
|
||||
AllowIPs []string `json:"allowIPs"`
|
||||
WebsiteID uint `json:"websiteID"`
|
||||
ConnUrl string `json:"connUrl"`
|
||||
}
|
||||
@@ -71,6 +71,7 @@ type SettingInfo struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
IpWhiteList string `json:"ipWhiteList"`
|
||||
ApiKeyValidityTime string `json:"apiKeyValidityTime"`
|
||||
LicenseVerify string `json:"licenseVerify"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
type McpServer struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
DockerCompose string `json:"dockerCompose"`
|
||||
Command string `json:"command"`
|
||||
ContainerName string `json:"containerName"`
|
||||
Message string `json:"message"`
|
||||
Port int `json:"port"`
|
||||
Status string `json:"status"`
|
||||
Env string `json:"env"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
SsePath string `json:"ssePath"`
|
||||
WebsiteID int `json:"websiteID"`
|
||||
Dir string `json:"dir"`
|
||||
HostIP string `json:"hostIP"`
|
||||
}
|
||||
@@ -16,6 +16,7 @@ type IGroupRepo interface {
|
||||
Delete(opts ...DBOption) error
|
||||
CancelDefault(groupType string) error
|
||||
WithByHostDefault() DBOption
|
||||
WithByWebsiteDefault() DBOption
|
||||
}
|
||||
|
||||
func NewIGroupRepo() IGroupRepo {
|
||||
@@ -56,6 +57,12 @@ func (u *GroupRepo) WithByHostDefault() DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *GroupRepo) WithByWebsiteDefault() DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("is_default = ? AND type = ?", 1, "website")
|
||||
}
|
||||
}
|
||||
|
||||
func (u *GroupRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package repo
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
|
||||
type McpServerRepo struct {
|
||||
}
|
||||
|
||||
type IMcpServerRepo interface {
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error)
|
||||
GetFirst(opts ...DBOption) (*model.McpServer, error)
|
||||
Create(mcpServer *model.McpServer) error
|
||||
Save(mcpServer *model.McpServer) error
|
||||
DeleteBy(opts ...DBOption) error
|
||||
List(opts ...DBOption) ([]model.McpServer, error)
|
||||
}
|
||||
|
||||
func NewIMcpServerRepo() IMcpServerRepo {
|
||||
return &McpServerRepo{}
|
||||
}
|
||||
|
||||
func (m McpServerRepo) Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error) {
|
||||
var servers []model.McpServer
|
||||
db := getDb(opts...).Model(&model.McpServer{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&servers).Error
|
||||
return count, servers, err
|
||||
}
|
||||
|
||||
func (m McpServerRepo) GetFirst(opts ...DBOption) (*model.McpServer, error) {
|
||||
var mcpServer model.McpServer
|
||||
if err := getDb(opts...).First(&mcpServer).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mcpServer, nil
|
||||
}
|
||||
|
||||
func (m McpServerRepo) List(opts ...DBOption) ([]model.McpServer, error) {
|
||||
var mcpServers []model.McpServer
|
||||
if err := getDb(opts...).Find(&mcpServers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mcpServers, nil
|
||||
}
|
||||
|
||||
func (m McpServerRepo) Create(mcpServer *model.McpServer) error {
|
||||
return getDb().Create(mcpServer).Error
|
||||
}
|
||||
|
||||
func (m McpServerRepo) Save(mcpServer *model.McpServer) error {
|
||||
return getDb().Save(mcpServer).Error
|
||||
}
|
||||
|
||||
func (m McpServerRepo) DeleteBy(opts ...DBOption) error {
|
||||
return getDb(opts...).Delete(&model.McpServer{}).Error
|
||||
}
|
||||
@@ -839,6 +839,9 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
|
||||
"param": strings.ReplaceAll(appInstall.Param, oldVal, newVal),
|
||||
"env": strings.ReplaceAll(appInstall.Env, oldVal, newVal),
|
||||
}, commonRepo.WithByID(appInstall.ID))
|
||||
if appKey == "mysql" || appKey == "postgresql" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if param == "user-password" {
|
||||
oldVal = fmt.Sprintf("\"PANEL_DB_USER_PASSWORD\":\"%v\"", appInstall.UserPassword)
|
||||
|
||||
@@ -442,17 +442,14 @@ func deleteLink(ctx context.Context, install *model.AppInstall, deleteDB bool, f
|
||||
return appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID))
|
||||
}
|
||||
|
||||
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
|
||||
if detail.DockerCompose == "" {
|
||||
return "", nil
|
||||
}
|
||||
func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map[string]interface{}, error) {
|
||||
composeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
value, ok := composeMap["services"]
|
||||
if !ok || value == nil {
|
||||
return "", buserr.New(constant.ErrFileParse)
|
||||
return nil, buserr.New("ErrFileParse")
|
||||
}
|
||||
servicesMap := value.(map[string]interface{})
|
||||
if len(servicesMap) == 1 {
|
||||
@@ -470,6 +467,34 @@ func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string
|
||||
delete(servicesMap, oldServiceName)
|
||||
}
|
||||
}
|
||||
serviceValue := servicesMap[install.ServiceName].(map[string]interface{})
|
||||
|
||||
oldComposeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal([]byte(install.DockerCompose), &oldComposeMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldValue, ok := oldComposeMap["services"]
|
||||
if !ok || oldValue == nil {
|
||||
return nil, buserr.New("ErrFileParse")
|
||||
}
|
||||
oldValueMap := oldValue.(map[string]interface{})
|
||||
oldServiceValue := oldValueMap[install.ServiceName].(map[string]interface{})
|
||||
if oldServiceValue["deploy"] != nil {
|
||||
serviceValue["deploy"] = oldServiceValue["deploy"]
|
||||
}
|
||||
servicesMap[install.ServiceName] = serviceValue
|
||||
composeMap["services"] = servicesMap
|
||||
return composeMap, nil
|
||||
}
|
||||
|
||||
func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) {
|
||||
if detail.DockerCompose == "" {
|
||||
return "", nil
|
||||
}
|
||||
composeMap, err := handleUpgradeCompose(install, detail)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
envs := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(install.Env), &envs); err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -518,47 +518,40 @@ func (u *BackupService) loadAccessToken(backup *model.BackupAccount) error {
|
||||
}
|
||||
|
||||
func (u *BackupService) loadRecordSize(records []model.BackupRecord) ([]dto.BackupFile, error) {
|
||||
var datas []dto.BackupFile
|
||||
data := make([]dto.BackupFile, len(records))
|
||||
clientMap := make(map[string]loadSizeHelper)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < len(records); i++ {
|
||||
var item dto.BackupFile
|
||||
item.ID = records[i].ID
|
||||
item.Name = records[i].FileName
|
||||
data[i].ID = records[i].ID
|
||||
data[i].Name = records[i].FileName
|
||||
itemPath := path.Join(records[i].FileDir, records[i].FileName)
|
||||
if _, ok := clientMap[records[i].Source]; !ok {
|
||||
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].Source))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load backup model %s from db failed, err: %v", records[i].Source, err)
|
||||
clientMap[records[i].Source] = loadSizeHelper{}
|
||||
datas = append(datas, item)
|
||||
continue
|
||||
}
|
||||
client, err := u.NewClient(&backup)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load backup client %s from db failed, err: %v", records[i].Source, err)
|
||||
clientMap[records[i].Source] = loadSizeHelper{}
|
||||
datas = append(datas, item)
|
||||
continue
|
||||
}
|
||||
item.Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
|
||||
datas = append(datas, item)
|
||||
data[i].Size, _ = client.Size(path.Join(strings.TrimLeft(backup.BackupPath, "/"), itemPath))
|
||||
clientMap[records[i].Source] = loadSizeHelper{backupPath: strings.TrimLeft(backup.BackupPath, "/"), client: client, isOk: true}
|
||||
continue
|
||||
}
|
||||
if clientMap[records[i].Source].isOk {
|
||||
wg.Add(1)
|
||||
go func(index int) {
|
||||
item.Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
|
||||
datas = append(datas, item)
|
||||
data[i].Size, _ = clientMap[records[index].Source].client.Size(path.Join(clientMap[records[index].Source].backupPath, itemPath))
|
||||
wg.Done()
|
||||
}(i)
|
||||
} else {
|
||||
datas = append(datas, item)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func loadLocalDir() (string, error) {
|
||||
|
||||
+103
-129
@@ -1,7 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -28,14 +27,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
clamServiceNameCentOs = "clamd@scan.service"
|
||||
clamServiceNameUbuntu = "clamav-daemon.service"
|
||||
freshClamService = "clamav-freshclam.service"
|
||||
resultDir = "clamav"
|
||||
clamServiceKey = "clam"
|
||||
freshClamServiceKey = "freshclam"
|
||||
resultDir = "clamav"
|
||||
)
|
||||
|
||||
type ClamService struct {
|
||||
serviceName string
|
||||
serviceName string
|
||||
freshClamService string
|
||||
}
|
||||
|
||||
type IClamService interface {
|
||||
@@ -63,23 +62,32 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
var baseInfo dto.ClamBaseInfo
|
||||
baseInfo.Version = "-"
|
||||
baseInfo.FreshVersion = "-"
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
if exist1 {
|
||||
c.serviceName = clamServiceNameCentOs
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
clamSvc, err := systemctl.GetServiceName(clamServiceKey)
|
||||
if err != nil {
|
||||
baseInfo.IsExist = false
|
||||
return baseInfo, nil
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
c.serviceName = clamServiceNameUbuntu
|
||||
baseInfo.IsExist = true
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
c.serviceName = clamSvc
|
||||
isExist, err := systemctl.IsExist(c.serviceName)
|
||||
if err != nil {
|
||||
baseInfo.IsExist = false
|
||||
}
|
||||
freshExist, _ := systemctl.IsExist(freshClamService)
|
||||
if freshExist {
|
||||
baseInfo.FreshIsExist = true
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshClamService)
|
||||
baseInfo.IsExist = isExist
|
||||
baseInfo.IsActive, _ = systemctl.IsActive(clamSvc)
|
||||
|
||||
freshSvc, err := systemctl.GetServiceName(freshClamServiceKey)
|
||||
if err != nil {
|
||||
baseInfo.FreshIsExist = false
|
||||
return baseInfo, nil
|
||||
}
|
||||
c.freshClamService = freshSvc
|
||||
freshisExist, err := systemctl.IsExist(c.freshClamService)
|
||||
if err != nil {
|
||||
baseInfo.FreshIsExist = false
|
||||
}
|
||||
baseInfo.FreshIsExist = freshisExist
|
||||
baseInfo.FreshIsActive, _ = systemctl.IsActive(freshSvc)
|
||||
|
||||
if !cmd.Which("clamdscan") {
|
||||
baseInfo.IsActive = false
|
||||
}
|
||||
@@ -110,22 +118,27 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) {
|
||||
}
|
||||
|
||||
func (c *ClamService) Operate(operate string) error {
|
||||
var err error
|
||||
switch operate {
|
||||
case "start", "restart", "stop":
|
||||
stdout, err := cmd.Execf("systemctl %s %s", operate, c.serviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
|
||||
}
|
||||
return nil
|
||||
case "fresh-start", "fresh-restart", "fresh-stop":
|
||||
stdout, err := cmd.Execf("systemctl %s %s", strings.TrimPrefix(operate, "fresh-"), freshClamService)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, stdout)
|
||||
}
|
||||
return nil
|
||||
case "start":
|
||||
err = systemctl.Start(c.serviceName)
|
||||
case "stop":
|
||||
err = systemctl.Stop(c.serviceName)
|
||||
case "restart":
|
||||
err = systemctl.Restart(c.serviceName)
|
||||
case "fresh-start":
|
||||
err = systemctl.Start(c.freshClamService)
|
||||
case "fresh-stop":
|
||||
err = systemctl.Stop(c.freshClamService)
|
||||
case "fresh-restart":
|
||||
err = systemctl.Restart(c.freshClamService)
|
||||
default:
|
||||
return fmt.Errorf("not support such operation: %v", operate)
|
||||
return fmt.Errorf("unsupported operation: %s", operate)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s %s failed: %v", operate, c.serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) {
|
||||
@@ -432,102 +445,80 @@ func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) {
|
||||
filePath := ""
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
filePath = c.getConfigPath("clamd")
|
||||
case "clamd-log":
|
||||
filePath = c.loadLogPath("clamd-log")
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/clamav.log"
|
||||
} else {
|
||||
filePath = "/var/log/clamd.scan"
|
||||
}
|
||||
case "freshclam":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
}
|
||||
filePath = c.getConfigPath("freshclam")
|
||||
case "freshclam-log":
|
||||
filePath = c.loadLogPath("freshclam-log")
|
||||
if len(filePath) != 0 {
|
||||
break
|
||||
}
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/var/log/clamav/freshclam.log"
|
||||
} else {
|
||||
filePath = "/var/log/freshclam.log"
|
||||
}
|
||||
default:
|
||||
return "", fmt.Errorf("not support such type")
|
||||
return "", fmt.Errorf("unsupported file type")
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
|
||||
content, err := systemctl.ViewConfig(filePath, systemctl.ConfigOption{TailLines: req.Tail})
|
||||
if err != nil {
|
||||
return "", buserr.New("ErrHttpReqNotFound")
|
||||
}
|
||||
var tail string
|
||||
if req.Tail != "0" {
|
||||
tail = req.Tail
|
||||
} else {
|
||||
tail = "+1"
|
||||
}
|
||||
cmd := exec.Command("tail", "-n", tail, filePath)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err)
|
||||
}
|
||||
return string(stdout), nil
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error {
|
||||
filePath := ""
|
||||
service := ""
|
||||
var (
|
||||
filePath string
|
||||
service string
|
||||
)
|
||||
|
||||
switch req.Name {
|
||||
case "clamd":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
service = clamServiceNameUbuntu
|
||||
filePath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
service = clamServiceNameCentOs
|
||||
filePath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
filePath = c.getConfigPath("clamd")
|
||||
service = c.serviceName
|
||||
case "freshclam":
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
filePath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
filePath = "/etc/freshclam.conf"
|
||||
}
|
||||
service = "clamav-freshclam.service"
|
||||
filePath = c.getConfigPath("freshclam")
|
||||
service = c.freshClamService
|
||||
default:
|
||||
return fmt.Errorf("not support such type")
|
||||
return fmt.Errorf("unsupported file type")
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(req.File)
|
||||
write.Flush()
|
||||
|
||||
_ = systemctl.Restart(service)
|
||||
if _, err := file.WriteString(req.File); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := systemctl.Restart(service); err != nil {
|
||||
return fmt.Errorf("restart %s failed: %v", service, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClamService) getConfigPath(confType string) string {
|
||||
switch confType {
|
||||
case "clamd":
|
||||
if _, err := os.Stat("/etc/clamav/clamd.conf"); err == nil {
|
||||
return "/etc/clamav/clamd.conf"
|
||||
}
|
||||
return "/etc/clamd.d/scan.conf"
|
||||
case "freshclam":
|
||||
if _, err := os.Stat("/etc/clamav/freshclam.conf"); err == nil {
|
||||
return "/etc/clamav/freshclam.conf"
|
||||
}
|
||||
return "/etc/freshclam.conf"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func StopAllCronJob(withCheck bool) bool {
|
||||
if withCheck {
|
||||
isActive := false
|
||||
exist1, _ := systemctl.IsExist(clamServiceNameCentOs)
|
||||
if exist1 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameCentOs)
|
||||
}
|
||||
exist2, _ := systemctl.IsExist(clamServiceNameUbuntu)
|
||||
if exist2 {
|
||||
isActive, _ = systemctl.IsActive(clamServiceNameUbuntu)
|
||||
isexist, _ := systemctl.IsExist(clamServiceKey)
|
||||
if isexist {
|
||||
isActive, _ = systemctl.IsActive(clamServiceKey)
|
||||
}
|
||||
if isActive {
|
||||
return false
|
||||
@@ -590,42 +581,25 @@ func loadResultFromLog(pathItem string) dto.ClamLog {
|
||||
return data
|
||||
}
|
||||
func (c *ClamService) loadLogPath(name string) string {
|
||||
confPath := ""
|
||||
if name == "clamd-log" {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/clamd.conf"
|
||||
} else {
|
||||
confPath = "/etc/clamd.d/scan.conf"
|
||||
}
|
||||
} else {
|
||||
if c.serviceName == clamServiceNameUbuntu {
|
||||
confPath = "/etc/clamav/freshclam.conf"
|
||||
} else {
|
||||
confPath = "/etc/freshclam.conf"
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(confPath); err != nil {
|
||||
return ""
|
||||
configKey := "clamd"
|
||||
searchPrefix := "LogFile "
|
||||
if name != "clamd-log" {
|
||||
configKey = "freshclam"
|
||||
searchPrefix = "UpdateLogFile "
|
||||
}
|
||||
confPath := c.getConfigPath(configKey)
|
||||
|
||||
content, err := os.ReadFile(confPath)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("Failed to read %s config: %v", configKey, err)
|
||||
return ""
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
if name == "clamd-log" {
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "LogFile ") {
|
||||
return strings.Trim(strings.ReplaceAll(line, "LogFile ", ""), " ")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "UpdateLogFile ") {
|
||||
return strings.Trim(strings.ReplaceAll(line, "UpdateLogFile ", ""), " ")
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(content), "\n") {
|
||||
if strings.HasPrefix(line, searchPrefix) {
|
||||
return strings.TrimSpace(strings.TrimPrefix(line, searchPrefix))
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
@@ -255,14 +255,14 @@ func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datas []dto.ContainerListStats
|
||||
datas := make([]dto.ContainerListStats, len(list))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(list))
|
||||
for i := 0; i < len(list); i++ {
|
||||
go func(item types.Container) {
|
||||
datas = append(datas, loadCpuAndMem(client, item.ID))
|
||||
go func(index int, item types.Container) {
|
||||
datas[index] = loadCpuAndMem(client, item.ID)
|
||||
wg.Done()
|
||||
}(list[i])
|
||||
}(i, list[i])
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
@@ -450,12 +450,14 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
networkSettings := oldContainer.NetworkSettings
|
||||
bridgeNetworkSettings := networkSettings.Networks[data.Network]
|
||||
if bridgeNetworkSettings.IPAMConfig != nil {
|
||||
ipv4Address := bridgeNetworkSettings.IPAMConfig.IPv4Address
|
||||
data.Ipv4 = ipv4Address
|
||||
ipv6Address := bridgeNetworkSettings.IPAMConfig.IPv6Address
|
||||
data.Ipv6 = ipv6Address
|
||||
if data.Network != "bridge" {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAMConfig.IPv4Address
|
||||
data.Ipv6 = bridgeNetworkSettings.IPAMConfig.IPv6Address
|
||||
}
|
||||
} else {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAddress
|
||||
if data.Network != "bridge" {
|
||||
data.Ipv4 = bridgeNetworkSettings.IPAddress
|
||||
}
|
||||
}
|
||||
|
||||
data.Cmd = oldContainer.Config.Cmd
|
||||
@@ -556,10 +558,7 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
|
||||
hostConf := oldContainer.HostConfig
|
||||
var networkConf network.NetworkingConfig
|
||||
if oldContainer.NetworkSettings != nil {
|
||||
for networkKey := range oldContainer.NetworkSettings.Networks {
|
||||
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
|
||||
break
|
||||
}
|
||||
networkConf.EndpointsConfig = oldContainer.NetworkSettings.Networks
|
||||
}
|
||||
if err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true}); err != nil {
|
||||
return err
|
||||
@@ -937,15 +936,27 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
|
||||
}
|
||||
return cpuPercent
|
||||
}
|
||||
|
||||
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
|
||||
memPercent := 0.0
|
||||
memUsage := float64(memStats.Usage)
|
||||
memLimit := float64(memStats.Limit)
|
||||
memUsage := calculateMemUsageUnixNoCache(memStats)
|
||||
memLimit := memStats.Limit
|
||||
if memUsage > 0.0 && memLimit > 0.0 {
|
||||
memPercent = (memUsage / memLimit) * 100.0
|
||||
memPercent = (float64(memUsage) / float64(memLimit)) * 100.0
|
||||
}
|
||||
return memPercent
|
||||
}
|
||||
|
||||
func calculateMemUsageUnixNoCache(mem types.MemoryStats) uint64 {
|
||||
if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage {
|
||||
return mem.Usage - v
|
||||
}
|
||||
if v := mem.Stats["inactive_file"]; v < mem.Usage {
|
||||
return mem.Usage - v
|
||||
}
|
||||
return mem.Usage
|
||||
}
|
||||
|
||||
func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) {
|
||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
||||
switch strings.ToLower(bioEntry.Op) {
|
||||
@@ -1063,7 +1074,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
|
||||
data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage)
|
||||
|
||||
data.MemoryCache = stats.MemoryStats.Stats["cache"]
|
||||
data.MemoryUsage = stats.MemoryStats.Usage
|
||||
data.MemoryUsage = calculateMemUsageUnixNoCache(stats.MemoryStats)
|
||||
data.MemoryLimit = stats.MemoryStats.Limit
|
||||
|
||||
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
|
||||
@@ -1183,9 +1194,10 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types
|
||||
for _, volume := range req.Volumes {
|
||||
if volume.Type == "volume" {
|
||||
hostConf.Mounts = append(hostConf.Mounts, mount.Mount{
|
||||
Type: mount.Type(volume.Type),
|
||||
Source: volume.SourceDir,
|
||||
Target: volume.ContainerDir,
|
||||
Type: mount.Type(volume.Type),
|
||||
Source: volume.SourceDir,
|
||||
Target: volume.ContainerDir,
|
||||
ReadOnly: volume.Mode == "ro",
|
||||
})
|
||||
config.Volumes[volume.ContainerDir] = struct{}{}
|
||||
} else {
|
||||
@@ -1200,10 +1212,7 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C
|
||||
|
||||
var oldNetworkConf network.NetworkingConfig
|
||||
if networkConf != nil {
|
||||
for networkKey := range networkConf.Networks {
|
||||
oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}}
|
||||
break
|
||||
}
|
||||
oldNetworkConf.EndpointsConfig = networkConf.Networks
|
||||
}
|
||||
|
||||
oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name)
|
||||
|
||||
@@ -114,8 +114,17 @@ func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !req.CleanRemoteData {
|
||||
for key := range accountMap {
|
||||
if key != constant.Local {
|
||||
delete(accountMap, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
cronjob.RetainCopies = 0
|
||||
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
|
||||
if len(accountMap) != 0 {
|
||||
u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{})
|
||||
}
|
||||
} else {
|
||||
u.removeExpiredLog(cronjob)
|
||||
}
|
||||
@@ -249,7 +258,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
|
||||
global.Cron.Remove(cron.EntryID(idItem))
|
||||
}
|
||||
global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs)
|
||||
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, IsDelete: true}); err != nil {
|
||||
if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, CleanRemoteData: req.CleanRemoteData, IsDelete: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
|
||||
|
||||
@@ -322,7 +322,8 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, accountMap
|
||||
if len(account) != 0 {
|
||||
global.LOG.Debugf("start upload file to %s, dir: %s", account, path.Join(accountMap[account].backupPath, cloudSrc))
|
||||
if _, err := accountMap[account].client.Upload(file, path.Join(accountMap[account].backupPath, cloudSrc)); err != nil {
|
||||
return "", err
|
||||
global.LOG.Errorf("upload file to %s failed, err: %v", account, err)
|
||||
continue
|
||||
}
|
||||
global.LOG.Debugf("upload successful!")
|
||||
}
|
||||
@@ -349,12 +350,18 @@ func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap m
|
||||
if cronjob.Type == "snapshot" {
|
||||
for _, account := range accounts {
|
||||
if len(account) != 0 {
|
||||
if _, ok := accountMap[account]; !ok {
|
||||
continue
|
||||
}
|
||||
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, "system_snapshot", records[i].FileName))
|
||||
}
|
||||
}
|
||||
_ = snapshotRepo.Delete(commonRepo.WithByName(strings.TrimSuffix(records[i].FileName, ".tar.gz")))
|
||||
} else {
|
||||
for _, account := range accounts {
|
||||
if _, ok := accountMap[account]; !ok {
|
||||
continue
|
||||
}
|
||||
if len(account) != 0 {
|
||||
_, _ = accountMap[account].client.Delete(path.Join(accountMap[account].backupPath, records[i].FileDir, records[i].FileName))
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
@@ -121,7 +122,7 @@ func (u *DeviceService) Update(key, value string) error {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system for new time zone failed, err: %v", err)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
fileUtils "github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/google/uuid"
|
||||
@@ -285,7 +285,7 @@ func (u *DeviceService) Clean(req []dto.Clean) {
|
||||
|
||||
if restart {
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system port failed, err: %v", err)
|
||||
}
|
||||
|
||||
@@ -365,17 +365,18 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
||||
|
||||
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
service := "docker"
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
dockerCmd, err := getDockerRestartCommand()
|
||||
h, err := systemctl.DefaultHandler(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Operation == "stop" {
|
||||
isSocketActive, _ := systemctl.IsActive("docker.socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop docker.socket", sudo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop docker.socket failed, err: %v", std)
|
||||
socketHandle, err := systemctl.DefaultHandler("docker.socket")
|
||||
if err == nil {
|
||||
status, err := socketHandle.IsActive()
|
||||
if err == nil && status.IsActive {
|
||||
if std, err := socketHandle.ExecuteAction("stop"); err != nil {
|
||||
global.LOG.Errorf("handle stop docker.socket failed, err: %v", std)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,13 +387,20 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("%s %s %s", dockerCmd, req.Operation, service)
|
||||
if isDockerSnapInstalled() {
|
||||
command := fmt.Sprintf("snap %s docker", req.Operation)
|
||||
stdout, err := cmd.Exec(command)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart docker: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
result, err := h.ExecuteAction(req.Operation)
|
||||
if err != nil {
|
||||
return errors.New(stdout)
|
||||
return errors.New(result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changeLogOption(daemonMap map[string]interface{}, logMaxFile, logMaxSize string) {
|
||||
if opts, ok := daemonMap["log-opts"]; ok {
|
||||
if len(logMaxFile) != 0 || len(logMaxSize) != 0 {
|
||||
@@ -451,31 +459,27 @@ func validateDockerConfig() error {
|
||||
return nil
|
||||
}
|
||||
if err != nil || (stdout != "" && strings.TrimSpace(stdout) != "configuration OK") {
|
||||
return fmt.Errorf("Docker configuration validation failed, err: %v", stdout)
|
||||
return fmt.Errorf("docker configuration validation failed, err: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDockerRestartCommand() (string, error) {
|
||||
func isDockerSnapInstalled() bool {
|
||||
stdout, err := cmd.Exec("which docker")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to find docker: %v", err)
|
||||
return false
|
||||
}
|
||||
dockerPath := stdout
|
||||
if strings.Contains(dockerPath, "snap") {
|
||||
return "snap", nil
|
||||
}
|
||||
return "systemctl", nil
|
||||
stdout = strings.TrimSpace(stdout)
|
||||
return strings.Contains(stdout, "snap")
|
||||
}
|
||||
|
||||
func restartDocker() error {
|
||||
restartCmd, err := getDockerRestartCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
if isDockerSnapInstalled() {
|
||||
stdout, err := cmd.Exec("snap restart docker")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart docker: %v", stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
stdout, err := cmd.Execf("%s restart docker", restartCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restart Docker: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
return systemctl.Restart("docker")
|
||||
}
|
||||
|
||||
@@ -46,4 +46,6 @@ var (
|
||||
phpExtensionsRepo = repo.NewIPHPExtensionsRepo()
|
||||
|
||||
favoriteRepo = repo.NewIFavoriteRepo()
|
||||
|
||||
mcpServerRepo = repo.NewIMcpServerRepo()
|
||||
)
|
||||
|
||||
@@ -366,10 +366,18 @@ func (f *FileService) MvFile(m request.FileMove) error {
|
||||
return buserr.New(constant.ErrMovePathFailed)
|
||||
}
|
||||
}
|
||||
var errs []error
|
||||
if m.Type == "cut" {
|
||||
if len(m.CoverPaths) > 0 {
|
||||
for _, src := range m.CoverPaths {
|
||||
if err := fo.CopyAndReName(src, m.NewPath, "", true); err != nil {
|
||||
errs = append(errs, err)
|
||||
global.LOG.Errorf("cut copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return fo.Cut(m.OldPaths, m.NewPath, m.Name, m.Cover)
|
||||
}
|
||||
var errs []error
|
||||
if m.Type == "copy" {
|
||||
for _, src := range m.OldPaths {
|
||||
if err := fo.CopyAndReName(src, m.NewPath, m.Name, m.Cover); err != nil {
|
||||
@@ -518,6 +526,7 @@ func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.Exi
|
||||
Name: info.Name(),
|
||||
Path: filePath,
|
||||
ModTime: info.ModTime(),
|
||||
IsDir: info.IsDir(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@ package service
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
@@ -14,11 +20,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/ini.v1"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HostToolService struct{}
|
||||
@@ -39,93 +40,108 @@ func NewIHostToolService() IHostToolService {
|
||||
}
|
||||
|
||||
func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) {
|
||||
res := &response.HostToolRes{}
|
||||
res.Type = req.Type
|
||||
res := &response.HostToolRes{Type: req.Type}
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
supervisorConfig := &response.Supervisor{}
|
||||
if !cmd.Which(constant.Supervisord) {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.IsExist = true
|
||||
serviceExist, _ := systemctl.IsExist(constant.Supervisord)
|
||||
if !serviceExist {
|
||||
serviceExist, _ = systemctl.IsExist(constant.Supervisor)
|
||||
if !serviceExist {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
} else {
|
||||
supervisorConfig.ServiceName = constant.Supervisor
|
||||
}
|
||||
} else {
|
||||
supervisorConfig.ServiceName = constant.Supervisord
|
||||
}
|
||||
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
supervisorConfig.ServiceName = serviceNameSet.Value
|
||||
}
|
||||
|
||||
versionRes, _ := cmd.Exec("supervisord -v")
|
||||
supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n")
|
||||
_, ctlRrr := exec.LookPath("supervisorctl")
|
||||
supervisorConfig.CtlExist = ctlRrr == nil
|
||||
|
||||
active, _ := systemctl.IsActive(supervisorConfig.ServiceName)
|
||||
if active {
|
||||
supervisorConfig.Status = "running"
|
||||
} else {
|
||||
supervisorConfig.Status = "stopped"
|
||||
}
|
||||
|
||||
pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath))
|
||||
if pathSet.ID != 0 || pathSet.Value != "" {
|
||||
supervisorConfig.ConfigPath = pathSet.Value
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
} else {
|
||||
supervisorConfig.Init = true
|
||||
}
|
||||
|
||||
servicePath := "/usr/lib/systemd/system/supervisor.service"
|
||||
fileOp := files.NewFileOp()
|
||||
if !fileOp.Stat(servicePath) {
|
||||
servicePath = "/usr/lib/systemd/system/supervisord.service"
|
||||
}
|
||||
if fileOp.Stat(servicePath) {
|
||||
startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart")
|
||||
if startCmd != "" {
|
||||
args := strings.Fields(startCmd)
|
||||
cIndex := -1
|
||||
for i, arg := range args {
|
||||
if arg == "-c" {
|
||||
cIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if cIndex != -1 && cIndex+1 < len(args) {
|
||||
supervisorConfig.ConfigPath = args[cIndex+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if supervisorConfig.ConfigPath == "" {
|
||||
configPath := "/etc/supervisord.conf"
|
||||
if !fileOp.Stat(configPath) {
|
||||
configPath = "/etc/supervisor/supervisord.conf"
|
||||
if fileOp.Stat(configPath) {
|
||||
supervisorConfig.ConfigPath = configPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.Config = supervisorConfig
|
||||
return h.getSupervisorStatus(res)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) getSupervisorStatus(res *response.HostToolRes) (*response.HostToolRes, error) {
|
||||
supervisorConfig := &response.Supervisor{}
|
||||
if !cmd.Which(constant.Supervisord) {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.IsExist = true
|
||||
|
||||
serviceName, err := h.determineServiceName()
|
||||
if err != nil || serviceName == "" {
|
||||
supervisorConfig.IsExist = false
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
supervisorConfig.ServiceName = serviceName
|
||||
|
||||
if nameSetting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)); nameSetting.Value != "" {
|
||||
supervisorConfig.ServiceName = nameSetting.Value
|
||||
}
|
||||
|
||||
if version, err := cmd.Exec("supervisord -v"); err == nil {
|
||||
supervisorConfig.Version = strings.TrimSpace(version)
|
||||
}
|
||||
|
||||
_, errCtl := exec.LookPath("supervisorctl")
|
||||
supervisorConfig.CtlExist = errCtl == nil
|
||||
|
||||
if active, err := systemctl.IsActive(supervisorConfig.ServiceName); err == nil && active {
|
||||
supervisorConfig.Status = "running"
|
||||
} else {
|
||||
supervisorConfig.Status = "stopped"
|
||||
}
|
||||
|
||||
h.resolveConfigPath(supervisorConfig)
|
||||
|
||||
res.Config = supervisorConfig
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *HostToolService) determineServiceName() (string, error) {
|
||||
if setting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)); setting.Value != "" {
|
||||
serviceName, err := systemctl.GetServiceName(setting.Value)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get service name error: %v", err)
|
||||
}
|
||||
return serviceName, nil
|
||||
}
|
||||
if serviceName, err := systemctl.GetServiceName(constant.Supervisord); err == nil {
|
||||
return serviceName, nil
|
||||
}
|
||||
return systemctl.GetServiceName(constant.Supervisor)
|
||||
}
|
||||
|
||||
func (h *HostToolService) resolveConfigPath(config *response.Supervisor) {
|
||||
if pathSetting, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)); pathSetting.Value != "" {
|
||||
config.ConfigPath = pathSetting.Value
|
||||
return
|
||||
}
|
||||
|
||||
config.Init = true
|
||||
|
||||
if servicePath, err := systemctl.GetServicePath(config.ServiceName); err == nil {
|
||||
if startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart"); startCmd != "" {
|
||||
if path := parseConfigPathFromCommand(startCmd); path != "" {
|
||||
config.ConfigPath = path
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultPaths := []string{
|
||||
"/etc/supervisord.conf",
|
||||
"/etc/supervisor/supervisord.conf",
|
||||
"/usr/local/etc/supervisord.conf",
|
||||
}
|
||||
for _, path := range defaultPaths {
|
||||
if systemctl.FileExist(path) {
|
||||
config.ConfigPath = path
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseConfigPathFromCommand(cmd string) string {
|
||||
parts := strings.Fields(cmd)
|
||||
for i, part := range parts {
|
||||
if part == "-c" && i+1 < len(parts) {
|
||||
return parts[i+1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
@@ -200,14 +216,28 @@ func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error {
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateTool(req request.HostToolReq) error {
|
||||
serviceName := req.Type
|
||||
if req.Type == constant.Supervisord {
|
||||
var serviceName string
|
||||
var err error
|
||||
switch req.Type {
|
||||
case constant.Supervisord:
|
||||
serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName))
|
||||
if serviceNameSet.ID != 0 || serviceNameSet.Value != "" {
|
||||
serviceName = serviceNameSet.Value
|
||||
} else {
|
||||
serviceName = req.Type
|
||||
}
|
||||
default:
|
||||
serviceName = req.Type
|
||||
}
|
||||
return systemctl.Operate(req.Operate, serviceName)
|
||||
serviceName, err = systemctl.GetServiceName(serviceName)
|
||||
if err != nil {
|
||||
if errors.Is(err, systemctl.ErrServiceNotFound) {
|
||||
return fmt.Errorf("%s service unavailable. Please ensure supervisor server is installed and configured", serviceName)
|
||||
}
|
||||
return err
|
||||
}
|
||||
_, err = systemctl.CustomAction(req.Operate, serviceName)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) {
|
||||
|
||||
@@ -26,7 +26,7 @@ type IImageRepoService interface {
|
||||
Login(req dto.OperateByID) error
|
||||
Create(req dto.ImageRepoCreate) error
|
||||
Update(req dto.ImageRepoUpdate) error
|
||||
BatchDelete(req dto.ImageRepoDelete) error
|
||||
Delete(req dto.OperateByID) error
|
||||
}
|
||||
|
||||
func NewIImageRepoService() IImageRepoService {
|
||||
@@ -89,31 +89,7 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
|
||||
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
|
||||
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
|
||||
}
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
if err := func() error {
|
||||
for range ticker.C {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
cancel()
|
||||
return errors.New("the docker service cannot be restarted")
|
||||
default:
|
||||
stdout, err := cmd.Exec("systemctl is-active docker")
|
||||
if string(stdout) == "active\n" && err == nil {
|
||||
global.LOG.Info("docker restart with new conf successful!")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
if err := stopBeforeUpdateRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -131,15 +107,32 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
|
||||
return imageRepoRepo.Create(&imageRepo)
|
||||
}
|
||||
|
||||
func (u *ImageRepoService) BatchDelete(req dto.ImageRepoDelete) error {
|
||||
for _, id := range req.Ids {
|
||||
if id == 1 {
|
||||
return errors.New("The default value cannot be edit !")
|
||||
}
|
||||
func (u *ImageRepoService) Delete(req dto.OperateByID) error {
|
||||
if req.ID == 1 {
|
||||
return errors.New("The default value cannot be edit !")
|
||||
}
|
||||
if err := imageRepoRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
|
||||
itemRepo, _ := imageRepoRepo.Get(commonRepo.WithByID(req.ID))
|
||||
if itemRepo.ID == 0 {
|
||||
return buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
if itemRepo.Auth {
|
||||
_, _ = cmd.Execf("docker logout -i %s", itemRepo.DownloadUrl)
|
||||
}
|
||||
if itemRepo.Protocol == "https" {
|
||||
return imageRepoRepo.Delete(commonRepo.WithByID(req.ID))
|
||||
}
|
||||
if err := u.handleRegistries("", itemRepo.DownloadUrl, "delete"); err != nil {
|
||||
return fmt.Errorf("delete registry %s failed, err: %v", itemRepo.DownloadUrl, err)
|
||||
}
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := imageRepoRepo.Delete(commonRepo.WithByID(req.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_ = restartDocker()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -154,37 +147,39 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRestart := false
|
||||
if repo.Protocol == "http" && req.Protocol == "https" {
|
||||
if err := u.handleRegistries("", repo.DownloadUrl, "delete"); err != nil {
|
||||
return fmt.Errorf("delete registry %s failed, err: %v", repo.DownloadUrl, err)
|
||||
}
|
||||
needRestart = true
|
||||
}
|
||||
if repo.Protocol == "http" && req.Protocol == "http" {
|
||||
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
|
||||
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
|
||||
if repo.DownloadUrl != req.DownloadUrl {
|
||||
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
|
||||
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
|
||||
}
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
if repo.Protocol == "https" && req.Protocol == "http" {
|
||||
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
|
||||
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
|
||||
}
|
||||
needRestart = true
|
||||
}
|
||||
if repo.Auth != req.Auth || repo.DownloadUrl != req.DownloadUrl {
|
||||
if repo.Auth {
|
||||
_, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
|
||||
}
|
||||
if req.Auth {
|
||||
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
if needRestart {
|
||||
if err := stopBeforeUpdateRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
if repo.Auth {
|
||||
_, _ = cmd.Execf("docker logout -i %s", repo.DownloadUrl)
|
||||
}
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
if req.Auth {
|
||||
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
@@ -256,3 +251,34 @@ func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stopBeforeUpdateRepo() error {
|
||||
if err := validateDockerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := restartDocker(); err != nil {
|
||||
return err
|
||||
}
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
if err := func() error {
|
||||
for range ticker.C {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
cancel()
|
||||
return errors.New("the docker service cannot be restarted")
|
||||
default:
|
||||
stdout, err := cmd.Exec("systemctl is-active docker")
|
||||
if string(stdout) == "active\n" && err == nil {
|
||||
global.LOG.Info("docker restart with new conf successful!")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,643 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/compose"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/nginx/parser"
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/mcp"
|
||||
"github.com/1Panel-dev/1Panel/cmd/server/nginx_conf"
|
||||
"github.com/subosito/gotenv"
|
||||
"gopkg.in/yaml.v3"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type McpServerService struct{}
|
||||
|
||||
type IMcpServerService interface {
|
||||
Page(req request.McpServerSearch) response.McpServersRes
|
||||
Create(create request.McpServerCreate) error
|
||||
Update(req request.McpServerUpdate) error
|
||||
Delete(id uint) error
|
||||
Operate(req request.McpServerOperate) error
|
||||
GetBindDomain() (response.McpBindDomainRes, error)
|
||||
BindDomain(req request.McpBindDomain) error
|
||||
UpdateBindDomain(req request.McpBindDomainUpdate) error
|
||||
}
|
||||
|
||||
func NewIMcpServerService() IMcpServerService {
|
||||
return &McpServerService{}
|
||||
}
|
||||
|
||||
func (m McpServerService) Page(req request.McpServerSearch) response.McpServersRes {
|
||||
var (
|
||||
res response.McpServersRes
|
||||
items []response.McpServerDTO
|
||||
)
|
||||
|
||||
total, data, _ := mcpServerRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize)
|
||||
for _, item := range data {
|
||||
_ = syncMcpServerContainerStatus(&item)
|
||||
serverDTO := response.McpServerDTO{
|
||||
McpServer: item,
|
||||
Environments: make([]request.Environment, 0),
|
||||
Volumes: make([]request.Volume, 0),
|
||||
}
|
||||
project, _ := docker.GetComposeProject(item.Name, path.Join(constant.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true)
|
||||
for _, service := range project.Services {
|
||||
if service.Environment != nil {
|
||||
for key, value := range service.Environment {
|
||||
serverDTO.Environments = append(serverDTO.Environments, request.Environment{
|
||||
Key: key,
|
||||
Value: *value,
|
||||
})
|
||||
}
|
||||
}
|
||||
if service.Volumes != nil {
|
||||
for _, volume := range service.Volumes {
|
||||
serverDTO.Volumes = append(serverDTO.Volumes, request.Volume{
|
||||
Source: volume.Source,
|
||||
Target: volume.Target,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
items = append(items, serverDTO)
|
||||
}
|
||||
res.Total = total
|
||||
res.Items = items
|
||||
return res
|
||||
}
|
||||
|
||||
func (m McpServerService) Update(req request.McpServerUpdate) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mcpServer.Port != req.Port {
|
||||
if err := checkPortExist(req.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if mcpServer.ContainerName != req.ContainerName {
|
||||
if err := checkContainerName(req.ContainerName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
mcpServer.Name = req.Name
|
||||
mcpServer.ContainerName = req.ContainerName
|
||||
mcpServer.Port = req.Port
|
||||
mcpServer.Command = req.Command
|
||||
mcpServer.BaseURL = req.BaseURL
|
||||
mcpServer.SsePath = req.SsePath
|
||||
mcpServer.HostIP = req.HostIP
|
||||
if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil {
|
||||
return err
|
||||
}
|
||||
env := handleEnv(mcpServer)
|
||||
mcpDir := path.Join(constant.McpDir, mcpServer.Name)
|
||||
envPath := path.Join(mcpDir, ".env")
|
||||
if err := gotenv.Write(env, envPath); err != nil {
|
||||
return err
|
||||
}
|
||||
dockerComposePath := path.Join(mcpDir, "docker-compose.yml")
|
||||
if err := files.NewFileOp().SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
mcpServer.Status = constant.RuntimeStarting
|
||||
if err := mcpServerRepo.Save(mcpServer); err != nil {
|
||||
return err
|
||||
}
|
||||
go startMcp(mcpServer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m McpServerService) Create(create request.McpServerCreate) error {
|
||||
servers, _ := mcpServerRepo.List()
|
||||
for _, server := range servers {
|
||||
if server.Port == create.Port {
|
||||
return buserr.New("ErrPortInUsed")
|
||||
}
|
||||
if server.ContainerName == create.ContainerName {
|
||||
return buserr.New("ErrContainerName")
|
||||
}
|
||||
if server.Name == create.Name {
|
||||
return buserr.New(constant.ErrNameIsExist)
|
||||
}
|
||||
if server.SsePath == create.SsePath {
|
||||
return buserr.New("ErrSsePath")
|
||||
}
|
||||
}
|
||||
if err := checkPortExist(create.Port); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkContainerName(create.ContainerName); err != nil {
|
||||
return err
|
||||
}
|
||||
mcpDir := path.Join(constant.McpDir, create.Name)
|
||||
mcpServer := &model.McpServer{
|
||||
Name: create.Name,
|
||||
ContainerName: create.ContainerName,
|
||||
Port: create.Port,
|
||||
Command: create.Command,
|
||||
Status: constant.RuntimeStarting,
|
||||
BaseURL: create.BaseURL,
|
||||
SsePath: create.SsePath,
|
||||
Dir: mcpDir,
|
||||
HostIP: create.HostIP,
|
||||
}
|
||||
if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env := handleEnv(mcpServer)
|
||||
filesOP := files.NewFileOp()
|
||||
if !filesOP.Stat(mcpDir) {
|
||||
_ = filesOP.CreateDir(mcpDir, 0644)
|
||||
}
|
||||
envPath := path.Join(mcpDir, ".env")
|
||||
if err := gotenv.Write(env, envPath); err != nil {
|
||||
return err
|
||||
}
|
||||
dockerComposePath := path.Join(mcpDir, "docker-compose.yml")
|
||||
if err := filesOP.SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mcpServerRepo.Create(mcpServer); err != nil {
|
||||
return err
|
||||
}
|
||||
addProxy(mcpServer)
|
||||
go startMcp(mcpServer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m McpServerService) Delete(id uint) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
_, _ = compose.Down(composePath)
|
||||
_ = files.NewFileOp().DeleteDir(path.Join(constant.McpDir, mcpServer.Name))
|
||||
|
||||
websiteID := GetWebsiteID()
|
||||
if websiteID > 0 {
|
||||
websiteService := NewIWebsiteService()
|
||||
delProxyReq := request.WebsiteProxyDel{
|
||||
ID: websiteID,
|
||||
Name: mcpServer.Name,
|
||||
}
|
||||
_ = websiteService.DeleteProxy(delProxyReq)
|
||||
}
|
||||
return mcpServerRepo.DeleteBy(commonRepo.WithByID(id))
|
||||
}
|
||||
|
||||
func (m McpServerService) Operate(req request.McpServerOperate) error {
|
||||
mcpServer, err := mcpServerRepo.GetFirst(commonRepo.WithByID(req.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
composePath := path.Join(mcpServer.Dir, "docker-compose.yml")
|
||||
var out string
|
||||
switch req.Operate {
|
||||
case "start":
|
||||
out, err = compose.Up(composePath)
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
case "stop":
|
||||
out, err = compose.Down(composePath)
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
case "restart":
|
||||
out, err = compose.Restart(composePath)
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
}
|
||||
if err != nil {
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
mcpServer.Message = out
|
||||
}
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
|
||||
func (m McpServerService) GetBindDomain() (response.McpBindDomainRes, error) {
|
||||
var res response.McpBindDomainRes
|
||||
websiteID := GetWebsiteID()
|
||||
if websiteID == 0 {
|
||||
return res, nil
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
}
|
||||
res.WebsiteID = website.ID
|
||||
res.Domain = website.PrimaryDomain
|
||||
if website.WebsiteSSLID > 0 {
|
||||
res.SSLID = website.WebsiteSSLID
|
||||
ssl, _ := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID))
|
||||
res.AcmeAccountID = ssl.AcmeAccountID
|
||||
}
|
||||
res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain)
|
||||
res.AllowIPs = GetAllowIps(website)
|
||||
return res, nil
|
||||
|
||||
}
|
||||
|
||||
func (m McpServerService) BindDomain(req request.McpBindDomain) error {
|
||||
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
|
||||
if nginxInstall.ID == 0 {
|
||||
return buserr.New("ErrOpenrestyInstall")
|
||||
}
|
||||
var (
|
||||
ipList []string
|
||||
err error
|
||||
)
|
||||
if len(req.IPList) > 0 {
|
||||
ipList, err = common.HandleIPList(req.IPList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
group, _ := groupRepo.Get(groupRepo.WithByWebsiteDefault())
|
||||
createWebsiteReq := request.WebsiteCreate{
|
||||
PrimaryDomain: req.Domain,
|
||||
Alias: strings.ToLower(req.Domain),
|
||||
Type: constant.Static,
|
||||
WebsiteGroupID: group.ID,
|
||||
}
|
||||
websiteService := NewIWebsiteService()
|
||||
if err := websiteService.CreateWebsite(createWebsiteReq); err != nil {
|
||||
return err
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = settingRepo.UpdateOrCreate("MCP_WEBSITE_ID", fmt.Sprintf("%d", website.ID))
|
||||
if len(ipList) > 0 {
|
||||
if err = ConfigAllowIPs(ipList, website); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: true,
|
||||
Type: "existed",
|
||||
WebsiteSSLID: req.SSLID,
|
||||
HttpConfig: "HTTPSOnly",
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = addMCPProxy(website.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m McpServerService) UpdateBindDomain(req request.McpBindDomainUpdate) error {
|
||||
nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty)
|
||||
if nginxInstall.ID == 0 {
|
||||
return buserr.New("ErrOpenrestyInstall")
|
||||
}
|
||||
var (
|
||||
ipList []string
|
||||
err error
|
||||
)
|
||||
if len(req.IPList) > 0 {
|
||||
ipList, err = common.HandleIPList(req.IPList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.SSLID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ssl.Pem == "" {
|
||||
return buserr.New("ErrSSL")
|
||||
}
|
||||
}
|
||||
websiteService := NewIWebsiteService()
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ConfigAllowIPs(ipList, website); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.SSLID > 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: true,
|
||||
Type: "existed",
|
||||
WebsiteSSLID: req.SSLID,
|
||||
HttpConfig: "HTTPSOnly",
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if website.WebsiteSSLID > 0 && req.SSLID == 0 {
|
||||
sslReq := request.WebsiteHTTPSOp{
|
||||
WebsiteID: website.ID,
|
||||
Enable: false,
|
||||
}
|
||||
if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
go updateMcpConfig(website.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateMcpConfig(websiteID uint) {
|
||||
servers, _ := mcpServerRepo.List()
|
||||
if len(servers) == 0 {
|
||||
return
|
||||
}
|
||||
website, _ := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
websiteDomain := website.Domains[0]
|
||||
var baseUrl string
|
||||
if website.Protocol == constant.ProtocolHTTP {
|
||||
baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain)
|
||||
} else {
|
||||
baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for _, server := range servers {
|
||||
if server.BaseURL != baseUrl {
|
||||
server.BaseURL = baseUrl
|
||||
server.HostIP = "127.0.0.1"
|
||||
_ = updateMcpServer(&server)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func addProxy(server *model.McpServer) {
|
||||
websiteID := GetWebsiteID()
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("[mcp] add proxy failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("[mcp] add proxy failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
if err = fileOp.CreateDir(includeDir, 0644); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
includePath := path.Join(includeDir, server.Name+".conf")
|
||||
config.FilePath = includePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
global.LOG.Errorf("write config failed, err: %v", buserr.WithErr(constant.ErrUpdateBuWebsite, err))
|
||||
return
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
global.LOG.Errorf("update nginx config failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func addMCPProxy(websiteID uint) error {
|
||||
servers, _ := mcpServerRepo.List()
|
||||
if len(servers) == 0 {
|
||||
return nil
|
||||
}
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileOp := files.NewFileOp()
|
||||
includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
|
||||
if !fileOp.Stat(includeDir) {
|
||||
if err = fileOp.CreateDir(includeDir, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
websiteDomain := website.Domains[0]
|
||||
var baseUrl string
|
||||
if website.Protocol == constant.ProtocolHTTP {
|
||||
baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain)
|
||||
} else {
|
||||
baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain)
|
||||
}
|
||||
if websiteDomain.Port != 80 && websiteDomain.Port != 443 {
|
||||
baseUrl = fmt.Sprintf("%s:%d", baseUrl, websiteDomain.Port)
|
||||
}
|
||||
for _, server := range servers {
|
||||
includePath := path.Join(includeDir, server.Name+".conf")
|
||||
config.FilePath = includePath
|
||||
directives := config.Directives
|
||||
location, ok := directives[0].(*components.Location)
|
||||
if !ok {
|
||||
err = errors.New("error")
|
||||
return err
|
||||
}
|
||||
location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, server.SsePath)})
|
||||
location.ChangePath("^~", server.SsePath)
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return buserr.WithErr(constant.ErrUpdateBuWebsite, err)
|
||||
}
|
||||
server.BaseURL = baseUrl
|
||||
server.HostIP = "127.0.0.1"
|
||||
go updateMcpServer(&server)
|
||||
}
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateMcpServer(mcpServer *model.McpServer) error {
|
||||
env := handleEnv(mcpServer)
|
||||
if err := gotenv.Write(env, path.Join(mcpServer.Dir, ".env")); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = mcpServerRepo.Save(mcpServer)
|
||||
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
_, _ = compose.Down(composePath)
|
||||
if _, err := compose.Up(composePath); err != nil {
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
mcpServer.Message = err.Error()
|
||||
}
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
|
||||
func handleEnv(mcpServer *model.McpServer) gotenv.Env {
|
||||
env := make(gotenv.Env)
|
||||
env["CONTAINER_NAME"] = mcpServer.ContainerName
|
||||
env["COMMAND"] = mcpServer.Command
|
||||
env["PANEL_APP_PORT_HTTP"] = strconv.Itoa(mcpServer.Port)
|
||||
env["BASE_URL"] = mcpServer.BaseURL
|
||||
env["SSE_PATH"] = mcpServer.SsePath
|
||||
env["HOST_IP"] = mcpServer.HostIP
|
||||
envStr, _ := gotenv.Marshal(env)
|
||||
mcpServer.Env = envStr
|
||||
return env
|
||||
}
|
||||
|
||||
func handleCreateParams(mcpServer *model.McpServer, environments []request.Environment, volumes []request.Volume) error {
|
||||
var composeContent []byte
|
||||
if mcpServer.ID == 0 {
|
||||
composeContent = mcp.DefaultMcpCompose
|
||||
} else {
|
||||
composeContent = []byte(mcpServer.DockerCompose)
|
||||
}
|
||||
composeMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(composeContent, &composeMap); err != nil {
|
||||
return err
|
||||
}
|
||||
services, serviceValid := composeMap["services"].(map[string]interface{})
|
||||
if !serviceValid {
|
||||
return buserr.New(constant.ErrFileParse)
|
||||
}
|
||||
serviceName := ""
|
||||
serviceValue := make(map[string]interface{})
|
||||
|
||||
if mcpServer.ID > 0 {
|
||||
serviceName = mcpServer.Name
|
||||
serviceValue = services[serviceName].(map[string]interface{})
|
||||
} else {
|
||||
for name, service := range services {
|
||||
serviceName = name
|
||||
serviceValue = service.(map[string]interface{})
|
||||
break
|
||||
}
|
||||
delete(services, serviceName)
|
||||
}
|
||||
delete(serviceValue, "environment")
|
||||
if len(environments) > 0 {
|
||||
envMap := make(map[string]string)
|
||||
for _, env := range environments {
|
||||
envMap[env.Key] = env.Value
|
||||
}
|
||||
serviceValue["environment"] = envMap
|
||||
}
|
||||
delete(serviceValue, "volumes")
|
||||
if len(volumes) > 0 {
|
||||
volumeList := make([]string, 0)
|
||||
for _, volume := range volumes {
|
||||
volumeList = append(volumeList, fmt.Sprintf("%s:%s", volume.Source, volume.Target))
|
||||
}
|
||||
serviceValue["volumes"] = volumeList
|
||||
}
|
||||
|
||||
services[mcpServer.Name] = serviceValue
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mcpServer.DockerCompose = string(composeByte)
|
||||
return nil
|
||||
}
|
||||
|
||||
func startMcp(mcpServer *model.McpServer) {
|
||||
composePath := path.Join(constant.McpDir, mcpServer.Name, "docker-compose.yml")
|
||||
if mcpServer.Status != constant.RuntimeNormal {
|
||||
_, _ = compose.Down(composePath)
|
||||
}
|
||||
if out, err := compose.Up(composePath); err != nil {
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
mcpServer.Message = out
|
||||
} else {
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
mcpServer.Message = ""
|
||||
}
|
||||
_ = syncMcpServerContainerStatus(mcpServer)
|
||||
}
|
||||
|
||||
func syncMcpServerContainerStatus(mcpServer *model.McpServer) error {
|
||||
containerNames := []string{mcpServer.ContainerName}
|
||||
cli, err := docker.NewClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cli.Close()
|
||||
containers, err := cli.ListContainersByName(containerNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
container := containers[0]
|
||||
switch container.State {
|
||||
case "exited":
|
||||
mcpServer.Status = constant.RuntimeError
|
||||
case "running":
|
||||
mcpServer.Status = constant.RuntimeRunning
|
||||
case "paused":
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
case "restarting":
|
||||
mcpServer.Status = constant.RuntimeReStarting
|
||||
default:
|
||||
if mcpServer.Status != constant.RuntimeBuildIng {
|
||||
mcpServer.Status = constant.RuntimeStopped
|
||||
}
|
||||
}
|
||||
return mcpServerRepo.Save(mcpServer)
|
||||
}
|
||||
|
||||
func GetWebsiteID() uint {
|
||||
websiteID, _ := settingRepo.Get(settingRepo.WithByKey("MCP_WEBSITE_ID"))
|
||||
if websiteID.Value == "" {
|
||||
return 0
|
||||
}
|
||||
websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64)
|
||||
return uint(websiteIDUint)
|
||||
}
|
||||
@@ -21,10 +21,10 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
@@ -166,7 +166,7 @@ func (u *SettingService) UpdateBindInfo(req dto.BindInfo) error {
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system with new bind info failed, err: %v", err)
|
||||
}
|
||||
@@ -214,7 +214,7 @@ func (u *SettingService) UpdatePort(port uint) error {
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system port failed, err: %v", err)
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", false, true)
|
||||
|
||||
go func() {
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||
}
|
||||
@@ -335,7 +335,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
|
||||
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, err := cmd.Exec("systemctl restart 1panel.service")
|
||||
err := systemctl.Restart("1panel")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart system failed, err: %v", err)
|
||||
}
|
||||
@@ -552,7 +552,7 @@ func (u *SettingService) GenerateRSAKey() error {
|
||||
return err
|
||||
}
|
||||
privateKeyPEM := exportPrivateKeyToPEM(privateKey)
|
||||
publicKeyPEM, err := exportPublicKeyToPEM(&privateKey.PublicKey)
|
||||
publicKeyPEM, _ := exportPublicKeyToPEM(&privateKey.PublicKey)
|
||||
err = settingRepo.UpdateOrCreate("PASSWORD_PRIVATE_KEY", privateKeyPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -503,7 +503,8 @@ func hasOs(name string) bool {
|
||||
strings.Contains(name, "arm64") ||
|
||||
strings.Contains(name, "armv7") ||
|
||||
strings.Contains(name, "ppc64le") ||
|
||||
strings.Contains(name, "s390x")
|
||||
strings.Contains(name, "s390x") ||
|
||||
strings.Contains(name, "riscv64")
|
||||
}
|
||||
|
||||
func loadOs() string {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
)
|
||||
|
||||
type snapHelper struct {
|
||||
@@ -43,16 +44,39 @@ func snapPanel(snap snapHelper, targetDir string) {
|
||||
defer snap.Wg.Done()
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": constant.Running})
|
||||
status := constant.StatusDone
|
||||
if err := common.CopyFile("/usr/local/bin/1panel", path.Join(targetDir, "1panel")); err != nil {
|
||||
status = err.Error()
|
||||
h, err := systemctl.DefaultHandler("1panel")
|
||||
|
||||
if err != nil {
|
||||
status = fmt.Sprintf("initialize service handle failed: %v", err)
|
||||
snap.Status.Panel = status
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile("/usr/local/bin/1pctl", targetDir); err != nil {
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, err := h.GetServicePath()
|
||||
if err != nil {
|
||||
status = fmt.Sprintf("get service path failed: %v", err)
|
||||
snap.Status.Panel = status
|
||||
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel": status})
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile(path.Join(binDir, "1panel"), path.Join(targetDir, "1panel")); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r /usr/local/bin/lang %s", targetDir)
|
||||
|
||||
if err := common.CopyFile("/etc/systemd/system/1panel.service", targetDir); err != nil {
|
||||
if err := common.CopyFile(path.Join(binDir, "1pctl"), targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
if _, err := cmd.Execf("cp -r %s/lang %s", binDir, targetDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
initScriptDir := path.Join(constant.DataDir, "initscript")
|
||||
if err := common.CopyFile(servicePath, initScriptDir); err != nil {
|
||||
status = err.Error()
|
||||
}
|
||||
global.LOG.Debugf("from %s copy init script to %s", initScriptDir, path.Join(targetDir, "initscript"))
|
||||
if err := common.CopyDirs(initScriptDir, path.Join(targetDir, "initscript")); err != nil { // copy init script to targetDir
|
||||
status = err.Error()
|
||||
}
|
||||
snap.Status.Panel = status
|
||||
@@ -260,7 +284,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string, secret str
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -print) -zcf %s %s %s %s", sourceDir, " -"+exStr, path, extraCmd, targetDir+"/"+name)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%s' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, "%P\n", targetDir+"/"+name, exStr, sourceDir)
|
||||
commands = fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read --exclude-from=<(find %s -type s -printf '%%P\n' | sed 's|^|./|') -zcf %s %s -C %s .", sourceDir, targetDir+"/"+name, exStr, sourceDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
stdout, err := cmd.ExecWithTimeOut(commands, 30*time.Minute)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -91,8 +92,15 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
||||
req.IsNew = true
|
||||
}
|
||||
|
||||
h, err := systemctl.DefaultHandler("1panel")
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "ServiceHandle", constant.StatusFailed, fmt.Sprintf("initialize service handle failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if req.IsNew || snap.InterruptStep == "1PanelBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), "/usr/local/bin"); err != nil {
|
||||
binDir := systemctl.BinaryPath
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), binDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -100,16 +108,32 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), "/usr/local/bin"); err != nil {
|
||||
binDir := systemctl.BinaryPath
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), binDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r %s %s", path.Join(snapFileDir, "1panel/lang"), "/usr/local/bin/")
|
||||
langDir := path.Join(binDir, "lang")
|
||||
if err := os.RemoveAll(langDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "RemoveLang", constant.StatusFailed, fmt.Sprintf("remove lang dir failed: %v", err))
|
||||
return
|
||||
}
|
||||
if err := common.CopyDirs(path.Join(snapFileDir, "1panel/lang"), langDir); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "CopyLang", constant.StatusFailed, fmt.Sprintf("copy lang files failed: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debug("recover 1pctl from snapshot file successful!")
|
||||
req.IsNew = true
|
||||
}
|
||||
if req.IsNew || snap.InterruptStep == "1PanelService" {
|
||||
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
servicePath, err := h.GetServicePath()
|
||||
currentServiceName := h.GetServiceName()
|
||||
if err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "GetServicePath", constant.StatusFailed, fmt.Sprintf("get service path failed: %v", err))
|
||||
return
|
||||
}
|
||||
global.LOG.Debugf("current service path: %s", servicePath)
|
||||
if err := common.CopyFile(selectInitScript(path.Join(snapFileDir, "1panel/initscript"), currentServiceName), servicePath); err != nil {
|
||||
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -147,7 +171,12 @@ func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover b
|
||||
global.LOG.Debugf("remove the file %s after the operation is successful", path.Dir(snapFileDir))
|
||||
_ = os.RemoveAll(path.Dir(snapFileDir))
|
||||
}
|
||||
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
|
||||
if h.ManagerName() == "systemd" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload")
|
||||
}
|
||||
if err := systemctl.Restart("1panel"); err != nil {
|
||||
global.LOG.Errorf("restart 1panel service failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func backupBeforeRecover(snap model.Snapshot) error {
|
||||
|
||||
+214
-162
@@ -2,8 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/geo"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
@@ -12,6 +11,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/geo"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -59,26 +61,16 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
|
||||
if err != nil {
|
||||
data.Status = constant.StatusDisable
|
||||
data.Message = err.Error()
|
||||
} else {
|
||||
active, err := systemctl.IsActive(serviceName)
|
||||
if !active {
|
||||
data.Status = constant.StatusDisable
|
||||
if err != nil {
|
||||
data.Message = err.Error()
|
||||
}
|
||||
} else {
|
||||
data.Status = constant.StatusEnable
|
||||
}
|
||||
}
|
||||
|
||||
out, err := systemctl.RunSystemCtl("is-enabled", serviceName)
|
||||
if err != nil {
|
||||
data.AutoStart = false
|
||||
isEnabled, _ := systemctl.IsEnable(serviceName)
|
||||
data.AutoStart = isEnabled
|
||||
isActive, err := systemctl.IsActive(serviceName)
|
||||
if isActive {
|
||||
data.Status = constant.StatusEnable
|
||||
} else {
|
||||
if out == "alias\n" {
|
||||
data.AutoStart, _ = systemctl.IsEnable("ssh")
|
||||
} else {
|
||||
data.AutoStart = out == "enabled\n"
|
||||
data.Status = constant.StatusDisable
|
||||
if err != nil {
|
||||
data.Message = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,29 +113,26 @@ func (u *SSHService) OperateSSH(operation string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
if operation == "enable" || operation == "disable" {
|
||||
serviceName += ".service"
|
||||
}
|
||||
if operation == "stop" {
|
||||
switch operation {
|
||||
case "enable":
|
||||
return systemctl.Enable(serviceName)
|
||||
case "disable":
|
||||
return systemctl.Disable(serviceName)
|
||||
case "start":
|
||||
return systemctl.Start(serviceName)
|
||||
case "stop":
|
||||
isSocketActive, _ := systemctl.IsActive(serviceName + ".socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop %s", sudo, serviceName+".socket")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop %s.socket failed, err: %v", serviceName, std)
|
||||
if err := systemctl.Stop(serviceName + ".socket"); err != nil {
|
||||
global.LOG.Errorf("Failed to stop %s.socket: %v", serviceName, err)
|
||||
}
|
||||
}
|
||||
return systemctl.Stop(serviceName)
|
||||
case "restart":
|
||||
return systemctl.Restart(serviceName)
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("%s systemctl %s %s", sudo, operation, serviceName)
|
||||
if err != nil {
|
||||
if strings.Contains(stdout, "alias name or linked unit file") {
|
||||
stdout, err := cmd.Execf("%s systemctl %s ssh", sudo, operation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s ssh(alias name or linked unit file) failed, stdout: %s, err: %v", operation, stdout, err)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s %s failed, stdout: %s, err: %v", operation, serviceName, stdout, err)
|
||||
if result, err := systemctl.CustomAction(operation, serviceName); err != nil {
|
||||
return fmt.Errorf("failed to execute custom action: %v", result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -198,7 +187,10 @@ func (u *SSHService) Update(req dto.SSHUpdate) error {
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
|
||||
err = systemctl.Restart(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle restart %s failed, err: %v", serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -216,8 +208,10 @@ func (u *SSHService) UpdateByFile(value string) error {
|
||||
if _, err = file.WriteString(value); err != nil {
|
||||
return err
|
||||
}
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
_, _ = cmd.Execf("%s systemctl restart %s", sudo, serviceName)
|
||||
err = systemctl.Restart(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle restart %s failed, err: %v", serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -298,7 +292,7 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth")) {
|
||||
if !info.IsDir() && (strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth") || strings.HasPrefix(info.Name(), "messages")) {
|
||||
if !strings.HasSuffix(info.Name(), ".gz") {
|
||||
fileList = append(fileList, sshFileItem{Name: pathItem, Year: info.ModTime().Year()})
|
||||
return nil
|
||||
@@ -326,7 +320,8 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog,
|
||||
nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd())
|
||||
for _, file := range fileList {
|
||||
commandItem := ""
|
||||
if strings.HasPrefix(path.Base(file.Name), "secure") {
|
||||
switch {
|
||||
case strings.HasPrefix(path.Base(file.Name), "secure"):
|
||||
switch req.Status {
|
||||
case constant.StatusSuccess:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
||||
@@ -335,8 +330,16 @@ func (u *SSHService) LoadLog(c *gin.Context, req dto.SearchSSHLog) (*dto.SSHLog,
|
||||
default:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' %s", file.Name, command)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(path.Base(file.Name), "auth.log") {
|
||||
case strings.HasPrefix(path.Base(file.Name), "messages"):
|
||||
switch req.Status {
|
||||
case constant.StatusSuccess:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*Accepted (password|publickey)' %s", file.Name, command)
|
||||
case constant.StatusFailed:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*(Failed password for|Connection closed by authenticating user)' %s", file.Name, command)
|
||||
default:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -aE 'sshd.*(Accepted|Failed password for|Connection closed)' %s", file.Name, command)
|
||||
}
|
||||
case strings.HasPrefix(path.Base(file.Name), "auth.log"):
|
||||
switch req.Status {
|
||||
case constant.StatusSuccess:
|
||||
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
|
||||
@@ -432,107 +435,160 @@ func loadSSHData(c *gin.Context, command string, showCountFrom, showCountTo, cur
|
||||
lines := strings.Split(string(stdout2), "\n")
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
var itemData dto.SSHHistory
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 12 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 统一时间解析逻辑
|
||||
var dateStr string
|
||||
var timeIndex int
|
||||
if strings.Contains(parts[0], "-") { // 处理RFC3339时间格式
|
||||
t, err := time.Parse(time.RFC3339Nano, parts[0])
|
||||
if err == nil {
|
||||
dateStr = t.Format("2006 Jan 2 15:04:05")
|
||||
timeIndex = 0
|
||||
}
|
||||
} else { // 处理系统日志格式
|
||||
dateParts := parts[:3]
|
||||
if len(dateParts) < 3 {
|
||||
continue
|
||||
}
|
||||
dateStr = strings.Join(dateParts, " ")
|
||||
timeIndex = 3
|
||||
}
|
||||
|
||||
// 根据日志类型解析内容
|
||||
switch {
|
||||
case strings.Contains(lines[i], "Failed password for"):
|
||||
itemData = loadFailedSecureDatas(lines[i])
|
||||
if len(itemData.Address) != 0 {
|
||||
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
|
||||
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
|
||||
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
failedCount++
|
||||
}
|
||||
case strings.Contains(lines[i], "Connection closed by authenticating user"):
|
||||
itemData = loadFailedAuthDatas(lines[i])
|
||||
if len(itemData.Address) != 0 {
|
||||
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
|
||||
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
|
||||
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
failedCount++
|
||||
}
|
||||
case strings.Contains(lines[i], "Accepted "):
|
||||
itemData = loadSuccessDatas(lines[i])
|
||||
if len(itemData.Address) != 0 {
|
||||
if successCount+failedCount >= showCountFrom && successCount+failedCount < showCountTo {
|
||||
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
|
||||
itemData.Date = loadDate(currentYear, itemData.DateStr, nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
successCount++
|
||||
}
|
||||
case strings.Contains(line, "Failed password for"):
|
||||
itemData = parseFailedPasswordLog(parts, timeIndex, dateStr)
|
||||
case strings.Contains(line, "Connection closed by authenticating user"):
|
||||
itemData = parseConnectionClosedLog(parts, timeIndex, dateStr)
|
||||
case strings.Contains(line, "Accepted"):
|
||||
itemData = parseAcceptedLog(parts, timeIndex, dateStr)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if itemData.Address == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
total := successCount + failedCount
|
||||
if total >= showCountFrom && total < showCountTo {
|
||||
itemData.Area, _ = geo.GetIPLocation(itemData.Address, common.GetLang(c))
|
||||
itemData.Date = parseLogDate(currentYear, dateStr, nyc)
|
||||
datas = append(datas, itemData)
|
||||
}
|
||||
|
||||
if itemData.Status == constant.StatusSuccess {
|
||||
successCount++
|
||||
} else {
|
||||
failedCount++
|
||||
}
|
||||
|
||||
if total >= showCountTo {
|
||||
break
|
||||
}
|
||||
}
|
||||
return datas, successCount, failedCount
|
||||
}
|
||||
|
||||
func loadSuccessDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
func parseFailedPasswordLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
|
||||
data := dto.SSHHistory{
|
||||
DateStr: dateStr,
|
||||
Status: constant.StatusFailed,
|
||||
AuthMode: "publickey",
|
||||
}
|
||||
data.DateStr = dataStr
|
||||
data.AuthMode = parts[4+index]
|
||||
data.User = parts[6+index]
|
||||
data.Address = parts[8+index]
|
||||
data.Port = parts[10+index]
|
||||
data.Status = constant.StatusSuccess
|
||||
return data
|
||||
}
|
||||
|
||||
func loadFailedAuthDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
// 查找关键字段位置
|
||||
for i := timeIndex; i < len(parts); i++ {
|
||||
switch parts[i] {
|
||||
case "for":
|
||||
if i+1 < len(parts) {
|
||||
data.User = parts[i+1]
|
||||
}
|
||||
case "from":
|
||||
if i+1 < len(parts) {
|
||||
data.Address = parts[i+1]
|
||||
}
|
||||
case "port":
|
||||
if i+1 < len(parts) {
|
||||
data.Port = parts[i+1]
|
||||
}
|
||||
case "password":
|
||||
data.AuthMode = "password"
|
||||
}
|
||||
}
|
||||
data.DateStr = dataStr
|
||||
switch index {
|
||||
case 1:
|
||||
data.User = parts[9]
|
||||
case 2:
|
||||
data.User = parts[10]
|
||||
default:
|
||||
data.User = parts[7]
|
||||
}
|
||||
data.AuthMode = parts[6+index]
|
||||
data.Address = parts[9+index]
|
||||
data.Port = parts[11+index]
|
||||
data.Status = constant.StatusFailed
|
||||
if strings.Contains(line, ": ") {
|
||||
data.Message = strings.Split(line, ": ")[1]
|
||||
}
|
||||
return data
|
||||
}
|
||||
func loadFailedSecureDatas(line string) dto.SSHHistory {
|
||||
var data dto.SSHHistory
|
||||
parts := strings.Fields(line)
|
||||
index, dataStr := analyzeDateStr(parts)
|
||||
if dataStr == "" {
|
||||
return data
|
||||
}
|
||||
data.DateStr = dataStr
|
||||
if strings.Contains(line, " invalid ") {
|
||||
data.AuthMode = parts[4+index]
|
||||
index += 2
|
||||
} else {
|
||||
data.AuthMode = parts[4+index]
|
||||
}
|
||||
data.User = parts[6+index]
|
||||
data.Address = parts[8+index]
|
||||
data.Port = parts[10+index]
|
||||
data.Status = constant.StatusFailed
|
||||
if strings.Contains(line, ": ") {
|
||||
data.Message = strings.Split(line, ": ")[1]
|
||||
|
||||
if strings.Contains(strings.Join(parts, " "), "invalid user") {
|
||||
data.Message = "invalid user attempt"
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func parseConnectionClosedLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
|
||||
data := dto.SSHHistory{
|
||||
DateStr: dateStr,
|
||||
Status: constant.StatusFailed,
|
||||
}
|
||||
|
||||
// 解析Alpine格式的特殊字段
|
||||
fieldStart := timeIndex + 5 // 跳过时间、主机、进程字段
|
||||
for i := fieldStart; i < len(parts); i++ {
|
||||
switch {
|
||||
case parts[i] == "user":
|
||||
if i+1 < len(parts) {
|
||||
data.User = parts[i+1]
|
||||
}
|
||||
case parts[i] == "port":
|
||||
if i+1 < len(parts) {
|
||||
data.Port = parts[i+1]
|
||||
}
|
||||
case isIPAddress(parts[i]):
|
||||
data.Address = parts[i]
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func parseAcceptedLog(parts []string, timeIndex int, dateStr string) dto.SSHHistory {
|
||||
data := dto.SSHHistory{
|
||||
DateStr: dateStr,
|
||||
Status: constant.StatusSuccess,
|
||||
AuthMode: "password", // 默认值
|
||||
}
|
||||
|
||||
// 处理不同日志格式
|
||||
fieldStart := timeIndex + 5 // 基础字段偏移
|
||||
for i := fieldStart; i < len(parts); i++ {
|
||||
switch {
|
||||
case parts[i] == "for":
|
||||
if i+1 < len(parts) {
|
||||
data.User = parts[i+1]
|
||||
}
|
||||
case parts[i] == "from":
|
||||
if i+1 < len(parts) {
|
||||
data.Address = parts[i+1]
|
||||
}
|
||||
case parts[i] == "port":
|
||||
if i+1 < len(parts) {
|
||||
data.Port = parts[i+1]
|
||||
}
|
||||
case strings.Contains(parts[i], "ssh2:"):
|
||||
data.AuthMode = "publickey"
|
||||
case parts[i] == "publickey":
|
||||
data.AuthMode = "publickey"
|
||||
case parts[i] == "password":
|
||||
data.AuthMode = "password"
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
func handleGunzip(path string) error {
|
||||
if _, err := cmd.Execf("gunzip %s", path); err != nil {
|
||||
return err
|
||||
@@ -541,40 +597,36 @@ func handleGunzip(path string) error {
|
||||
}
|
||||
|
||||
func loadServiceName() (string, error) {
|
||||
if exist, _ := systemctl.IsExist("sshd"); exist {
|
||||
return "sshd", nil
|
||||
} else if exist, _ := systemctl.IsExist("ssh"); exist {
|
||||
return "ssh", nil
|
||||
}
|
||||
return "", errors.New("The ssh or sshd service is unavailable")
|
||||
}
|
||||
|
||||
func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time {
|
||||
itemDate, err := time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, DateStr), nyc)
|
||||
serviceName, err := systemctl.GetServiceName("ssh")
|
||||
if err != nil {
|
||||
itemDate, _ = time.ParseInLocation("2006 Jan 2 15:04:05", DateStr, nyc)
|
||||
if errors.Is(err, systemctl.ErrServiceNotFound) {
|
||||
return "", fmt.Errorf("SSH service unavailable. Please ensure OpenSSH server is installed and configured")
|
||||
}
|
||||
return "", fmt.Errorf("failed to load SSH service name: %w", err)
|
||||
}
|
||||
return itemDate
|
||||
return serviceName, nil
|
||||
}
|
||||
|
||||
func analyzeDateStr(parts []string) (int, string) {
|
||||
t, err := time.Parse(time.RFC3339Nano, parts[0])
|
||||
if err == nil {
|
||||
if len(parts) < 12 {
|
||||
return 0, ""
|
||||
}
|
||||
return 0, t.Format("2006 Jan 2 15:04:05")
|
||||
}
|
||||
t, err = time.Parse(constant.DateTimeLayout, fmt.Sprintf("%s %s", parts[0], parts[1]))
|
||||
if err == nil {
|
||||
if len(parts) < 14 {
|
||||
return 0, ""
|
||||
}
|
||||
return 1, t.Format("2006 Jan 2 15:04:05")
|
||||
func parseLogDate(currentYear int, dateStr string, loc *time.Location) time.Time {
|
||||
// 处理带年份和不带年份的情况
|
||||
formats := []string{
|
||||
"2006 Jan 2 15:04:05",
|
||||
"Jan 2 15:04:05",
|
||||
}
|
||||
|
||||
if len(parts) < 14 {
|
||||
return 0, ""
|
||||
for _, format := range formats {
|
||||
t, err := time.ParseInLocation(format, dateStr, loc)
|
||||
if err == nil {
|
||||
if t.Year() == 0 {
|
||||
return time.Date(currentYear, t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), 0, loc)
|
||||
}
|
||||
return t
|
||||
}
|
||||
}
|
||||
return 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2])
|
||||
return time.Now().In(loc)
|
||||
}
|
||||
|
||||
func isIPAddress(s string) bool {
|
||||
return net.ParseIP(s) != nil
|
||||
|
||||
}
|
||||
|
||||
+154
-80
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/common"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||
httpUtil "github.com/1Panel-dev/1Panel/backend/utils/http"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
)
|
||||
|
||||
type UpgradeService struct{}
|
||||
@@ -103,123 +105,159 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
|
||||
}
|
||||
downloadPath := fmt.Sprintf("%s/%s/%s/release", global.CONF.System.RepoUrl, mode, req.Version)
|
||||
fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", itemArch)
|
||||
_ = settingRepo.Update("SystemStatus", "Upgrading")
|
||||
serviceHandle, _ := systemctl.DefaultHandler("1panel")
|
||||
currentServiceName := serviceHandle.GetServiceName()
|
||||
if err := settingRepo.Update("SystemStatus", "Upgrading"); err != nil {
|
||||
return fmt.Errorf("update system status failed: %w", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := settingRepo.Update("SystemStatus", "Free"); err != nil {
|
||||
global.LOG.Errorf("Reset system status failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
_ = global.Cron.Stop()
|
||||
defer func() {
|
||||
global.Cron.Start()
|
||||
}()
|
||||
if err := fileOp.DownloadFileWithProxy(downloadPath+"/"+fileName, rootDir+"/"+fileName); err != nil {
|
||||
global.LOG.Errorf("download service file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
defer global.Cron.Start()
|
||||
|
||||
if err := fileOp.DownloadFileWithProxy(
|
||||
fmt.Sprintf("%s/%s", downloadPath, fileName),
|
||||
path.Join(rootDir, fileName),
|
||||
); err != nil {
|
||||
global.LOG.Errorf("Failed to download upgrade package: %v", err)
|
||||
return
|
||||
}
|
||||
global.LOG.Info("download all file successful!")
|
||||
defer func() {
|
||||
_ = os.Remove(rootDir)
|
||||
}()
|
||||
if err := handleUnTar(rootDir+"/"+fileName, rootDir, ""); err != nil {
|
||||
global.LOG.Errorf("decompress file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
defer os.RemoveAll(rootDir)
|
||||
|
||||
if err := handleUnTar(path.Join(rootDir, fileName), rootDir, ""); err != nil {
|
||||
global.LOG.Errorf("Failed to extract package: %v", err)
|
||||
return
|
||||
}
|
||||
tmpDir := rootDir + "/" + strings.ReplaceAll(fileName, ".tar.gz", "")
|
||||
|
||||
tmpDir := path.Join(rootDir, strings.TrimSuffix(fileName, ".tar.gz"))
|
||||
|
||||
if err := u.handleBackup(fileOp, originalDir); err != nil {
|
||||
global.LOG.Errorf("handle backup original file failed, err: %v", err)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
return
|
||||
}
|
||||
global.LOG.Info("backup original data successful, now start to upgrade!")
|
||||
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1panel"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 1)
|
||||
global.LOG.Errorf("Backup failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 2)
|
||||
return
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r %s /usr/local/bin", path.Join(tmpDir, "lang"))
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo")
|
||||
_, _ = cmd.Execf("mkdir %s && cp %s %s/", geoPath, path.Join(tmpDir, "GeoIP.mmdb"), geoPath)
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, _ := serviceHandle.GetServicePath()
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
||||
|
||||
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl", global.CONF.System.BaseDir); err != nil {
|
||||
global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err)
|
||||
criticalUpdates := []struct {
|
||||
src string
|
||||
dest string
|
||||
step int
|
||||
}{
|
||||
{path.Join(tmpDir, "1panel"), path.Join(binDir, "1panel"), 1},
|
||||
{path.Join(tmpDir, "1pctl"), path.Join(binDir, "1pctl"), 2},
|
||||
{selectInitScript(path.Join(tmpDir, "initscript"), currentServiceName), servicePath, 3},
|
||||
}
|
||||
|
||||
for _, update := range criticalUpdates {
|
||||
if err := common.Copy(update.src, update.dest); err != nil {
|
||||
global.LOG.Errorf("Update %s failed: %v", path.Base(update.dest), err)
|
||||
u.handleRollback(originalDir, update.step)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl",
|
||||
global.CONF.System.BaseDir); err != nil {
|
||||
global.LOG.Errorf("Update base directory failed: %v", err)
|
||||
u.handleRollback(originalDir, 2)
|
||||
return
|
||||
}
|
||||
|
||||
if err := common.CopyFile(path.Join(tmpDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
|
||||
u.handleRollback(originalDir, 3)
|
||||
return
|
||||
langDir := path.Join(binDir, "lang")
|
||||
if err := common.Copy(path.Join(tmpDir, "lang"), langDir); err != nil {
|
||||
global.LOG.Errorf("Update language files failed: %v", err)
|
||||
}
|
||||
if err := common.Copy(path.Join(tmpDir, "GeoIP.mmdb"), geoPath); err != nil {
|
||||
global.LOG.Warnf("Update GeoIP database failed: %v", err)
|
||||
}
|
||||
|
||||
global.LOG.Info("upgrade successful!")
|
||||
go writeLogs(req.Version)
|
||||
_ = settingRepo.Update("SystemVersion", req.Version)
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
checkPointOfWal()
|
||||
_, _ = cmd.ExecWithTimeOut("systemctl daemon-reload && systemctl restart 1panel.service", 1*time.Minute)
|
||||
if err := settingRepo.Update("SystemVersion", req.Version); err != nil {
|
||||
global.LOG.Errorf("Update system version failed: %v", err)
|
||||
}
|
||||
if serviceHandle.ManagerName() == "systemd" {
|
||||
_, _ = cmd.Exec("systemctl daemon-reload")
|
||||
}
|
||||
if err := systemctl.Restart("1panel"); err != nil {
|
||||
global.LOG.Errorf("Service restart failed: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) error {
|
||||
if err := fileOp.Copy("/usr/local/bin/1panel", originalDir); err != nil {
|
||||
return err
|
||||
global.LOG.Info("Initiating backup procedure...")
|
||||
h, _ := systemctl.DefaultHandler("1panel")
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, _ := h.GetServicePath()
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
||||
|
||||
backupItems := []struct {
|
||||
src string
|
||||
dest string
|
||||
}{
|
||||
{path.Join(binDir, "1panel"), originalDir},
|
||||
{path.Join(binDir, "1pctl"), originalDir},
|
||||
{servicePath, originalDir},
|
||||
{path.Join(binDir, "lang"), originalDir},
|
||||
{geoPath, originalDir},
|
||||
}
|
||||
if err := fileOp.Copy("/usr/local/bin/1pctl", originalDir); err != nil {
|
||||
return err
|
||||
|
||||
for _, item := range backupItems {
|
||||
if err := fileOp.Copy(item.src, item.dest); err != nil {
|
||||
return fmt.Errorf("backup %s failed: %w", path.Base(item.src), err)
|
||||
}
|
||||
}
|
||||
if err := fileOp.Copy("/etc/systemd/system/1panel.service", originalDir); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r /usr/local/bin/lang %s", originalDir)
|
||||
_, _ = cmd.Execf("cp %s %s", path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb"), originalDir)
|
||||
checkPointOfWal()
|
||||
if err := handleTar(path.Join(global.CONF.System.BaseDir, "1panel/db"), originalDir, "db.tar.gz", "db/1Panel.db-*", ""); err != nil {
|
||||
return err
|
||||
|
||||
if err := handleTar(
|
||||
path.Join(global.CONF.System.BaseDir, "1panel/db"),
|
||||
originalDir,
|
||||
"db.tar.gz",
|
||||
"db/1Panel.db-*",
|
||||
"",
|
||||
); err != nil {
|
||||
return fmt.Errorf("database backup failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
|
||||
_ = settingRepo.Update("SystemStatus", "Free")
|
||||
global.LOG.Info("Initiating rollback procedure...")
|
||||
h, _ := systemctl.DefaultHandler("1panel")
|
||||
binDir := systemctl.BinaryPath
|
||||
servicePath, _ := h.GetServicePath()
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo/GeoIP.mmdb")
|
||||
|
||||
checkPointOfWal()
|
||||
if _, err := os.Stat(path.Join(originalDir, "1Panel.db")); err == nil {
|
||||
if err := common.CopyFile(path.Join(originalDir, "1Panel.db"), global.CONF.System.DbPath); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
||||
rollbackSteps := []struct {
|
||||
src string
|
||||
dest string
|
||||
}{
|
||||
{path.Join(originalDir, "1panel"), path.Join(binDir, "1panel")},
|
||||
{path.Join(originalDir, "1pctl"), path.Join(binDir, "1pctl")},
|
||||
{path.Join(originalDir, filepath.Base(servicePath)), servicePath},
|
||||
{path.Join(originalDir, "lang"), path.Join(binDir, "lang")},
|
||||
{path.Join(originalDir, "GeoIP.mmdb"), geoPath},
|
||||
}
|
||||
|
||||
for _, step := range rollbackSteps[:errStep] {
|
||||
if err := common.CopyFile(step.src, step.dest); err != nil {
|
||||
global.LOG.Errorf("Rollback %s failed: %v", path.Base(step.src), err)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(path.Join(originalDir, "db.tar.gz")); err == nil {
|
||||
if err := handleUnTar(path.Join(originalDir, "db.tar.gz"), global.CONF.System.DbPath, ""); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel db failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
if err := common.CopyFile(path.Join(originalDir, "1panel"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1pctl failed, err: %v", err)
|
||||
}
|
||||
if errStep == 1 {
|
||||
return
|
||||
}
|
||||
if err := common.CopyFile(path.Join(originalDir, "1pctl"), "/usr/local/bin"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
}
|
||||
_, _ = cmd.Execf("cp -r %s /usr/local/bin", path.Join(originalDir, "lang"))
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo")
|
||||
_, _ = cmd.Execf("mkdir %s && cp %s %s/", geoPath, path.Join(originalDir, "GeoIP.mmdb"), geoPath)
|
||||
|
||||
if errStep == 2 {
|
||||
return
|
||||
}
|
||||
if err := common.CopyFile(path.Join(originalDir, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
global.LOG.Errorf("rollback 1panel failed, err: %v", err)
|
||||
if err := systemctl.Restart("1panel"); err != nil {
|
||||
global.LOG.Errorf("Service restart during rollback failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,5 +395,41 @@ func loadArch() (string, error) {
|
||||
if strings.Contains(std, "s390x") {
|
||||
return "s390x", nil
|
||||
}
|
||||
if strings.Contains(std, "riscv64") {
|
||||
return "riscv64", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported such arch: %s", std)
|
||||
}
|
||||
|
||||
func selectInitScript(path string, serviceName string) string {
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
mgr := systemctl.GetGlobalManager().Name()
|
||||
var serviceFileName string
|
||||
switch mgr {
|
||||
case "systemd":
|
||||
serviceFileName = "1panel.service"
|
||||
case "openrc":
|
||||
serviceFileName = "1paneld.openrc"
|
||||
case "sysvinit":
|
||||
isWrt := systemctl.FileExist("/etc/rc.common")
|
||||
if isWrt {
|
||||
serviceFileName = "1paneld.procd"
|
||||
} else {
|
||||
serviceFileName = "1paneld.init"
|
||||
}
|
||||
default:
|
||||
serviceFileName = serviceName
|
||||
global.LOG.Warnf("[%s]unselect InitScript, used default: %s", mgr, serviceName)
|
||||
}
|
||||
sourcePath := filepath.Join(path, serviceFileName)
|
||||
targetPath := filepath.Join(path, serviceName)
|
||||
|
||||
if serviceFileName != serviceName {
|
||||
if _, err := cmd.Execf("cp %s %s", sourcePath, targetPath); err != nil {
|
||||
global.LOG.Errorf("Failed to copy init script from %s to %s: %v",
|
||||
serviceFileName, serviceName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return targetPath
|
||||
}
|
||||
|
||||
@@ -427,7 +427,9 @@ func (w WebsiteService) UpdateWebsite(req request.WebsiteUpdate) error {
|
||||
}
|
||||
}
|
||||
website.PrimaryDomain = req.PrimaryDomain
|
||||
website.WebsiteGroupID = req.WebsiteGroupID
|
||||
if req.WebsiteGroupID > 0 {
|
||||
website.WebsiteGroupID = req.WebsiteGroupID
|
||||
}
|
||||
website.Remark = req.Remark
|
||||
website.IPV6 = req.IPV6
|
||||
|
||||
@@ -496,6 +498,10 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
|
||||
if err := websiteDomainRepo.DeleteBy(ctx, websiteDomainRepo.WithWebsiteId(req.ID)); err != nil {
|
||||
return err
|
||||
}
|
||||
websiteID := GetWebsiteID()
|
||||
if req.ID == websiteID {
|
||||
_ = settingRepo.Update("MCP_WEBSITE_ID", "0")
|
||||
}
|
||||
tx.Commit()
|
||||
|
||||
uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/website/%s", website.Alias))
|
||||
@@ -1764,11 +1770,22 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
|
||||
}
|
||||
directives := config.GetDirectives()
|
||||
|
||||
location, ok := directives[0].(*components.Location)
|
||||
if !ok {
|
||||
err = errors.New("error")
|
||||
return
|
||||
var (
|
||||
location *components.Location
|
||||
ok bool
|
||||
)
|
||||
for _, directive := range directives {
|
||||
if directive.GetName() == "location" {
|
||||
location, ok = directive.(*components.Location)
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if location == nil {
|
||||
return nil, buserr.New("ErrConfigParse")
|
||||
}
|
||||
|
||||
proxyConfig.ProxyPass = location.ProxyPass
|
||||
proxyConfig.Cache = location.Cache
|
||||
if location.CacheTime > 0 {
|
||||
|
||||
@@ -23,6 +23,7 @@ type System struct {
|
||||
Language string `mapstructure:"language"`
|
||||
IsDemo bool `mapstructure:"is_demo"`
|
||||
IsIntl bool `mapstructure:"is_intl"`
|
||||
LicenseVerify string `mapstructure:"license_verify"`
|
||||
AppRepo string `mapstructure:"app_repo"`
|
||||
ChangeUserInfo string `mapstructure:"change_user_info"`
|
||||
OneDriveID string `mapstructure:"one_drive_id"`
|
||||
|
||||
@@ -35,6 +35,7 @@ var WebUrlMap = map[string]struct{}{
|
||||
"/ai": {},
|
||||
"/ai/model": {},
|
||||
"/ai/gpu": {},
|
||||
"/ai/mcp": {},
|
||||
|
||||
"/containers": {},
|
||||
"/containers/container": {},
|
||||
|
||||
@@ -17,4 +17,5 @@ var (
|
||||
RuntimeDir = path.Join(DataDir, "runtime")
|
||||
RecycleBinDir = "/.1panel_clash"
|
||||
SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
|
||||
McpDir = path.Join(DataDir, "mcp")
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
RuntimeStopped = "stopped"
|
||||
RuntimeUnhealthy = "unhealthy"
|
||||
RuntimeCreating = "creating"
|
||||
RuntimeReStarting = "restarting"
|
||||
|
||||
RuntimePHP = "php"
|
||||
RuntimeNode = "node"
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "mode: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'Please install Openresty first'
|
||||
ErrSSL: "Certificate content is empty, please check the certificate!"
|
||||
ErrSsePath: "SSE path is duplicated"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'Token verification error, please reset and scan again.'
|
||||
|
||||
@@ -285,6 +285,7 @@ SystemMode: "モード: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'まず Openresty をインストールしてください'
|
||||
ErrSSL: "証明書の内容が空です。証明書を確認してください!"
|
||||
ErrSsePath: "SSE パスが重複しています"
|
||||
|
||||
|
||||
#mobile app
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "모드: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: '먼저 Openresty를 설치하세요'
|
||||
ErrSSL: "인증서 내용이 비어 있습니다. 인증서를 확인하세요!"
|
||||
ErrSsePath: "SSE 경로가 중복되었습니다"
|
||||
|
||||
|
||||
#mobile app
|
||||
|
||||
@@ -287,7 +287,7 @@ SystemMode: "Mod: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'Sila pasang Openresty terlebih dahulu'
|
||||
ErrSSL: "Kandungan sijil kosong, sila periksa sijil!"
|
||||
|
||||
ErrSsePath: "Laluan SSE bertindan"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'Ralat pengesahan token, sila tetapkan semula dan imbas semula.'
|
||||
|
||||
@@ -285,6 +285,7 @@ SystemMode: "modo: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: 'Por favor, instale o Openresty primeiro'
|
||||
ErrSSL: "O conteúdo do certificado está vazio, por favor, verifique o certificado!"
|
||||
ErrSsePath: "Caminho SSE duplicado"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'Erro de verificação do token, por favor, reinicie e escaneie novamente.'
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "режим: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: "Пожалуйста, установите Openresty сначала"
|
||||
ErrSSL: "Содержимое сертификата пустое, пожалуйста, проверьте сертификат!"
|
||||
ErrSsePath: "Путь SSE дублируется"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: 'шибка проверки токена, пожалуйста, сбросьте и отсканируйте снова.'
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "模式: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: "請先安裝 Openresty"
|
||||
ErrSSL: "證書內容為空,請檢查證書!"
|
||||
ErrSsePath: "SSE 路徑重複"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: '令牌驗證錯誤,請重置後再次掃碼'
|
||||
|
||||
@@ -288,6 +288,7 @@ SystemMode: "模式: "
|
||||
#ai-tool
|
||||
ErrOpenrestyInstall: '请先安装 Openresty'
|
||||
ErrSSL: "证书内容为空,请检查证书!"
|
||||
ErrSsePath: "SSE 路径重复"
|
||||
|
||||
#mobile app
|
||||
ErrVerifyToken: '令牌验证错误,请重置后再次扫码'
|
||||
|
||||
@@ -23,8 +23,11 @@ func Init() {
|
||||
|
||||
constant.SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
|
||||
|
||||
constant.McpDir = path.Join(constant.DataDir, "mcp")
|
||||
|
||||
dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir,
|
||||
global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir, constant.SSLLogDir}
|
||||
global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir,
|
||||
constant.SSLLogDir, constant.McpDir}
|
||||
|
||||
fileOp := files.NewFileOp()
|
||||
for _, dir := range dirs {
|
||||
|
||||
@@ -84,6 +84,15 @@ func Init() {
|
||||
global.CONF.System.ApiKeyValidityTime = apiKeyValidityTimeSetting.Value
|
||||
}
|
||||
|
||||
if global.CONF.System.LicenseVerify == "" {
|
||||
licenseVerify, err := settingRepo.Get(settingRepo.WithByKey("LicenseVerify"))
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load service license verify from setting failed, err: %v", err)
|
||||
}
|
||||
global.CONF.System.LicenseVerify = licenseVerify.Value
|
||||
}
|
||||
handleLicenseVerify(global.CONF.System.LicenseVerify, settingRepo)
|
||||
|
||||
handleUserInfo(global.CONF.System.ChangeUserInfo, settingRepo)
|
||||
|
||||
handleCronjobStatus()
|
||||
@@ -207,6 +216,12 @@ func loadLocalDir() {
|
||||
global.LOG.Errorf("error type dir: %T", varMap["dir"])
|
||||
}
|
||||
|
||||
func handleLicenseVerify(licenseVerify string, settingRepo repo.ISettingRepo) {
|
||||
if err := settingRepo.Update("LicenseVerify", licenseVerify); err != nil {
|
||||
global.LOG.Fatalf("init license verify before start failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
|
||||
if len(tags) == 0 {
|
||||
return
|
||||
|
||||
@@ -105,6 +105,12 @@ func Init() {
|
||||
migrations.AddOllamaModel,
|
||||
migrations.AddAppMenu,
|
||||
migrations.AddAppPanelName,
|
||||
migrations.AddLicenseVerify,
|
||||
|
||||
migrations.AddMcpServer,
|
||||
migrations.AddPbootCMSPHPExtensions,
|
||||
migrations.DeleteV2Openresty,
|
||||
migrations.UpdateOnedrive,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -448,3 +448,64 @@ var AddAppPanelName = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddLicenseVerify = &gormigrate.Migration{
|
||||
ID: "20250226-add-license-verify",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
|
||||
if err := tx.Create(&model.Setting{Key: "LicenseVerify", Value: "LX"}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddMcpServer = &gormigrate.Migration{
|
||||
ID: "20250401-add-mcp-server",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.McpServer{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddPbootCMSPHPExtensions = &gormigrate.Migration{
|
||||
ID: "20250625-add-php-extensions",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
item := &model.PHPExtensions{
|
||||
Name: "PbootCMS",
|
||||
}
|
||||
if err := tx.Where("name = ?", "PbootCMS").FirstOrCreate(item, &model.PHPExtensions{
|
||||
Name: "PbootCMS",
|
||||
Extensions: "curl,gd,mbstring,mysqli,openssl,pdo_mysql,pdo_sqlite,sqlite3",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var DeleteV2Openresty = &gormigrate.Migration{
|
||||
ID: "20250701-delete-v2-openresty",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.Where("version = '1.27.1.2-0-1-focal'").Delete(&model.AppDetail{}).Error
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateOnedrive = &gormigrate.Migration{
|
||||
ID: "20250704-update-onedrive",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.Setting{}).
|
||||
Where("key = ?", "OneDriveID").
|
||||
Update("value", "NTQ0NmNmZTMtNGM3OS00N2EwLWFlMjUtZmM2NDU0NzhlMmQ5").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.Setting{}).
|
||||
Where("key = ?", "OneDriveSc").
|
||||
Update("value", "bGRlOFF+WEVrR1M0b25Vb1VsRWpMYzE2MW9rTXZEM25KdnZ1MGN6MA==").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -188,7 +188,12 @@ func Routers() *gin.Engine {
|
||||
|
||||
swaggerRouter := Router.Group("1panel")
|
||||
docs.SwaggerInfo.BasePath = "/api/v1"
|
||||
swaggerRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
swaggerRouter.GET("/swagger/*any", func(c *gin.Context) {
|
||||
if !checkSession(c) {
|
||||
handleNoRoute(c)
|
||||
return
|
||||
}
|
||||
}, ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
PublicGroup := Router.Group("")
|
||||
{
|
||||
PublicGroup.GET("/health", func(c *gin.Context) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package viper
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
@@ -103,6 +104,7 @@ func Init() {
|
||||
global.CONF.System.Entrance = entrance
|
||||
global.CONF.System.Language = language
|
||||
global.CONF.System.ChangeUserInfo = loadChangeInfo()
|
||||
global.CONF.System.LicenseVerify = os.Getenv("LXWARE_LICENSE_VERIFY")
|
||||
global.Viper = v
|
||||
}
|
||||
|
||||
|
||||
@@ -24,5 +24,6 @@ func commonGroups() []CommonRouter {
|
||||
&ProcessRouter{},
|
||||
&WebsiteCARouter{},
|
||||
&AIToolsRouter{},
|
||||
&McpServerRouter{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
aiToolsRouter.GET("/ollama/exec", baseApi.OllamaWsSsh)
|
||||
aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel)
|
||||
aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel)
|
||||
@@ -27,5 +26,14 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/domain/bind", baseApi.BindDomain)
|
||||
aiToolsRouter.POST("/domain/get", baseApi.GetBindDomain)
|
||||
aiToolsRouter.POST("/domain/update", baseApi.UpdateBindDomain)
|
||||
|
||||
aiToolsRouter.POST("/mcp/search", baseApi.PageMcpServers)
|
||||
aiToolsRouter.POST("/mcp/server", baseApi.CreateMcpServer)
|
||||
aiToolsRouter.POST("/mcp/server/update", baseApi.UpdateMcpServer)
|
||||
aiToolsRouter.POST("/mcp/server/del", baseApi.DeleteMcpServer)
|
||||
aiToolsRouter.POST("/mcp/server/op", baseApi.OperateMcpServer)
|
||||
aiToolsRouter.POST("/mcp/domain/bind", baseApi.BindMcpDomain)
|
||||
aiToolsRouter.GET("/mcp/domain/get", baseApi.GetMcpBindDomain)
|
||||
aiToolsRouter.POST("/mcp/domain/update", baseApi.UpdateMcpBindDomain)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
Use(middleware.PasswordExpired())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
baRouter.GET("/exec", baseApi.ContainerWsSsh)
|
||||
baRouter.GET("/exec", baseApi.ContainerWsSSH)
|
||||
baRouter.GET("/stats/:id", baseApi.ContainerStats)
|
||||
|
||||
baRouter.POST("", baseApi.ContainerCreate)
|
||||
|
||||
@@ -38,7 +38,6 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.POST("/redis/persistence/conf", baseApi.LoadPersistenceConf)
|
||||
cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus)
|
||||
cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf)
|
||||
cmdRouter.GET("/redis/exec", baseApi.RedisWsSsh)
|
||||
cmdRouter.GET("/redis/check", baseApi.CheckHasCli)
|
||||
cmdRouter.POST("/redis/install/cli", baseApi.InstallCli)
|
||||
cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type McpServerRouter struct {
|
||||
}
|
||||
|
||||
func (m *McpServerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
mcpRouter := Router.Group("mcp")
|
||||
mcpRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).Use(middleware.PasswordExpired())
|
||||
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
mcpRouter.POST("/search", baseApi.PageMcpServers)
|
||||
mcpRouter.POST("/server", baseApi.CreateMcpServer)
|
||||
}
|
||||
}
|
||||
@@ -47,9 +47,9 @@ func Start() {
|
||||
session.Init()
|
||||
gin.SetMode(gin.DebugMode)
|
||||
cron.Run()
|
||||
hook.Init()
|
||||
InitOthers()
|
||||
business.Init()
|
||||
hook.Init()
|
||||
|
||||
rootRouter := router.Routers()
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
cosSDK "github.com/tencentyun/cos-go-sdk-v5"
|
||||
)
|
||||
@@ -18,6 +19,7 @@ type cosClient struct {
|
||||
|
||||
func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
region := loadParamFromVars("region", vars)
|
||||
endpoint := loadParamFromVars("endpoint", vars)
|
||||
accessKey := loadParamFromVars("accessKey", vars)
|
||||
secretKey := loadParamFromVars("secretKey", vars)
|
||||
bucket := loadParamFromVars("bucket", vars)
|
||||
@@ -26,7 +28,16 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
scType = "Standard"
|
||||
}
|
||||
|
||||
u, _ := url.Parse(fmt.Sprintf("https://cos.%s.myqcloud.com", region))
|
||||
// 允许使用对象存储双栈域名
|
||||
endpointType := "cos"
|
||||
if len(endpoint) != 0 {
|
||||
re := regexp.MustCompile(`.*cos-dualstack\..*`)
|
||||
if re.MatchString(endpoint) {
|
||||
endpointType = "cos-dualstack"
|
||||
}
|
||||
}
|
||||
|
||||
u, _ := url.Parse(fmt.Sprintf("https://%s.%s.myqcloud.com", endpointType, region))
|
||||
b := &cosSDK.BaseURL{BucketURL: u}
|
||||
client := cosSDK.NewClient(b, &http.Client{
|
||||
Transport: &cosSDK.AuthorizationTransport{
|
||||
@@ -36,7 +47,7 @@ func NewCosClient(vars map[string]interface{}) (*cosClient, error) {
|
||||
})
|
||||
|
||||
if len(bucket) != 0 {
|
||||
u2, _ := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", bucket, region))
|
||||
u2, _ := url.Parse(fmt.Sprintf("https://%s.%s.%s.myqcloud.com", bucket, endpointType, region))
|
||||
b2 := &cosSDK.BaseURL{BucketURL: u2}
|
||||
clientWithBucket := cosSDK.NewClient(b2, &http.Client{
|
||||
Transport: &cosSDK.AuthorizationTransport{
|
||||
|
||||
@@ -3,13 +3,12 @@ package common
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
mathRand "math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
@@ -18,6 +17,9 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/buserr"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
@@ -124,6 +126,9 @@ func GetSortedVersions(versions []string) []string {
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) error {
|
||||
if fi, err := os.Stat(src); err == nil && fi.IsDir() {
|
||||
return fmt.Errorf("src is a directory, use CopyDirs instead")
|
||||
}
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -152,7 +157,57 @@ func CopyFile(src, dst string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func CopyDirs(srcDir, dstDir string) error {
|
||||
srcInfo, err := os.Stat(srcDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !srcInfo.IsDir() {
|
||||
return fmt.Errorf("%s is not a directory", srcDir)
|
||||
}
|
||||
if err := os.MkdirAll(dstDir, srcInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relPath, err := filepath.Rel(srcDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstPath := filepath.Join(dstDir, relPath)
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(dstPath, info.Mode())
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
return copySymlink(path, dstPath)
|
||||
}
|
||||
return CopyFile(path, dstPath)
|
||||
})
|
||||
}
|
||||
|
||||
func copySymlink(src, dst string) error {
|
||||
link, err := os.Readlink(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Symlink(link, dst)
|
||||
}
|
||||
func Copy(src, dst string) error {
|
||||
fi, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch mode := fi.Mode(); {
|
||||
case mode.IsDir():
|
||||
return CopyDirs(src, dst)
|
||||
case mode.IsRegular():
|
||||
return CopyFile(src, dst)
|
||||
default:
|
||||
return fmt.Errorf("unsupported file type: %s", mode)
|
||||
}
|
||||
}
|
||||
func IsCrossVersion(version1, version2 string) bool {
|
||||
version1s := strings.Split(version1, ".")
|
||||
version2s := strings.Split(version2, ".")
|
||||
|
||||
@@ -349,26 +349,30 @@ func (f FileOp) DownloadFileWithProxy(url, dst string) error {
|
||||
}
|
||||
|
||||
func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
|
||||
for _, p := range oldPaths {
|
||||
var dstPath string
|
||||
if name != "" {
|
||||
dstPath = filepath.Join(dst, name)
|
||||
if f.Stat(dstPath) {
|
||||
dstPath = dst
|
||||
}
|
||||
} else {
|
||||
base := filepath.Base(p)
|
||||
dstPath = filepath.Join(dst, base)
|
||||
if len(oldPaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
var dstPath string
|
||||
coverFlag := ""
|
||||
if name != "" {
|
||||
dstPath = filepath.Join(dst, name)
|
||||
if f.Stat(dstPath) {
|
||||
dstPath = dst
|
||||
}
|
||||
coverFlag := ""
|
||||
if cover {
|
||||
coverFlag = "-f"
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf(`mv %s '%s' '%s'`, coverFlag, p, dstPath)
|
||||
if err := cmd.ExecCmd(cmdStr); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dstPath = dst
|
||||
coverFlag = "-f"
|
||||
}
|
||||
var quotedPaths []string
|
||||
for _, p := range oldPaths {
|
||||
quotedPaths = append(quotedPaths, fmt.Sprintf("'%s'", p))
|
||||
}
|
||||
mvCommand := fmt.Sprintf("mv %s %s '%s'", coverFlag, strings.Join(quotedPaths, " "), dstPath)
|
||||
if err := cmd.ExecCmd(mvCommand); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
)
|
||||
|
||||
var ForwardListRegex = regexp.MustCompile(`^port=(\d{1,5}):proto=(.+?):toport=(\d{1,5}):toaddr=(.*)$`)
|
||||
@@ -41,27 +42,15 @@ func (f *Firewall) Version() (string, error) {
|
||||
}
|
||||
|
||||
func (f *Firewall) Start() error {
|
||||
stdout, err := cmd.Exec("systemctl start firewalld")
|
||||
if err != nil {
|
||||
return fmt.Errorf("enable the firewall failed, err: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
return systemctl.Start("firewalld")
|
||||
}
|
||||
|
||||
func (f *Firewall) Stop() error {
|
||||
stdout, err := cmd.Exec("systemctl stop firewalld")
|
||||
if err != nil {
|
||||
return fmt.Errorf("stop the firewall failed, err: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
return systemctl.Stop("firewalld")
|
||||
}
|
||||
|
||||
func (f *Firewall) Restart() error {
|
||||
stdout, err := cmd.Exec("systemctl restart firewalld")
|
||||
if err != nil {
|
||||
return fmt.Errorf("restart the firewall failed, err: %s", stdout)
|
||||
}
|
||||
return nil
|
||||
return systemctl.Restart("firewalld")
|
||||
}
|
||||
|
||||
func (f *Firewall) Reload() error {
|
||||
|
||||
@@ -46,7 +46,9 @@ func NewLocation(directive IDirective) *Location {
|
||||
location.Host = params[1]
|
||||
}
|
||||
case "proxy_cache":
|
||||
location.Cache = true
|
||||
if params[0] != "off" {
|
||||
location.Cache = true
|
||||
}
|
||||
case "if":
|
||||
if params[0] == "(" && params[1] == "$uri" && params[2] == "~*" {
|
||||
dirs := dir.GetBlock().GetDirectives()
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
@@ -82,58 +79,6 @@ func (c *ConnInfo) Close() {
|
||||
_ = c.Client.Close()
|
||||
}
|
||||
|
||||
type SshConn struct {
|
||||
StdinPipe io.WriteCloser
|
||||
ComboOutput *wsBufferWriter
|
||||
Session *gossh.Session
|
||||
}
|
||||
|
||||
func (c *ConnInfo) NewSshConn(cols, rows int) (*SshConn, error) {
|
||||
sshSession, err := c.Client.NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stdinP, err := sshSession.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comboWriter := new(wsBufferWriter)
|
||||
sshSession.Stdout = comboWriter
|
||||
sshSession.Stderr = comboWriter
|
||||
|
||||
modes := gossh.TerminalModes{
|
||||
gossh.ECHO: 1,
|
||||
gossh.TTY_OP_ISPEED: 14400,
|
||||
gossh.TTY_OP_OSPEED: 14400,
|
||||
}
|
||||
if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := sshSession.Shell(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SshConn{StdinPipe: stdinP, ComboOutput: comboWriter, Session: sshSession}, nil
|
||||
}
|
||||
|
||||
func (s *SshConn) Close() {
|
||||
if s.Session != nil {
|
||||
s.Session.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type wsBufferWriter struct {
|
||||
buffer bytes.Buffer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (w *wsBufferWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.buffer.Write(p)
|
||||
}
|
||||
|
||||
func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) {
|
||||
if len(passPhrase) != 0 {
|
||||
return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase)
|
||||
|
||||
+37
-12
@@ -3,6 +3,7 @@ package ssl
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"github.com/go-acme/lego/v4/providers/dns/westcn"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/clouddns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/dnspod"
|
||||
"github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||
"github.com/go-acme/lego/v4/providers/dns/huaweicloud"
|
||||
@@ -75,6 +77,7 @@ const (
|
||||
Volcengine DnsType = "Volcengine"
|
||||
CloudFlare DnsType = "CloudFlare"
|
||||
CloudDns DnsType = "CloudDns"
|
||||
ClouDNS DnsType = "ClouDNS"
|
||||
FreeMyIP DnsType = "FreeMyIP"
|
||||
NameSilo DnsType = "NameSilo"
|
||||
NameCheap DnsType = "NameCheap"
|
||||
@@ -83,21 +86,26 @@ const (
|
||||
TencentCloud DnsType = "TencentCloud"
|
||||
HuaweiCloud DnsType = "HuaweiCloud"
|
||||
RainYun DnsType = "RainYun"
|
||||
WestCN DnsType = "WestCN"
|
||||
)
|
||||
|
||||
type DNSParam struct {
|
||||
ID string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Email string `json:"email"`
|
||||
APIkey string `json:"apiKey"`
|
||||
APIUser string `json:"apiUser"`
|
||||
APISecret string `json:"apiSecret"`
|
||||
SecretID string `json:"secretID"`
|
||||
Region string `json:"region"`
|
||||
ClientID string `json:"clientID"`
|
||||
Password string `json:"password"`
|
||||
ID string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Email string `json:"email"`
|
||||
APIkey string `json:"apiKey"`
|
||||
APIUser string `json:"apiUser"`
|
||||
APISecret string `json:"apiSecret"`
|
||||
SecretID string `json:"secretID"`
|
||||
Region string `json:"region"`
|
||||
ClientID string `json:"clientID"`
|
||||
Password string `json:"password"`
|
||||
AuthID string `json:"authID"`
|
||||
SubAuthID string `json:"subAuthID"`
|
||||
AuthPassword string `json:"authPassword"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -158,6 +166,15 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
|
||||
clouddnsConfig.PollingInterval = pollingInterval
|
||||
clouddnsConfig.TTL = ttl
|
||||
p, err = clouddns.NewDNSProviderConfig(clouddnsConfig)
|
||||
case ClouDNS:
|
||||
cloudnsConfig := cloudns.NewDefaultConfig()
|
||||
cloudnsConfig.AuthID = param.AuthID
|
||||
cloudnsConfig.SubAuthID = param.SubAuthID
|
||||
cloudnsConfig.AuthPassword = param.AuthPassword
|
||||
cloudnsConfig.PropagationTimeout = propagationTimeout
|
||||
cloudnsConfig.PollingInterval = pollingInterval
|
||||
cloudnsConfig.TTL = ttl
|
||||
p, err = cloudns.NewDNSProviderConfig(cloudnsConfig)
|
||||
case FreeMyIP:
|
||||
freeMyIpConfig := freemyip.NewDefaultConfig()
|
||||
freeMyIpConfig.Token = param.Token
|
||||
@@ -219,6 +236,14 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
|
||||
rainyunConfig.PollingInterval = pollingInterval
|
||||
rainyunConfig.TTL = ttl
|
||||
p, err = rainyun.NewDNSProviderConfig(rainyunConfig)
|
||||
case WestCN:
|
||||
westcnConfig := westcn.NewDefaultConfig()
|
||||
westcnConfig.Username = param.Username
|
||||
westcnConfig.Password = param.Password
|
||||
westcnConfig.PropagationTimeout = propagationTimeout
|
||||
westcnConfig.PollingInterval = pollingInterval
|
||||
westcnConfig.TTL = ttl
|
||||
p, err = westcn.NewDNSProviderConfig(westcnConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
package systemctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"go4.org/syncutil/singleflight"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var (
|
||||
aliasFile string
|
||||
serviceAliases sync.Map
|
||||
saveTimer *time.Timer
|
||||
saveMutex sync.Mutex
|
||||
afterSaveTime = 20 * time.Second
|
||||
)
|
||||
var (
|
||||
ErrServiceNotFound = errors.New("service not found")
|
||||
ErrDiscoveryTimeout = errors.New("service discovery timeout for: %w")
|
||||
ErrServiceDiscovery = errors.New("service discovery failed for: %w")
|
||||
ErrNoValidService = errors.New("no valid service found for: %w")
|
||||
)
|
||||
|
||||
func loadPredefinedAliases() map[string][]string {
|
||||
return map[string][]string{
|
||||
"clam": {"clamav-daemon.service", "clamd@scan.service", "clamd"},
|
||||
"freshclam": {"clamav-freshclam.service", "freshclam.service"},
|
||||
"fail2ban": {"fail2ban.service", "fail2ban"},
|
||||
"supervisor": {"supervisord.service", "supervisor.service", "supervisord", "supervisor"},
|
||||
"ssh": {"sshd.service", "ssh.service", "sshd", "ssh"},
|
||||
"1panel": {"1panel.service", "1paneld"},
|
||||
"docker": {"docker.service", "dockerd"},
|
||||
}
|
||||
}
|
||||
|
||||
func InitializeServiceDiscovery() {
|
||||
svcName := loadAliasesFromConfig()
|
||||
if len(svcName) > 0 {
|
||||
RegisterServiceAliases(svcName)
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterServiceAliases(aliases map[string][]string) {
|
||||
for key, values := range aliases {
|
||||
existing, loaded := serviceAliases.LoadOrStore(key, values)
|
||||
if loaded {
|
||||
merged := append(existing.([]string), values...)
|
||||
serviceAliases.Store(key, merged)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadAliasesFromConfig() map[string][]string {
|
||||
data, err := os.ReadFile(aliasFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var rawAliases map[string][]string
|
||||
json.Unmarshal(data, &rawAliases)
|
||||
|
||||
validAliases := make(map[string][]string)
|
||||
for key, aliases := range rawAliases {
|
||||
valid := []string{}
|
||||
for _, alias := range aliases {
|
||||
confirmed, _ := confirmServiceExists(alias)
|
||||
if confirmed {
|
||||
valid = append(valid, alias)
|
||||
}
|
||||
}
|
||||
if len(valid) > 0 {
|
||||
validAliases[key] = valid
|
||||
}
|
||||
}
|
||||
return validAliases
|
||||
}
|
||||
|
||||
func cleanupKeywordAliases(keyword string) {
|
||||
serviceAliases.Range(func(k, v interface{}) bool {
|
||||
if k.(string) != keyword {
|
||||
return true
|
||||
}
|
||||
aliases := v.([]string)
|
||||
valid := make([]string, 0)
|
||||
for _, alias := range aliases {
|
||||
confirmed, _ := confirmServiceExists(alias)
|
||||
if confirmed {
|
||||
valid = append(valid, alias)
|
||||
}
|
||||
}
|
||||
if len(valid) == 0 {
|
||||
serviceAliases.Delete(k)
|
||||
serviceExistenceCache.Delete(k)
|
||||
} else {
|
||||
serviceAliases.Store(k, valid)
|
||||
}
|
||||
return true
|
||||
})
|
||||
go scheduleSave()
|
||||
}
|
||||
|
||||
func smartServiceName(keyword string) (string, error) {
|
||||
mgr := GetGlobalManager()
|
||||
processedName := handleServiceNaming(mgr, keyword)
|
||||
|
||||
confirmed, _ := confirmServiceExists(processedName)
|
||||
if confirmed {
|
||||
updateAliases(keyword, processedName)
|
||||
return processedName, nil
|
||||
}
|
||||
|
||||
candidates := append([]string{processedName}, getAliases(keyword)...)
|
||||
if name, err := validateCandidatesConcurrently(candidates); err == nil {
|
||||
updateAliases(keyword, name)
|
||||
return name, nil
|
||||
}
|
||||
|
||||
discoveredName, err := discoverAndSelectService(keyword)
|
||||
if err != nil {
|
||||
cleanupKeywordAliases(keyword)
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
updateAliases(keyword, discoveredName)
|
||||
return discoveredName, nil
|
||||
}
|
||||
func handleServiceNaming(mgr ServiceManager, keyword string) string {
|
||||
keyword = strings.ToLower(keyword)
|
||||
if strings.HasSuffix(keyword, ".service.socket") {
|
||||
keyword = strings.TrimSuffix(keyword, ".service.socket") + ".socket"
|
||||
}
|
||||
if mgr.Name() != "systemd" {
|
||||
keyword = strings.TrimSuffix(keyword, ".service")
|
||||
return keyword
|
||||
}
|
||||
if !strings.HasSuffix(keyword, ".service") &&
|
||||
!strings.HasSuffix(keyword, ".socket") {
|
||||
keyword += ".service"
|
||||
}
|
||||
return keyword
|
||||
}
|
||||
func validateCandidatesConcurrently(candidates []string) (string, error) {
|
||||
var (
|
||||
g errgroup.Group
|
||||
found = make(chan string, 1)
|
||||
)
|
||||
|
||||
for _, candidate := range candidates {
|
||||
cand := candidate
|
||||
g.Go(func() error {
|
||||
confirmed, _ := confirmServiceExists(cand)
|
||||
if confirmed {
|
||||
select {
|
||||
case found <- cand:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ErrServiceNotFound
|
||||
})
|
||||
}
|
||||
|
||||
resultErr := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(found)
|
||||
resultErr <- g.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case name := <-found:
|
||||
return name, nil
|
||||
case <-time.After(1000 * time.Millisecond):
|
||||
return "", fmt.Errorf(ErrDiscoveryTimeout.Error(), candidates[0])
|
||||
case err := <-resultErr:
|
||||
if err != nil {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
}
|
||||
func discoverAndSelectService(keyword string) (string, error) {
|
||||
discovered, err := discoverServices(keyword)
|
||||
if err != nil {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
|
||||
if len(discovered) == 0 {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
selected, err := selectBestMatch(keyword, discovered)
|
||||
if err != nil {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
|
||||
confirmed, err := confirmServiceExists(selected)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("service existence check failed: %w", err)
|
||||
}
|
||||
if confirmed {
|
||||
return selected, nil
|
||||
}
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
func selectBestMatch(keyword string, candidates []string) (string, error) {
|
||||
if len(candidates) == 0 {
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
lowerKeyword := strings.ToLower(keyword)
|
||||
var exactMatch string
|
||||
var firstContainMatch string
|
||||
for _, name := range candidates {
|
||||
if strings.EqualFold(name, keyword) {
|
||||
exactMatch = name
|
||||
break
|
||||
}
|
||||
}
|
||||
if exactMatch != "" {
|
||||
return exactMatch, nil
|
||||
}
|
||||
for _, name := range candidates {
|
||||
if strings.Contains(strings.ToLower(name), lowerKeyword) {
|
||||
firstContainMatch = name
|
||||
global.LOG.Debugf("[%s] [keyword: %s] Found first contain match: %s", getManagerName(), keyword, firstContainMatch)
|
||||
break
|
||||
}
|
||||
}
|
||||
if firstContainMatch != "" {
|
||||
return firstContainMatch, nil
|
||||
}
|
||||
return "", fmt.Errorf("%w: %q (no exact or partial match)", ErrNoValidService, keyword)
|
||||
}
|
||||
|
||||
type cacheItem struct {
|
||||
services []string
|
||||
expires time.Time
|
||||
exists bool
|
||||
}
|
||||
|
||||
var (
|
||||
discoveryCache sync.Map
|
||||
discoveryGroup singleflight.Group
|
||||
)
|
||||
|
||||
func discoverServices(keyword string) ([]string, error) {
|
||||
result, err := discoveryGroup.Do(keyword, func() (interface{}, error) {
|
||||
if cached, ok := discoveryCache.Load(keyword); ok {
|
||||
item := cached.(cacheItem)
|
||||
if time.Now().Before(item.expires) {
|
||||
return item.services, nil
|
||||
}
|
||||
discoveryCache.Delete(keyword)
|
||||
}
|
||||
manager := GetGlobalManager()
|
||||
results, err := manager.FindServices(keyword)
|
||||
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Find services failed for %s: %v", keyword, err)
|
||||
return nil, fmt.Errorf("%w: %q (%v)", ErrServiceDiscovery, keyword, err)
|
||||
} else {
|
||||
discoveryCache.Store(keyword, cacheItem{
|
||||
services: results,
|
||||
expires: time.Now().Add(5 * time.Minute),
|
||||
})
|
||||
}
|
||||
return results, err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.([]string), nil
|
||||
}
|
||||
|
||||
func updateAliases(keyword, alias string) {
|
||||
if keyword == alias {
|
||||
return
|
||||
}
|
||||
existing, _ := serviceAliases.LoadOrStore(keyword, []string{})
|
||||
aliases := existing.([]string)
|
||||
if contains(aliases, alias) {
|
||||
return
|
||||
}
|
||||
serviceAliases.Store(keyword, append(aliases, alias))
|
||||
go scheduleSave()
|
||||
}
|
||||
|
||||
func scheduleSave() {
|
||||
saveMutex.Lock()
|
||||
defer saveMutex.Unlock()
|
||||
|
||||
if saveTimer != nil {
|
||||
saveTimer.Stop()
|
||||
}
|
||||
|
||||
dataSnapshot := make(map[string][]string)
|
||||
serviceAliases.Range(func(k, v interface{}) bool {
|
||||
dataSnapshot[k.(string)] = append([]string{}, v.([]string)...)
|
||||
return true
|
||||
})
|
||||
aliasFile = filepath.Join(constant.ResourceDir, "svcaliases.json")
|
||||
saveTimer = time.AfterFunc(afterSaveTime, func() {
|
||||
tmpFile := aliasFile + ".tmp"
|
||||
if err := saveAliasesToFile(dataSnapshot, tmpFile); err == nil {
|
||||
os.Rename(tmpFile, aliasFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
func saveAliasesToFile(data map[string][]string, path string) error {
|
||||
fileData, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("serialization failed: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, fileData, 0644); err != nil {
|
||||
return fmt.Errorf("file write failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var serviceExistenceCache sync.Map
|
||||
|
||||
func confirmServiceExists(serviceName string) (bool, error) {
|
||||
if val, ok := serviceExistenceCache.Load(serviceName); ok {
|
||||
if item, ok := val.(cacheItem); ok && time.Now().Before(item.expires) {
|
||||
return item.exists, nil
|
||||
}
|
||||
serviceExistenceCache.Delete(serviceName)
|
||||
}
|
||||
handler := NewServiceHandler(defaultServiceConfig(serviceName))
|
||||
isExist, err := handler.IsExists()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("check service existence failed: %w", err)
|
||||
}
|
||||
serviceExistenceCache.Store(serviceName, cacheItem{
|
||||
exists: isExist.IsExists,
|
||||
expires: time.Now().Add(30 * time.Second),
|
||||
})
|
||||
return isExist.IsExists, nil
|
||||
}
|
||||
|
||||
func getAliases(keyword string) []string {
|
||||
predefined := loadPredefinedAliases()[keyword]
|
||||
runtimeAliases, _ := serviceAliases.LoadOrStore(keyword, []string{})
|
||||
merged := make(map[string]struct{})
|
||||
for _, alias := range predefined {
|
||||
merged[alias] = struct{}{}
|
||||
}
|
||||
for _, alias := range runtimeAliases.([]string) {
|
||||
merged[alias] = struct{}{}
|
||||
}
|
||||
result := make([]string, 0, len(merged))
|
||||
for k := range merged {
|
||||
result = append(result, k)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type ConfigOption struct {
|
||||
TailLines string
|
||||
}
|
||||
|
||||
func ViewConfig(path string, opt ConfigOption) (string, error) {
|
||||
var cmd []string
|
||||
if opt.TailLines != "" && opt.TailLines != "0" {
|
||||
cmd = []string{"tail", "-n", opt.TailLines, path}
|
||||
} else {
|
||||
cmd = []string{"cat", path}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
output, err := executeCommand(ctx, cmd[0], cmd[1:]...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("view config failed: %w", err)
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package systemctl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
type ServiceConfig struct {
|
||||
ServiceName map[string]string
|
||||
}
|
||||
|
||||
type ServiceHandler struct {
|
||||
config *ServiceConfig
|
||||
manager ServiceManager
|
||||
}
|
||||
|
||||
func NewServiceHandler(serviceNames map[string]string) *ServiceHandler {
|
||||
mgr := GetGlobalManager()
|
||||
if mgr == nil {
|
||||
global.LOG.Error("failed to get global service manager when creating ServiceHandler")
|
||||
return nil
|
||||
}
|
||||
return &ServiceHandler{
|
||||
config: &ServiceConfig{
|
||||
ServiceName: serviceNames,
|
||||
},
|
||||
manager: mgr,
|
||||
}
|
||||
}
|
||||
|
||||
type ServiceStatus struct {
|
||||
IsActive bool `json:"isActive"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
IsExists bool `json:"isExists"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
type ServiceIsActive struct {
|
||||
IsActive bool `json:"isActive"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
type ServiceIsEnabled struct {
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
type ServiceResult struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
var (
|
||||
BinaryPath = "/usr/local/bin" // 1panl service default path
|
||||
ErrServiceNotExist = errors.New("service does not exist")
|
||||
)
|
||||
|
||||
func defaultServiceConfig(serviceName string) map[string]string {
|
||||
mgr := getManagerName()
|
||||
if mgr == "" {
|
||||
global.LOG.Error("failed to get manager name for default service config")
|
||||
return nil
|
||||
}
|
||||
return map[string]string{
|
||||
mgr: serviceName,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) ManagerName() string { return h.manager.Name() }
|
||||
func getManagerName() string {
|
||||
if mgr := GetGlobalManager(); mgr != nil {
|
||||
return mgr.Name()
|
||||
}
|
||||
global.LOG.Error("failed to get global service manager")
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) GetServiceName() string {
|
||||
manager := h.ManagerName()
|
||||
if manager == "" {
|
||||
global.LOG.Error("manager name is empty when getting service name")
|
||||
return ""
|
||||
}
|
||||
return h.config.ServiceName[manager]
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) GetServicePath() (string, error) {
|
||||
manager := h.ManagerName()
|
||||
serviceName := h.config.ServiceName[manager]
|
||||
|
||||
if serviceName == "" {
|
||||
err := fmt.Errorf("service name not found for %s", manager)
|
||||
global.LOG.Errorf("GetServicePath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
cleanPath := filepath.Clean(serviceName)
|
||||
if strings.Contains(cleanPath, "..") {
|
||||
err := fmt.Errorf("invalid path: %q", cleanPath)
|
||||
global.LOG.Errorf("GetServicePath security check failed: %v", err)
|
||||
return "", err
|
||||
}
|
||||
switch manager {
|
||||
case "systemd":
|
||||
return findSystemdPath(cleanPath)
|
||||
case "openrc", "sysvinit":
|
||||
return checkInitDPath(cleanPath)
|
||||
default:
|
||||
err := fmt.Errorf("unsupported init system: %s", manager)
|
||||
global.LOG.Errorf("GetServicePath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
func findSystemdPath(name string) (string, error) {
|
||||
paths := []string{"/etc/systemd/system", "/usr/lib/systemd/system",
|
||||
"/usr/share/systemd/system", "/usr/local/lib/systemd/system"}
|
||||
|
||||
for _, p := range paths {
|
||||
if path := filepath.Join(p, name); FileExist(path) {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
err := fmt.Errorf("service path not found for %s", name)
|
||||
global.LOG.Errorf("findSystemdPath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
func checkInitDPath(name string) (string, error) {
|
||||
path := filepath.Join("/etc/init.d", name)
|
||||
if !FileExist(path) {
|
||||
err := fmt.Errorf("service path not found for %s", name)
|
||||
global.LOG.Errorf("checkInitDPath error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) ExecuteAction(action string) (ServiceResult, error) {
|
||||
successMsg := fmt.Sprintf("%s : %s completed", action, h.GetServiceName())
|
||||
return h.executeAction(action, successMsg)
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) IsExists() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
isExist, _ := manager.ServiceExists(h.config)
|
||||
return ServiceStatus{
|
||||
IsExists: isExist,
|
||||
}, nil
|
||||
}
|
||||
func (h *ServiceHandler) IsActive() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
if manager == nil {
|
||||
global.LOG.Error("service manager not initialized during active check")
|
||||
return ServiceStatus{}, fmt.Errorf("service manager not initialized")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
activeCmd, err := manager.BuildCommand("status", h.config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Build status command failed: %v", err)
|
||||
return ServiceStatus{}, fmt.Errorf("build status command failed: %w", err)
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, activeCmd[0], activeCmd[1:]...)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "inactive") {
|
||||
return ServiceStatus{
|
||||
IsExists: false,
|
||||
}, nil
|
||||
}
|
||||
global.LOG.Errorf("Active check execution failed: %v", err)
|
||||
return ServiceStatus{}, fmt.Errorf("status check failed: %w", err)
|
||||
}
|
||||
|
||||
isActive, err := manager.ParseStatus(string(output), h.config, "active")
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Status parse error: %v", err)
|
||||
}
|
||||
return ServiceStatus{
|
||||
IsActive: isActive,
|
||||
Output: string(output),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) IsEnabled() (ServiceStatus, error) {
|
||||
manager := GetGlobalManager()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
enabledCmd, err := manager.BuildCommand("is-enabled", h.config)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
return ServiceStatus{
|
||||
IsEnabled: false,
|
||||
}, nil
|
||||
}
|
||||
global.LOG.Errorf("Build is-enabled command failed: %v", err)
|
||||
return ServiceStatus{}, fmt.Errorf("build enabled check command failed: %w", err)
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, enabledCmd[0], enabledCmd[1:]...)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "disabled") {
|
||||
return ServiceStatus{
|
||||
IsEnabled: false,
|
||||
}, nil
|
||||
}
|
||||
// // isEnabled, err := h.ParseStatus(string(output), h.config, "enabled")
|
||||
// global.LOG.Errorf("Enabled check execution failed: %v", err)
|
||||
// return ServiceStatus{}, fmt.Errorf("enabled check failed: %w", err)
|
||||
}
|
||||
|
||||
isEnabled, err := manager.ParseStatus(string(output), h.config, "enabled")
|
||||
if err != nil {
|
||||
global.LOG.Warnf("Enabled status parse error: %v", err)
|
||||
}
|
||||
return ServiceStatus{
|
||||
IsEnabled: isEnabled,
|
||||
Output: string(output),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) StartService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("start")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) StopService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("stop")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) RestartService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("restart")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) EnableService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("enable")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) DisableService() (ServiceResult, error) {
|
||||
return h.ExecuteAction("disable")
|
||||
}
|
||||
|
||||
func (h *ServiceHandler) executeAction(action, successMsg string) (ServiceResult, error) {
|
||||
manager := GetGlobalManager()
|
||||
if manager == nil {
|
||||
global.LOG.Error("service manager not initialized during action execution")
|
||||
return ServiceResult{}, fmt.Errorf("service manager not initialized")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultCommandTimeout)
|
||||
defer cancel()
|
||||
|
||||
cmdArgs, err := manager.BuildCommand(action, h.config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Build command failed for action %s: %v", action, err)
|
||||
return ServiceResult{}, fmt.Errorf("build command failed: %w", err)
|
||||
}
|
||||
|
||||
output, err := executeCommand(ctx, cmdArgs[0], cmdArgs[1:]...)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("%s operation failed: %v", action, err)
|
||||
return ServiceResult{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("%s failed", action),
|
||||
Output: string(output),
|
||||
}, fmt.Errorf("%s operation failed: %w", action, err)
|
||||
}
|
||||
|
||||
global.LOG.Infof("[%s]: %s", manager.Name(), successMsg)
|
||||
return ServiceResult{
|
||||
Success: true,
|
||||
Message: successMsg,
|
||||
Output: string(output),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
package systemctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
var (
|
||||
managers = make(map[string]ServiceManager)
|
||||
mu sync.RWMutex
|
||||
globalManager ServiceManager
|
||||
managerPriority = []string{"systemd", "openrc", "sysvinit"}
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCommandTimeout = 60 * time.Second
|
||||
serviceCheckTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type ServiceManager interface {
|
||||
Name() string
|
||||
IsAvailable() bool
|
||||
ServiceExists(*ServiceConfig) (bool, error)
|
||||
BuildCommand(string, *ServiceConfig) ([]string, error)
|
||||
ParseStatus(string, *ServiceConfig, string) (bool, error)
|
||||
FindServices(string) ([]string, error)
|
||||
}
|
||||
|
||||
type baseManager struct {
|
||||
name string
|
||||
cmdTool string
|
||||
activeRegex *regexp.Regexp
|
||||
enabledRegex *regexp.Regexp
|
||||
}
|
||||
|
||||
func (b *baseManager) Name() string { return b.name }
|
||||
|
||||
func isRootUser() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
func (b *baseManager) buildBaseCommand() []string {
|
||||
var cmdArgs []string
|
||||
if !isRootUser() {
|
||||
cmdArgs = append(cmdArgs, "sudo")
|
||||
}
|
||||
cmdArgs = append(cmdArgs, b.cmdTool)
|
||||
return cmdArgs
|
||||
}
|
||||
func (b *baseManager) commonServiceExists(config *ServiceConfig, checkFn func(string) (bool, error)) (bool, error) {
|
||||
if name := config.ServiceName[b.name]; name != "" {
|
||||
exists, checkErr := checkFn(name)
|
||||
if checkErr != nil {
|
||||
return false, nil
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
func (b *baseManager) ParseStatus(output string, _ *ServiceConfig, statusType string) (bool, error) {
|
||||
if output == "" {
|
||||
return false, nil
|
||||
}
|
||||
switch statusType {
|
||||
case "active":
|
||||
if b.activeRegex == nil {
|
||||
return false, nil
|
||||
}
|
||||
return b.activeRegex.MatchString(output), nil
|
||||
case "enabled":
|
||||
if b.enabledRegex == nil {
|
||||
return false, nil
|
||||
}
|
||||
return b.enabledRegex.MatchString(output), nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func registerManager(m ServiceManager) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
managers[m.Name()] = m
|
||||
}
|
||||
func init() {
|
||||
for _, mgr := range []ServiceManager{
|
||||
newSystemdManager(),
|
||||
newOpenrcManager(),
|
||||
newSysvinitManager(),
|
||||
} {
|
||||
registerManager(mgr)
|
||||
}
|
||||
}
|
||||
func InitializeGlobalManager() (err error) {
|
||||
|
||||
for _, name := range managerPriority {
|
||||
if mgr, ok := managers[name]; ok && mgr.IsAvailable() {
|
||||
if testManager(mgr) {
|
||||
globalManager = mgr
|
||||
global.LOG.Infof("Initialized service manager: %s", name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("no available service manager found (tried: %v)", managerPriority)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func testManager(mgr ServiceManager) bool {
|
||||
_, err := mgr.BuildCommand("status", &ServiceConfig{
|
||||
ServiceName: map[string]string{mgr.Name(): "test-service"},
|
||||
})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func GetGlobalManager() ServiceManager {
|
||||
if globalManager == nil {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if globalManager == nil {
|
||||
return initializeWithRetry()
|
||||
}
|
||||
}
|
||||
return globalManager
|
||||
}
|
||||
func initializeWithRetry() ServiceManager {
|
||||
const (
|
||||
maxRetries = 5
|
||||
initialWait = 1 * time.Second
|
||||
)
|
||||
backoff := initialWait
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
if err := InitializeGlobalManager(); err == nil {
|
||||
return globalManager
|
||||
}
|
||||
|
||||
logMessage := fmt.Sprintf("Manager init attempt %d/%d failed", attempt, maxRetries)
|
||||
if global.LOG != nil {
|
||||
global.LOG.Warn(logMessage)
|
||||
} else {
|
||||
fmt.Printf("[WARN] %s\n", logMessage)
|
||||
}
|
||||
|
||||
if attempt < maxRetries {
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
}
|
||||
|
||||
if global.LOG != nil {
|
||||
global.LOG.Error("All manager initialization attempts failed")
|
||||
} else {
|
||||
fmt.Println("[FATAL] All manager initialization attempts failed")
|
||||
}
|
||||
panic("unable to initialize service manager")
|
||||
}
|
||||
func executeCommand(ctx context.Context, command string, args ...string) ([]byte, error) {
|
||||
if _, ok := ctx.Deadline(); !ok {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, defaultCommandTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, command, args...)
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return buf.Bytes(), &CommandError{
|
||||
Cmd: cmd.String(),
|
||||
Output: buf.String(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type systemdManager struct{ baseManager }
|
||||
|
||||
func newSystemdManager() ServiceManager {
|
||||
return &systemdManager{baseManager{
|
||||
name: "systemd",
|
||||
cmdTool: "systemctl",
|
||||
activeRegex: regexp.MustCompile(`(?i)Active:\s+active\b`),
|
||||
enabledRegex: regexp.MustCompile(`(?i)^\s*enabled\s*$`),
|
||||
}}
|
||||
}
|
||||
|
||||
func (m *systemdManager) IsAvailable() bool {
|
||||
_, err := exec.LookPath(m.cmdTool)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *systemdManager) ServiceExists(config *ServiceConfig) (bool, error) {
|
||||
return m.commonServiceExists(config, func(name string) (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), serviceCheckTimeout)
|
||||
defer cancel()
|
||||
out, cmdErr := executeCommand(ctx, m.cmdTool, "is-enabled", name)
|
||||
if cmdErr == nil || strings.Contains(string(out), "disabled") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *systemdManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) {
|
||||
cmdArgs := m.buildBaseCommand()
|
||||
service := config.ServiceName[m.name]
|
||||
switch action {
|
||||
case "is-enabled":
|
||||
cmdArgs = append(cmdArgs, "is-enabled", service)
|
||||
default:
|
||||
cmdArgs = append(cmdArgs, action, service)
|
||||
}
|
||||
return cmdArgs, nil
|
||||
}
|
||||
|
||||
func (m *systemdManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) {
|
||||
if strings.Contains(output, "could not be found") {
|
||||
return false, nil
|
||||
}
|
||||
result, err := m.baseManager.ParseStatus(output, config, statusType)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (m *systemdManager) FindServices(keyword string) ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
out, err := executeCommand(ctx, m.cmdTool, "list-unit-files", "--type=service", "--no-legend")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list systemd services: %w", err)
|
||||
}
|
||||
|
||||
var services []string
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 1 {
|
||||
continue
|
||||
}
|
||||
serviceName := fields[0]
|
||||
if strings.Contains(serviceName, keyword) {
|
||||
services = append(services, serviceName)
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
type sysvinitManager struct{ baseManager }
|
||||
|
||||
func newSysvinitManager() ServiceManager {
|
||||
return &sysvinitManager{baseManager{
|
||||
name: "sysvinit",
|
||||
cmdTool: "service",
|
||||
activeRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(running|active)\b(?:$|\s)`),
|
||||
enabledRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(enabled)\b(?:$|\s)`),
|
||||
}}
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) IsAvailable() bool {
|
||||
_, err := exec.LookPath(m.cmdTool)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) ServiceExists(config *ServiceConfig) (bool, error) {
|
||||
return m.commonServiceExists(config, func(name string) (bool, error) {
|
||||
_, err := os.Stat(filepath.Join("/etc/init.d", name))
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", name, err)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) {
|
||||
service := config.ServiceName[m.name]
|
||||
switch action {
|
||||
case "is-enabled":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", service)}, nil
|
||||
case "is-active", "status":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", service),
|
||||
}, nil
|
||||
default:
|
||||
cmdArgs := m.buildBaseCommand()
|
||||
cmdArgs = append(cmdArgs, service, action)
|
||||
return cmdArgs, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) {
|
||||
serviceName := config.ServiceName[m.name]
|
||||
switch statusType {
|
||||
case "enabled":
|
||||
if strings.Contains(output, "no such file or directory") {
|
||||
return false, nil
|
||||
}
|
||||
return strings.TrimSpace(output) != "", nil
|
||||
case "active":
|
||||
if strings.Contains(output, "not found") {
|
||||
return false, nil
|
||||
}
|
||||
if strings.Contains(output, "running") || strings.Contains(output, "active") {
|
||||
return true, nil
|
||||
}
|
||||
default:
|
||||
result, err := m.baseManager.ParseStatus(output, config, statusType)
|
||||
if err != nil {
|
||||
global.LOG.Debugf("[sysvinit] Status parse failed. [ServiceName:%s] Type: %s, Output: %q", serviceName, statusType, output)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
return false, fmt.Errorf("unsupported status type: %s", statusType)
|
||||
}
|
||||
|
||||
func (m *sysvinitManager) FindServices(keyword string) ([]string, error) {
|
||||
files, err := os.ReadDir("/etc/init.d/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read init.d directory: %w", err)
|
||||
}
|
||||
|
||||
var services []string
|
||||
for _, file := range files {
|
||||
if strings.Contains(file.Name(), keyword) {
|
||||
services = append(services, file.Name())
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
type openrcManager struct{ baseManager }
|
||||
|
||||
func newOpenrcManager() ServiceManager {
|
||||
return &openrcManager{baseManager{
|
||||
name: "openrc",
|
||||
cmdTool: "rc-service",
|
||||
// activeRegex: regexp.MustCompile(`(?i)^\s*status:\s+(started|running|active)\s*$`),
|
||||
// enabledRegex: regexp.MustCompile(`(?i)^[^\|]+\|\s*(default|enabled)\b.*$`),
|
||||
activeRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(running|active)\b(?:$|\s)`),
|
||||
enabledRegex: regexp.MustCompile(`(?i)(?:^|\s)\b(enabled)\b(?:$|\s)`),
|
||||
}}
|
||||
}
|
||||
func (m *openrcManager) IsAvailable() bool {
|
||||
_, err := exec.LookPath(m.cmdTool)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (m *openrcManager) ServiceExists(config *ServiceConfig) (bool, error) {
|
||||
return m.commonServiceExists(config, func(name string) (bool, error) {
|
||||
_, err := os.Stat(filepath.Join("/etc/init.d", name))
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", name, err)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *openrcManager) BuildCommand(action string, config *ServiceConfig) ([]string, error) {
|
||||
cmdArgs := m.buildBaseCommand()
|
||||
service := config.ServiceName[m.name]
|
||||
switch action {
|
||||
case "is-enabled":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if ls /etc/runlevels/default/%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", service)}, nil
|
||||
case "is-active", "status":
|
||||
return []string{
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", service),
|
||||
}, nil
|
||||
case "enable":
|
||||
return []string{"rc-update", "add", service, "default"}, nil
|
||||
case "disable":
|
||||
return []string{"rc-update", "del", service, "default"}, nil
|
||||
default:
|
||||
cmdArgs = append(cmdArgs, service, action)
|
||||
return cmdArgs, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *openrcManager) ParseStatus(output string, config *ServiceConfig, statusType string) (bool, error) {
|
||||
if strings.Contains(output, "does not exist") {
|
||||
return false, nil
|
||||
}
|
||||
result, err := m.baseManager.ParseStatus(output, config, statusType)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (m *openrcManager) FindServices(keyword string) ([]string, error) {
|
||||
files, err := os.ReadDir("/etc/init.d/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read init.d directory: %w", err)
|
||||
}
|
||||
keyword = strings.ToLower(keyword)
|
||||
var services []string
|
||||
for _, file := range files {
|
||||
if strings.Contains(file.Name(), keyword) {
|
||||
services = append(services, file.Name())
|
||||
}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
type CommandError struct {
|
||||
Cmd string
|
||||
Err error
|
||||
Output string
|
||||
}
|
||||
|
||||
func (e CommandError) Error() string {
|
||||
return fmt.Sprintf("command %q failed: %v \nOutput: %s",
|
||||
e.Cmd, e.Err, e.Output)
|
||||
}
|
||||
@@ -2,63 +2,153 @@ package systemctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
func RunSystemCtl(args ...string) (string, error) {
|
||||
cmd := exec.Command("systemctl", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
func DefaultHandler(serviceName string) (*ServiceHandler, error) {
|
||||
svcName, err := smartServiceName(serviceName)
|
||||
if err != nil {
|
||||
return string(output), fmt.Errorf("failed to run command: %w", err)
|
||||
return nil, ErrServiceNotFound
|
||||
}
|
||||
return string(output), nil
|
||||
return NewServiceHandler(defaultServiceConfig(svcName)), nil
|
||||
}
|
||||
|
||||
func IsActive(serviceName string) (bool, error) {
|
||||
out, err := RunSystemCtl("is-active", serviceName)
|
||||
func GetServiceName(serviceName string) (string, error) {
|
||||
serviceName, err := smartServiceName(serviceName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
return out == "active\n", nil
|
||||
return serviceName, nil
|
||||
}
|
||||
|
||||
func IsEnable(serviceName string) (bool, error) {
|
||||
out, err := RunSystemCtl("is-enabled", serviceName)
|
||||
func GetServicePath(serviceName string) (string, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return "", ErrServiceNotFound
|
||||
}
|
||||
return out == "enabled\n", nil
|
||||
return handler.GetServicePath()
|
||||
}
|
||||
|
||||
func CustomAction(action string, serviceName string) (ServiceResult, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("CustomAction handler init failed: %v", err)
|
||||
return ServiceResult{}, ErrServiceNotFound
|
||||
}
|
||||
result, err := handler.ExecuteAction(action)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("CustomAction %s failed: %v", action, err)
|
||||
return result, fmt.Errorf("%s operation failed: %w | Output: %s", action, err, result.Output)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func IsExist(serviceName string) (bool, error) {
|
||||
out, err := RunSystemCtl("is-enabled", serviceName)
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
if strings.Contains(out, "disabled") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
result, _ := handler.IsExists()
|
||||
if result.IsExists {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func handlerErr(out string, err error) error {
|
||||
func Start(serviceName string) error {
|
||||
handler, _ := DefaultHandler(serviceName)
|
||||
result, err := handler.StartService()
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
return errors.New(out)
|
||||
}
|
||||
return err
|
||||
global.LOG.Errorf("Service start failed: %v | Output: %s", err, result.Output)
|
||||
return fmt.Errorf("start failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Stop(serviceName string) error {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Stop handler init failed: %v", err)
|
||||
return fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
result, err := handler.StopService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service stop failed: %v", err)
|
||||
return fmt.Errorf("stop failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Restart(serviceName string) error {
|
||||
out, err := RunSystemCtl("restart", serviceName)
|
||||
return handlerErr(out, err)
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Restart handler init failed: %v", err)
|
||||
return fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
result, err := handler.RestartService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service restart failed: %v", err)
|
||||
return fmt.Errorf("restart failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Operate(operate, serviceName string) error {
|
||||
out, err := RunSystemCtl(operate, serviceName)
|
||||
return handlerErr(out, err)
|
||||
func Enable(serviceName string) error {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Enable handler init failed: %v", err)
|
||||
return fmt.Errorf("%s is not exist", serviceName)
|
||||
}
|
||||
result, err := handler.EnableService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service enable failed: %v | Output: %s", err, result.Output)
|
||||
return fmt.Errorf("%s enable failed: %v ", serviceName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Disable(serviceName string) error {
|
||||
handler, _ := DefaultHandler(serviceName)
|
||||
result, err := handler.DisableService()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("Service disable failed: %v", err)
|
||||
return fmt.Errorf("disable failed: %v | Output: %s", err, result.Output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsActive(serviceName string) (bool, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
status, err := handler.IsActive()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return status.IsActive, nil
|
||||
}
|
||||
|
||||
func IsEnable(serviceName string) (bool, error) {
|
||||
handler, err := DefaultHandler(serviceName)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
status, err := handler.IsEnabled()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return status.IsEnabled, nil
|
||||
}
|
||||
|
||||
type LogOption struct {
|
||||
TailLines string
|
||||
}
|
||||
|
||||
func FileExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -25,8 +25,13 @@ type LocalCommand struct {
|
||||
pty *os.File
|
||||
}
|
||||
|
||||
func NewCommand(commands []string) (*LocalCommand, error) {
|
||||
cmd := exec.Command("docker", commands...)
|
||||
func NewCommand(initCmd []string) (*LocalCommand, error) {
|
||||
cmd := exec.Command("docker", initCmd...)
|
||||
if term := os.Getenv("TERM"); term != "" {
|
||||
cmd.Env = append(os.Environ(), "TERM="+term)
|
||||
} else {
|
||||
cmd.Env = append(os.Environ(), "TERM=xterm")
|
||||
}
|
||||
|
||||
pty, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
@@ -90,4 +95,5 @@ func (lcmd *LocalCommand) Wait(quitChan chan bool) {
|
||||
global.LOG.Errorf("ssh session wait failed, err: %v", err)
|
||||
setQuit(quitChan)
|
||||
}
|
||||
setQuit(quitChan)
|
||||
}
|
||||
|
||||
@@ -53,6 +53,11 @@ func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) {
|
||||
}
|
||||
|
||||
func (sws *LocalWsSession) masterWrite(data []byte) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
global.LOG.Errorf("A panic occurred during write ws message to master, error message: %v", r)
|
||||
}
|
||||
}()
|
||||
sws.writeMutex.Lock()
|
||||
defer sws.writeMutex.Unlock()
|
||||
wsData, err := json.Marshal(WsMsg{
|
||||
@@ -72,6 +77,7 @@ func (sws *LocalWsSession) masterWrite(data []byte) error {
|
||||
func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
setQuit(exitCh)
|
||||
global.LOG.Errorf("A panic occurred during receive ws message, error message: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -3,6 +3,7 @@ package toolbox
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
@@ -22,15 +23,15 @@ type FirewallClient interface {
|
||||
}
|
||||
|
||||
func NewFail2Ban() (*Fail2ban, error) {
|
||||
isExist, _ := systemctl.IsExist("fail2ban.service")
|
||||
isExist, _ := systemctl.IsExist("fail2ban")
|
||||
if isExist {
|
||||
if _, err := os.Stat(defaultPath); err != nil {
|
||||
if err := initLocalFile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stdout, err := cmd.Exec("systemctl restart fail2ban.service")
|
||||
err := systemctl.Restart("fail2ban")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("restart fail2ban failed, err: %s", stdout)
|
||||
global.LOG.Errorf("restart fail2ban failed, err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -47,20 +48,25 @@ func (f *Fail2ban) Status() (bool, bool, bool) {
|
||||
}
|
||||
|
||||
func (f *Fail2ban) Version() string {
|
||||
stdout, err := cmd.Exec("fail2ban-client version")
|
||||
stdout, err := cmd.Exec("fail2ban-client --version")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load the fail2ban version failed, err: %s", stdout)
|
||||
return "-"
|
||||
}
|
||||
return strings.ReplaceAll(stdout, "\n", "")
|
||||
versionRe := regexp.MustCompile(`(?i)fail2ban[:\s-]*v?(\d+\.\d+\.\d+)`)
|
||||
matches := versionRe.FindStringSubmatch(stdout)
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
global.LOG.Errorf("Version regex failed to match output: %s", stdout)
|
||||
return "-"
|
||||
}
|
||||
|
||||
func (f *Fail2ban) Operate(operate string) error {
|
||||
switch operate {
|
||||
case "start", "restart", "stop", "enable", "disable":
|
||||
stdout, err := cmd.Execf("systemctl %s fail2ban.service", operate)
|
||||
stdout, err := systemctl.CustomAction(operate, "fail2ban")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the fail2ban.service failed, err: %s", operate, stdout)
|
||||
return fmt.Errorf("%s the fail2ban failed, err: %s", operate, stdout.Output)
|
||||
}
|
||||
return nil
|
||||
case "reload":
|
||||
@@ -136,7 +142,32 @@ func initLocalFile() error {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
initFile := `#DEFAULT-START
|
||||
var initFile string
|
||||
if systemctl.GetGlobalManager().Name() == "openrc" {
|
||||
initFile = `[sshd]
|
||||
enabled = true
|
||||
filter = alpine-sshd
|
||||
port = $ssh_port
|
||||
logpath = $logpath
|
||||
maxretry = 2
|
||||
banaction = $banaction
|
||||
|
||||
[sshd-ddos]
|
||||
enabled = true
|
||||
filter = alpine-sshd-ddos
|
||||
port = $ssh_port
|
||||
logpath = /var/log/messages
|
||||
maxretry = 2
|
||||
|
||||
[sshd-key]
|
||||
enabled = true
|
||||
filter = alpine-sshd-key
|
||||
port = $ssh_port
|
||||
logpath = /var/log/messages
|
||||
maxretry = 2
|
||||
`
|
||||
} else {
|
||||
initFile = `# DEFAULT-START
|
||||
[DEFAULT]
|
||||
bantime = 600
|
||||
findtime = 300
|
||||
@@ -146,36 +177,93 @@ action = %(action_mwl)s
|
||||
#DEFAULT-END
|
||||
|
||||
[sshd]
|
||||
ignoreip = 127.0.0.1/8
|
||||
ignoreip = 127.0.0.1/8 ::1
|
||||
enabled = true
|
||||
filter = sshd
|
||||
port = 22
|
||||
port = $ssh_port
|
||||
maxretry = 5
|
||||
findtime = 300
|
||||
bantime = 600
|
||||
banaction = $banaction
|
||||
action = %(action_mwl)s
|
||||
logpath = $logpath`
|
||||
|
||||
banaction := ""
|
||||
if active, _ := systemctl.IsActive("firewalld"); active {
|
||||
banaction = "firewallcmd-ipset"
|
||||
} else if active, _ := systemctl.IsActive("ufw"); active {
|
||||
banaction = "ufw"
|
||||
} else {
|
||||
banaction = "iptables-allports"
|
||||
logpath = $logpath
|
||||
maxmatches = 3
|
||||
`
|
||||
}
|
||||
// 检测防火墙类型
|
||||
banaction := detectFirewall()
|
||||
|
||||
// 检测SSH端口(支持Alpine的sshd_config位置)
|
||||
sshPort := detectSSHPort()
|
||||
|
||||
// 检测日志路径(兼容Alpine的多种日志位置)
|
||||
logPath := detectAuthLogPath()
|
||||
|
||||
// 执行变量替换
|
||||
initFile = strings.ReplaceAll(initFile, "$banaction", banaction)
|
||||
|
||||
logPath := ""
|
||||
if _, err := os.Stat("/var/log/secure"); err == nil {
|
||||
logPath = "/var/log/secure"
|
||||
} else {
|
||||
logPath = "/var/log/auth.log"
|
||||
}
|
||||
initFile = strings.ReplaceAll(initFile, "$logpath", logPath)
|
||||
initFile = strings.ReplaceAll(initFile, "$ssh_port", sshPort)
|
||||
|
||||
if err := os.WriteFile(defaultPath, []byte(initFile), 0640); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectFirewall() string {
|
||||
if active, _ := systemctl.IsActive("firewalld"); active {
|
||||
return "firewallcmd-ipset"
|
||||
}
|
||||
if active, _ := systemctl.IsActive("ufw"); active {
|
||||
return "ufw"
|
||||
}
|
||||
if active, _ := systemctl.IsActive("iptables"); active {
|
||||
return "iptables-allports"
|
||||
}
|
||||
if active, _ := systemctl.IsActive("nftables"); active {
|
||||
return "nftables-allports"
|
||||
}
|
||||
return "iptables-allports"
|
||||
}
|
||||
|
||||
func detectAuthLogPath() string {
|
||||
paths := []string{
|
||||
"/var/log/auth.log", // 常见默认路径
|
||||
"/var/log/messages", // Alpine系统日志
|
||||
"/var/log/secure", // RHEL风格路径
|
||||
"/var/log/syslog", // Debian风格路径
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return "/var/log/auth.log"
|
||||
}
|
||||
|
||||
func detectSSHPort() string {
|
||||
// 检查标准sshd_config路径
|
||||
configPaths := []string{
|
||||
"/etc/ssh/sshd_config",
|
||||
"/etc/sshd_config",
|
||||
}
|
||||
|
||||
for _, path := range configPaths {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "Port") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 2 {
|
||||
return parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "22" // 默认SSH端口
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
@@ -12,6 +11,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
@@ -94,9 +95,9 @@ func (f *Ftp) Status() (bool, bool) {
|
||||
func (f *Ftp) Operate(operate string) error {
|
||||
switch operate {
|
||||
case "start", "restart", "stop":
|
||||
stdout, err := cmd.Execf("systemctl %s pure-ftpd.service", operate)
|
||||
stdout, err := systemctl.CustomAction(operate, "pure-ftpd")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s the pure-ftpd.service failed, err: %s", operate, stdout)
|
||||
return fmt.Errorf("%s the pure-ftpd service failed, err: %s", operate, stdout.Output)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
|
||||
+17
-10
@@ -9,22 +9,29 @@ if [ ! -f "1pctl" ]; then
|
||||
wget https://github.com/1Panel-dev/installer/raw/main/1pctl
|
||||
fi
|
||||
|
||||
if [ ! -f "1panel.service" ]; then
|
||||
wget https://github.com/1Panel-dev/installer/raw/main/1panel.service
|
||||
fi
|
||||
|
||||
if [ ! -f "install.sh" ]; then
|
||||
wget https://github.com/1Panel-dev/installer/raw/main/install.sh
|
||||
fi
|
||||
|
||||
if [ ! -f "GeoIP.mmdb" ]; then
|
||||
wget https://resource.1panel.pro/geo/GeoIP.mmdb
|
||||
if [ ! -d "initscript" ]; then
|
||||
wget https://github.com/1Panel-dev/installer/raw/main/initscript/1panel.service
|
||||
mkdir -p initscript && cd initscript
|
||||
for file in 1panel.service 1paneld.init 1paneld.openrc 1paneld.procd; do
|
||||
wget -q https://github.com/1Panel-dev/installer/raw/main/initscript/$file
|
||||
done
|
||||
cd ..
|
||||
fi
|
||||
|
||||
if [ ! -f "lang.tar.gz" ]; then
|
||||
wget https://resource.1panel.pro/language/lang.tar.gz
|
||||
tar zxvf lang.tar.gz
|
||||
rm -rf lang.tar.gz
|
||||
if [ ! -d "lang" ]; then
|
||||
mkdir -p lang && cd lang
|
||||
for lang in en fa pt-BR ru zh; do
|
||||
wget -q https://github.com/1Panel-dev/installer/raw/main/lang/$lang.sh
|
||||
done
|
||||
cd ..
|
||||
fi
|
||||
|
||||
if [ ! -f "GeoIP.mmdb" ]; then
|
||||
wget https://resource.fit2cloud.com/1panel/package/geo/GeoIP.mmdb
|
||||
fi
|
||||
|
||||
chmod 755 1pctl install.sh
|
||||
|
||||
@@ -58,8 +58,15 @@ var restoreCmd = &cobra.Command{
|
||||
geoPath := path.Join(global.CONF.System.BaseDir, "1panel/geo")
|
||||
_, _ = cmdUtils.Execf("mkdir %s && cp %s %s/", geoPath, path.Join(tmpPath, "GeoIP.mmdb"), geoPath)
|
||||
fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep3"))
|
||||
if err := common.CopyFile(path.Join(tmpPath, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
return err
|
||||
if isSystemd() {
|
||||
if err := common.CopyFile(path.Join(tmpPath, "1panel.service"), "/etc/systemd/system"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
if err := common.CopyFile(path.Join(tmpPath, "1paneld"), "/etc/init.d"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep4"))
|
||||
checkPointOfWal()
|
||||
@@ -124,3 +131,10 @@ func handleUnTar(sourceFile, targetDir string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSystemd() bool {
|
||||
if _, err := os.Stat("/etc/systemd/system"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ system:
|
||||
repo_url: https://resource.1panel.pro
|
||||
app_repo: https://apps.1panel.pro
|
||||
is_demo: false
|
||||
port: 8888
|
||||
is_intl: true
|
||||
port: 9999
|
||||
username: admin
|
||||
password: admin123
|
||||
|
||||
|
||||
+629
-626
File diff suppressed because it is too large
Load Diff
+629
-626
File diff suppressed because it is too large
Load Diff
+378
-384
@@ -3342,6 +3342,8 @@ definitions:
|
||||
type: string
|
||||
lastCleanTime:
|
||||
type: string
|
||||
licenseVerify:
|
||||
type: string
|
||||
localTime:
|
||||
type: string
|
||||
menuTabs:
|
||||
@@ -11237,381 +11239,6 @@ paths:
|
||||
formatEN: update group [name][type]
|
||||
formatZH: 更新组 [name][type]
|
||||
paramKeys: []
|
||||
/host/conffile/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHConf'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Update host SSH setting by file
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: update SSH conf
|
||||
formatZH: 修改 SSH 配置文件
|
||||
paramKeys: []
|
||||
/host/ssh/conf:
|
||||
get:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH conf
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/generate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.GenerateSSH'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Generate host SSH secret
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: generate SSH secret
|
||||
formatZH: '生成 SSH 密钥 '
|
||||
paramKeys: []
|
||||
/host/ssh/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SearchSSHLog'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHLog'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH logs
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/operate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.Operate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Operate SSH
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operation
|
||||
formatEN: '[operation] SSH'
|
||||
formatZH: '[operation] SSH '
|
||||
paramKeys: []
|
||||
/host/ssh/search:
|
||||
post:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHInfo'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH setting info
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/secret:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.GenerateLoad'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH secret
|
||||
tags:
|
||||
- SSH
|
||||
/host/ssh/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHUpdate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Update host SSH setting
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- key
|
||||
- value
|
||||
formatEN: update SSH setting [key] => [value]
|
||||
formatZH: 修改 SSH 配置 [key] => [value]
|
||||
paramKeys: []
|
||||
/host/tool:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.HostToolRes'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool status
|
||||
tags:
|
||||
- Host tool
|
||||
/host/tool/config:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolConfig'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.HostToolConfig'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] tool config'
|
||||
formatZH: '[operate] 主机工具配置文件 '
|
||||
paramKeys: []
|
||||
/host/tool/create:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolCreate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Create Host tool Config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- type
|
||||
formatEN: create [type] config
|
||||
formatZH: 创建 [type] 配置
|
||||
paramKeys: []
|
||||
/host/tool/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolLogReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool logs
|
||||
tags:
|
||||
- Host tool
|
||||
/host/tool/operate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Operate tool
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
- type
|
||||
formatEN: '[operate] [type]'
|
||||
formatZH: '[operate] [type] '
|
||||
paramKeys: []
|
||||
/host/tool/supervisor/process:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.SupervisorProcessConfig'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get Supervisor process config
|
||||
tags:
|
||||
- Host tool
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SupervisorProcessConfig'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Create Supervisor process
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] process'
|
||||
formatZH: '[operate] 守护进程 '
|
||||
paramKeys: []
|
||||
/host/tool/supervisor/process/file:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SupervisorProcessFileReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get Supervisor process config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] Supervisor Process Config file'
|
||||
formatZH: '[operate] Supervisor 进程文件 '
|
||||
paramKeys: []
|
||||
/hosts:
|
||||
post:
|
||||
consumes:
|
||||
@@ -11878,6 +11505,32 @@ paths:
|
||||
formatEN: update quick command [name]
|
||||
formatZH: 更新快捷命令 [name]
|
||||
paramKeys: []
|
||||
/hosts/conffile/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHConf'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Update host SSH setting by file
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: update SSH conf
|
||||
formatZH: 修改 SSH 配置文件
|
||||
paramKeys: []
|
||||
/hosts/del:
|
||||
post:
|
||||
consumes:
|
||||
@@ -12197,6 +11850,157 @@ paths:
|
||||
summary: Page host
|
||||
tags:
|
||||
- Host
|
||||
/hosts/ssh/conf:
|
||||
get:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH conf
|
||||
tags:
|
||||
- SSH
|
||||
/hosts/ssh/generate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.GenerateSSH'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Generate host SSH secret
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys: []
|
||||
formatEN: generate SSH secret
|
||||
formatZH: '生成 SSH 密钥 '
|
||||
paramKeys: []
|
||||
/hosts/ssh/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SearchSSHLog'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHLog'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH logs
|
||||
tags:
|
||||
- SSH
|
||||
/hosts/ssh/operate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.Operate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Operate SSH
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operation
|
||||
formatEN: '[operation] SSH'
|
||||
formatZH: '[operation] SSH '
|
||||
paramKeys: []
|
||||
/hosts/ssh/search:
|
||||
post:
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHInfo'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH setting info
|
||||
tags:
|
||||
- SSH
|
||||
/hosts/ssh/secret:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.GenerateLoad'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Load host SSH secret
|
||||
tags:
|
||||
- SSH
|
||||
/hosts/ssh/update:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.SSHUpdate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Update host SSH setting
|
||||
tags:
|
||||
- SSH
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- key
|
||||
- value
|
||||
formatEN: update SSH setting [key] => [value]
|
||||
formatZH: 修改 SSH 配置 [key] => [value]
|
||||
paramKeys: []
|
||||
/hosts/test/byid/{id}:
|
||||
post:
|
||||
consumes:
|
||||
@@ -12240,6 +12044,204 @@ paths:
|
||||
summary: Test host conn by info
|
||||
tags:
|
||||
- Host
|
||||
/hosts/tool:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.HostToolRes'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool status
|
||||
tags:
|
||||
- Host tool
|
||||
/hosts/tool/config:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolConfig'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.HostToolConfig'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] tool config'
|
||||
formatZH: '[operate] 主机工具配置文件 '
|
||||
paramKeys: []
|
||||
/hosts/tool/create:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolCreate'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Create Host tool Config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- type
|
||||
formatEN: create [type] config
|
||||
formatZH: 创建 [type] 配置
|
||||
paramKeys: []
|
||||
/hosts/tool/log:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolLogReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get tool logs
|
||||
tags:
|
||||
- Host tool
|
||||
/hosts/tool/operate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.HostToolReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Operate tool
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
- type
|
||||
formatEN: '[operate] [type]'
|
||||
formatZH: '[operate] [type] '
|
||||
paramKeys: []
|
||||
/hosts/tool/supervisor/process:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.SupervisorProcessConfig'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get Supervisor process config
|
||||
tags:
|
||||
- Host tool
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SupervisorProcessConfig'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Create Supervisor process
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] process'
|
||||
formatZH: '[operate] 守护进程 '
|
||||
paramKeys: []
|
||||
/hosts/tool/supervisor/process/file:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SupervisorProcessFileReq'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
- Timestamp: []
|
||||
summary: Get Supervisor process config
|
||||
tags:
|
||||
- Host tool
|
||||
x-panel-log:
|
||||
BeforeFunctions: []
|
||||
bodyKeys:
|
||||
- operate
|
||||
formatEN: '[operate] Supervisor Process Config file'
|
||||
formatZH: '[operate] Supervisor 进程文件 '
|
||||
paramKeys: []
|
||||
/hosts/tree:
|
||||
post:
|
||||
consumes:
|
||||
@@ -16766,15 +16768,7 @@ schemes:
|
||||
- https
|
||||
securityDefinitions:
|
||||
ApiKeyAuth:
|
||||
description: |-
|
||||
Custom Token Format, Format: md5('1panel' + API-Key + UnixTimestamp).
|
||||
```
|
||||
eg:
|
||||
curl -X GET "http://localhost:4004/api/v1/dashboard/current" \
|
||||
-H "1Panel-Token: <1panel_token>" \
|
||||
-H "1Panel-Timestamp: <current_unix_timestamp>"
|
||||
```
|
||||
- `1Panel-Token` is the key for the panel API Key.
|
||||
description: '- `1Panel-Token` is the key for the panel API Key.'
|
||||
in: header
|
||||
name: 1Panel-Token
|
||||
type: apiKey
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
mcp-server:
|
||||
image: supercorp/supergateway:latest
|
||||
container_name: ${CONTAINER_NAME}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${PANEL_APP_PORT_HTTP}"
|
||||
command: [
|
||||
"--stdio", "${COMMAND}",
|
||||
"--port", "${PANEL_APP_PORT_HTTP}",
|
||||
"--baseUrl", "${BASE_URL}",
|
||||
"--ssePath", "${SSE_PATH}",
|
||||
"--messagePath", "${SSE_PATH}/messages"
|
||||
]
|
||||
networks:
|
||||
- 1panel-network
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
@@ -0,0 +1,8 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed compose.yml
|
||||
var DefaultMcpCompose []byte
|
||||
@@ -37,3 +37,6 @@ var DomainNotFoundHTML []byte
|
||||
|
||||
//go:embed stop.html
|
||||
var StopHTML []byte
|
||||
|
||||
//go:embed sse.conf
|
||||
var SSE []byte
|
||||
|
||||
@@ -7,6 +7,7 @@ location ^~ /test {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $http_connection;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_http_version 1.1;
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
add_header Cache-Control no-cache;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
location ^~ /github {
|
||||
proxy_pass http://127.0.0.1:8001/github;
|
||||
proxy_buffering off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection '';
|
||||
chunked_transfer_encoding off;
|
||||
}
|
||||
@@ -28,3 +28,4 @@ stats.html
|
||||
*.sln
|
||||
*.sw?
|
||||
.vscode
|
||||
*.code-workspace
|
||||
@@ -109,4 +109,75 @@ export namespace AI {
|
||||
websiteID?: number;
|
||||
connUrl: string;
|
||||
}
|
||||
|
||||
export interface Environment {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Volume {
|
||||
source: string;
|
||||
target: string;
|
||||
}
|
||||
|
||||
export interface McpServer {
|
||||
id: number;
|
||||
name: string;
|
||||
status: string;
|
||||
baseUrl: string;
|
||||
ssePath: string;
|
||||
command: string;
|
||||
port: number;
|
||||
message: string;
|
||||
createdAt?: string;
|
||||
containerName: string;
|
||||
environments: Environment[];
|
||||
volumes: Volume[];
|
||||
dir?: string;
|
||||
hostIP: string;
|
||||
protocol: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface McpServerSearch extends ReqPage {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface McpServerDelete {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface McpServerOperate {
|
||||
id: number;
|
||||
operate: string;
|
||||
}
|
||||
|
||||
export interface McpBindDomain {
|
||||
domain: string;
|
||||
sslID: number;
|
||||
ipList: string;
|
||||
}
|
||||
|
||||
export interface McpDomainRes {
|
||||
domain: string;
|
||||
sslID: number;
|
||||
acmeAccountID: number;
|
||||
allowIPs: string[];
|
||||
websiteID?: number;
|
||||
connUrl: string;
|
||||
}
|
||||
|
||||
export interface McpBindDomainUpdate {
|
||||
websiteID: number;
|
||||
sslID: number;
|
||||
ipList: string;
|
||||
}
|
||||
|
||||
export interface ImportMcpServer {
|
||||
name: string;
|
||||
command: string;
|
||||
ssePath: string;
|
||||
containerName: string;
|
||||
environments: Environment[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,9 +212,6 @@ export namespace Container {
|
||||
password: string;
|
||||
auth: boolean;
|
||||
}
|
||||
export interface RepoDelete {
|
||||
ids: Array<number>;
|
||||
}
|
||||
export interface RepoInfo {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
|
||||
@@ -81,6 +81,7 @@ export namespace Cronjob {
|
||||
export interface CronjobDelete {
|
||||
ids: Array<number>;
|
||||
cleanData: boolean;
|
||||
cleanRemoteData: boolean;
|
||||
}
|
||||
export interface UpdateStatus {
|
||||
id: number;
|
||||
|
||||
@@ -62,6 +62,7 @@ export namespace Setting {
|
||||
apiKey: string;
|
||||
ipWhiteList: string;
|
||||
apiKeyValidityTime: number;
|
||||
licenseVerify: string;
|
||||
}
|
||||
export interface SettingUpdate {
|
||||
key: string;
|
||||
|
||||
@@ -39,3 +39,35 @@ export const getBindDomain = (req: AI.BindDomainReq) => {
|
||||
export const updateBindDomain = (req: AI.BindDomain) => {
|
||||
return http.post(`/ai/domain/update`, req);
|
||||
};
|
||||
|
||||
export const pageMcpServer = (req: AI.McpServerSearch) => {
|
||||
return http.post<ResPage<AI.McpServer>>(`/ai/mcp/search`, req);
|
||||
};
|
||||
|
||||
export const createMcpServer = (req: AI.McpServer) => {
|
||||
return http.post(`/ai/mcp/server`, req);
|
||||
};
|
||||
|
||||
export const updateMcpServer = (req: AI.McpServer) => {
|
||||
return http.post(`/ai/mcp/server/update`, req);
|
||||
};
|
||||
|
||||
export const deleteMcpServer = (req: AI.McpServerDelete) => {
|
||||
return http.post(`/ai/mcp/server/del`, req);
|
||||
};
|
||||
|
||||
export const operateMcpServer = (req: AI.McpServerOperate) => {
|
||||
return http.post(`/ai/mcp/server/op`, req);
|
||||
};
|
||||
|
||||
export const bindMcpDomain = (req: AI.McpBindDomain) => {
|
||||
return http.post(`/ai/mcp/domain/bind`, req);
|
||||
};
|
||||
|
||||
export const getMcpDomain = () => {
|
||||
return http.get<AI.McpDomainRes>(`/ai/mcp/domain/get`);
|
||||
};
|
||||
|
||||
export const updateMcpDomain = (req: AI.McpBindDomainUpdate) => {
|
||||
return http.post(`/ai/mcp/domain/update`, req);
|
||||
};
|
||||
|
||||
@@ -135,8 +135,8 @@ export const createImageRepo = (params: Container.RepoCreate) => {
|
||||
export const updateImageRepo = (params: Container.RepoUpdate) => {
|
||||
return http.post(`/containers/repo/update`, params, TimeoutEnum.T_40S);
|
||||
};
|
||||
export const deleteImageRepo = (params: Container.RepoDelete) => {
|
||||
return http.post(`/containers/repo/del`, params, TimeoutEnum.T_40S);
|
||||
export const deleteImageRepo = (id: Number) => {
|
||||
return http.post(`/containers/repo/del`, { id: id }, TimeoutEnum.T_40S);
|
||||
};
|
||||
|
||||
// composeTemplate
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user