diff --git a/backend/app/api/v1/website.go b/backend/app/api/v1/website.go index 1b91656f6..21b4795a8 100644 --- a/backend/app/api/v1/website.go +++ b/backend/app/api/v1/website.go @@ -912,3 +912,25 @@ func (b *BaseApi) UpdateRedirectConfigFile(c *gin.Context) { } helper.SuccessWithOutData(c) } + +// @Tags Website +// @Summary Get website dir +// @Description 获取网站目录配置 +// @Accept json +// @Param request body request.WebsiteCommonReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /websites/dir [post] +func (b *BaseApi) GetDirConfig(c *gin.Context) { + var req request.WebsiteCommonReq + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + res, err := websiteService.LoadWebsiteDirConfig(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, res) +} diff --git a/backend/app/dto/request/website.go b/backend/app/dto/request/website.go index 379e185a0..a8bfe46ea 100644 --- a/backend/app/dto/request/website.go +++ b/backend/app/dto/request/website.go @@ -213,3 +213,7 @@ type WebsiteWafFileUpdate struct { Content string `json:"content" validate:"required"` Type string `json:"type" validate:"required,oneof=cc ip_white ip_block url_white url_block cookie_block args_check post_check ua_check file_ext_block"` } + +type WebsiteCommonReq struct { + ID uint `json:"id" validate:"required"` +} diff --git a/backend/app/dto/response/website.go b/backend/app/dto/response/website.go index 66ed28de8..3cfad9acb 100644 --- a/backend/app/dto/response/website.go +++ b/backend/app/dto/response/website.go @@ -53,3 +53,10 @@ type PHPConfig struct { type NginxRewriteRes struct { Content string `json:"content"` } + +type WebsiteDirConfig struct { + Dirs []string `json:"dirs"` + User string `json:"user"` + UserGroup string `json:"userGroup"` + Msg string `json:"msg"` +} diff --git a/backend/app/service/website.go b/backend/app/service/website.go index 638ef078a..b70af3e00 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -9,13 +9,16 @@ import ( "encoding/pem" "errors" "fmt" + "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/backend/utils/common" + "github.com/spf13/afero" "os" "path" "reflect" "regexp" "strconv" "strings" + "syscall" "time" "github.com/1Panel-dev/1Panel/backend/utils/compose" @@ -80,6 +83,7 @@ type IWebsiteService interface { GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) UpdateRewriteConfig(req request.NginxRewriteUpdate) error + LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) UpdateSiteDir(req request.WebsiteUpdateDir) error UpdateSitePermission(req request.WebsiteUpdateDirPermission) error OperateProxy(req request.WebsiteProxyConfig) (err error) @@ -1389,7 +1393,7 @@ func (w WebsiteService) UpdateSiteDir(req request.WebsiteUpdateDir) error { runDir := req.SiteDir siteDir := path.Join("/www/sites", website.Alias, "index") if req.SiteDir != "/" { - siteDir = fmt.Sprintf("%s/%s", siteDir, req.SiteDir) + siteDir = fmt.Sprintf("%s%s", siteDir, req.SiteDir) } if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "root", Params: []string{siteDir}}}, &website); err != nil { return err @@ -1408,9 +1412,6 @@ func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermiss return err } absoluteIndexPath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index") - if website.SiteDir != "/" { - absoluteIndexPath = path.Join(absoluteIndexPath, website.SiteDir) - } chownCmd := fmt.Sprintf("chown -R %s:%s %s", req.User, req.Group, absoluteIndexPath) if cmd.HasNoPasswordSudo() { chownCmd = fmt.Sprintf("sudo %s", chownCmd) @@ -2318,3 +2319,52 @@ func (w WebsiteService) UpdateWafFile(req request.WebsiteWafFileUpdate) (err err rulePath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "waf", "rules", fmt.Sprintf("%s.json", req.Type)) return files.NewFileOp().WriteFile(rulePath, strings.NewReader(req.Content), 0755) } + +func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) { + website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) + if err != nil { + return nil, err + } + res := &response.WebsiteDirConfig{} + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + absoluteIndexPath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index") + var appFs = afero.NewOsFs() + info, err := appFs.Stat(absoluteIndexPath) + if err != nil { + return nil, err + } + res.User = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Uid), 10) + res.UserGroup = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Gid), 10) + + indexFiles, err := os.ReadDir(absoluteIndexPath) + if err != nil { + return nil, err + } + res.Dirs = []string{"/"} + for _, file := range indexFiles { + if !file.IsDir() { + continue + } + res.Dirs = append(res.Dirs, fmt.Sprintf("/%s", file.Name())) + fileInfo, _ := file.Info() + if fileInfo.Sys().(*syscall.Stat_t).Uid != 1000 || fileInfo.Sys().(*syscall.Stat_t).Gid != 1000 { + res.Msg = i18n.GetMsgByKey("ErrPathPermission") + } + childFiles, _ := os.ReadDir(absoluteIndexPath + "/" + file.Name()) + for _, childFile := range childFiles { + if !childFile.IsDir() { + continue + } + childInfo, _ := childFile.Info() + if childInfo.Sys().(*syscall.Stat_t).Uid != 1000 || childInfo.Sys().(*syscall.Stat_t).Gid != 1000 { + res.Msg = i18n.GetMsgByKey("ErrPathPermission") + } + res.Dirs = append(res.Dirs, fmt.Sprintf("/%s/%s", file.Name(), childFile.Name())) + } + } + + return res, nil +} diff --git a/backend/app/service/website_utils.go b/backend/app/service/website_utils.go index 2c1520b8e..9387e25f4 100644 --- a/backend/app/service/website_utils.go +++ b/backend/app/service/website_utils.go @@ -639,3 +639,7 @@ func chownRootDir(path string) error { } return nil } + +func checkWebsiteDirPermission(path string) error { + return nil +} diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 754bac182..0a57fa5d3 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -66,6 +66,7 @@ ErrGroupIsUsed: 'The group is in use and cannot be deleted' ErrBackupMatch: 'the backup file does not match the current partial data of the website: {{ .detail}}"' ErrBackupExist: 'the backup file corresponds to a portion of the original data that does not exist: {{ .detail}}"' ErrPHPResource: 'The local runtime does not support switching!' +ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the index directory, which may cause Access denied errors when accessing the website.' #ssl ErrSSLCannotDelete: "The certificate is being used by the website and cannot be removed" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 74a1a9b85..12ec41da1 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -66,6 +66,7 @@ ErrGroupIsUsed: '分組正在使用中,無法刪除' ErrBackupMatch: '該備份文件與當前網站部分數據不匹配: {{ .detail}}"' ErrBackupExist: '該備份文件對應部分原數據不存在: {{ .detail}}"' ErrPHPResource: '本地運行環境不支持切換!' +ErrPathPermission: 'index 目錄下檢測到非 1000:1000 權限文件夾,可能導致網站訪問 Access denied 錯誤' #ssl ErrSSLCannotDelete: "證書正在被網站使用,無法刪除" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 5146c27d0..7d32a11f8 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -66,6 +66,7 @@ ErrGroupIsUsed: '分组正在使用中,无法删除' ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail}}"' ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail}}"' ErrPHPResource: '本地运行环境不支持切换!' +ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可能导致网站访问 Access denied 错误' #ssl ErrSSLCannotDelete: "证书正在被网站使用,无法删除" diff --git a/backend/router/ro_website.go b/backend/router/ro_website.go index ff6780a52..d6894e51a 100644 --- a/backend/router/ro_website.go +++ b/backend/router/ro_website.go @@ -53,6 +53,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) { groupRouter.POST("/dir/update", baseApi.UpdateSiteDir) groupRouter.POST("/dir/permission", baseApi.UpdateSiteDirPermission) + groupRouter.POST("/dir", baseApi.GetDirConfig) groupRouter.POST("/proxies", baseApi.GetProxyConfig) groupRouter.POST("/proxies/update", baseApi.UpdateProxyConfig) diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 04fd5e1bd..0aaaba1b4 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -1,5 +1,5 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Package docs GENERATED BY SWAG; DO NOT EDIT +// This file was generated by swaggo/swag package docs import "github.com/swaggo/swag" @@ -6446,31 +6446,6 @@ const docTemplate = `{ } } }, - "/host/tool/supervisor/process/load": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "获取 Supervisor 进程状态", - "tags": [ - "Host tool" - ], - "summary": "Load Supervisor process status", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/response.ProcessStatus" - } - } - } - } - } - }, "/hosts": { "post": { "security": [ @@ -9907,6 +9882,39 @@ const docTemplate = `{ } } }, + "/websites/dir": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取网站目录配置", + "consumes": [ + "application/json" + ], + "tags": [ + "Website" + ], + "summary": "Get website dir", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/websites/dir/permission": { "post": { "security": [ @@ -12781,10 +12789,7 @@ const docTemplate = `{ "type": "integer" }, "type": { - "type": "string", - "enum": [ - "mysql" - ] + "type": "string" }, "username": { "type": "string" @@ -12822,6 +12827,9 @@ const docTemplate = `{ "port": { "type": "integer" }, + "type": { + "type": "string" + }, "username": { "type": "string" }, @@ -16401,6 +16409,17 @@ const docTemplate = `{ } } }, + "request.WebsiteCommonReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + } + } + }, "request.WebsiteCreate": { "type": "object", "required": [ @@ -17444,26 +17463,6 @@ const docTemplate = `{ } } }, - "response.ProcessStatus": { - "type": "object", - "properties": { - "PID": { - "type": "string" - }, - "msg": { - "type": "string" - }, - "name": { - "type": "string" - }, - "status": { - "type": "string" - }, - "uptime": { - "type": "string" - } - } - }, "response.WebsiteAcmeAccountDTO": { "type": "object", "properties": { diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 20bfdf3d9..7e93919bc 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -6439,31 +6439,6 @@ } } }, - "/host/tool/supervisor/process/load": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "获取 Supervisor 进程状态", - "tags": [ - "Host tool" - ], - "summary": "Load Supervisor process status", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/response.ProcessStatus" - } - } - } - } - } - }, "/hosts": { "post": { "security": [ @@ -9900,6 +9875,39 @@ } } }, + "/websites/dir": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "获取网站目录配置", + "consumes": [ + "application/json" + ], + "tags": [ + "Website" + ], + "summary": "Get website dir", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/websites/dir/permission": { "post": { "security": [ @@ -12774,10 +12782,7 @@ "type": "integer" }, "type": { - "type": "string", - "enum": [ - "mysql" - ] + "type": "string" }, "username": { "type": "string" @@ -12815,6 +12820,9 @@ "port": { "type": "integer" }, + "type": { + "type": "string" + }, "username": { "type": "string" }, @@ -16394,6 +16402,17 @@ } } }, + "request.WebsiteCommonReq": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + } + } + }, "request.WebsiteCreate": { "type": "object", "required": [ @@ -17437,26 +17456,6 @@ } } }, - "response.ProcessStatus": { - "type": "object", - "properties": { - "PID": { - "type": "string" - }, - "msg": { - "type": "string" - }, - "name": { - "type": "string" - }, - "status": { - "type": "string" - }, - "uptime": { - "type": "string" - } - } - }, "response.WebsiteAcmeAccountDTO": { "type": "object", "properties": { diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index 2b21b96ae..37a94d90e 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -737,8 +737,6 @@ definitions: port: type: integer type: - enum: - - mysql type: string username: type: string @@ -771,6 +769,8 @@ definitions: type: string port: type: integer + type: + type: string username: type: string version: @@ -3169,6 +3169,13 @@ definitions: required: - email type: object + request.WebsiteCommonReq: + properties: + id: + type: integer + required: + - id + type: object request.WebsiteCreate: properties: IPV6: @@ -3869,19 +3876,6 @@ definitions: uploadMaxSize: type: string type: object - response.ProcessStatus: - properties: - PID: - type: string - msg: - type: string - name: - type: string - status: - type: string - uptime: - type: string - type: object response.WebsiteAcmeAccountDTO: properties: createdAt: @@ -8116,21 +8110,6 @@ paths: formatEN: '[operate] Supervisor Process Config file' formatZH: '[operate] Supervisor 进程文件 ' paramKeys: [] - /host/tool/supervisor/process/load: - post: - description: 获取 Supervisor 进程状态 - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/response.ProcessStatus' - type: array - security: - - ApiKeyAuth: [] - summary: Load Supervisor process status - tags: - - Host tool /hosts: post: consumes: @@ -10305,6 +10284,26 @@ paths: formatEN: Delete website [domain] formatZH: 删除网站 [domain] paramKeys: [] + /websites/dir: + post: + consumes: + - application/json + description: 获取网站目录配置 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.WebsiteCommonReq' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Get website dir + tags: + - Website /websites/dir/permission: post: consumes: diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index a09556e95..536abc302 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -431,4 +431,11 @@ export namespace Website { runtimeID: number; retainConfig: boolean; } + + export interface DirConfig { + dirs: string[]; + user: string; + userGroup: string; + msg: string; + } } diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index a411f7f75..eae941a36 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -234,3 +234,7 @@ export const UpdateRedirectConfigFile = (req: Website.RedirectFileUpdate) => { export const ChangePHPVersion = (req: Website.PHPVersionChange) => { return http.post(`/websites/php/version`, req); }; + +export const GetDirConfig = (req: Website.ProxyReq) => { + return http.post(`/websites/dir`, req); +}; diff --git a/frontend/src/views/website/website/config/basic/site-folder/index.vue b/frontend/src/views/website/website/config/basic/site-folder/index.vue index 23dcf4a22..c7516363b 100644 --- a/frontend/src/views/website/website/config/basic/site-folder/index.vue +++ b/frontend/src/views/website/website/config/basic/site-folder/index.vue @@ -21,7 +21,6 @@ - {{ $t('website.runUserHelper') }} + + +
{{ $t('website.wafFolder') }} @@ -67,8 +71,8 @@