Date: Thu, 11 Dec 2025 16:59:58 +0800
Subject: [PATCH 20/40] feat(frontend): add autocomplete attributes for MFA
input (#11297)
---
frontend/src/views/login/components/login-form.vue | 1 +
1 file changed, 1 insertion(+)
diff --git a/frontend/src/views/login/components/login-form.vue b/frontend/src/views/login/components/login-form.vue
index d491693a60f0..9cc826e1cbd2 100644
--- a/frontend/src/views/login/components/login-form.vue
+++ b/frontend/src/views/login/components/login-form.vue
@@ -16,6 +16,7 @@
size="large"
:placeholder="$t('commons.login.mfaCode')"
v-model.trim="mfaLoginForm.code"
+ autocomplete="one-time-code"
@input="mfaLogin(true)"
>
From a9c9f0272a0f0524e5d5979ada4dff4bff423f62 Mon Sep 17 00:00:00 2001
From: ssongliu <73214554+ssongliu@users.noreply.github.com>
Date: Thu, 11 Dec 2025 17:00:22 +0800
Subject: [PATCH 21/40] fix: Fix cronjob manual stop not working (#11300)
---
agent/utils/cmd/cmdx.go | 41 +++++++++++++++++++++++++++++++----------
1 file changed, 31 insertions(+), 10 deletions(-)
diff --git a/agent/utils/cmd/cmdx.go b/agent/utils/cmd/cmdx.go
index 5923280fa996..8bd5e1081be6 100644
--- a/agent/utils/cmd/cmdx.go
+++ b/agent/utils/cmd/cmdx.go
@@ -108,12 +108,16 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
cmd = exec.CommandContext(newContext, name, arg...)
} else {
if c.context == nil {
+ newContext = context.Background()
cmd = exec.Command(name, arg...)
} else {
newContext = c.context
cmd = exec.CommandContext(c.context, name, arg...)
}
}
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Setpgid: true,
+ }
customWriter := &CustomWriter{taskItem: c.taskItem}
var stdout, stderr bytes.Buffer
@@ -145,22 +149,39 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) {
cmd.Dir = c.workDir
}
- err := cmd.Run()
+ if err := cmd.Start(); err != nil {
+ return "", fmt.Errorf("cmd.Start() failed with '%s'\n", err)
+ }
if c.taskItem != nil {
customWriter.Flush()
}
- if c.timeout != 0 {
- if newContext != nil && errors.Is(newContext.Err(), context.DeadlineExceeded) {
- return "", buserr.New("ErrCmdTimeout")
+
+ done := make(chan error, 1)
+ go func() {
+ done <- cmd.Wait()
+ }()
+ select {
+ case err := <-done:
+ if err != nil {
+ return handleErr(stdout, stderr, c.IgnoreExist1, err)
}
- }
- if err != nil {
- if err.Error() == "signal: killed" {
- return "", buserr.New("ErrShutDown")
+ return stdout.String(), nil
+ case <-newContext.Done():
+ if cmd.Process != nil && cmd.Process.Pid > 0 {
+ syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
+ }
+ var err error
+ switch newContext.Err() {
+ case context.DeadlineExceeded:
+ err = buserr.New("ErrCmdTimeout")
+ case context.Canceled:
+ err = buserr.New("ErrShutDown")
+ default:
+ err = newContext.Err()
}
- return handleErr(stdout, stderr, c.IgnoreExist1, err)
+ <-done
+ return "", err
}
- return stdout.String(), nil
}
func WithContext(ctx context.Context) Option {
From 3aedb7e82d9c08e279fa72f79f682a72e2b471c0 Mon Sep 17 00:00:00 2001
From: KOMATA <20227709+HynoR@users.noreply.github.com>
Date: Thu, 11 Dec 2025 17:00:34 +0800
Subject: [PATCH 22/40] chore: Add trusted dependencies for vue-office packages
to support bun toolkit (#11291)
---
frontend/package.json | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/frontend/package.json b/frontend/package.json
index ee9ab4c6dacb..c15765d032b9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -92,6 +92,10 @@
"vite-svg-loader": "^5.1.0",
"vue-tsc": "^0.29.8"
},
+ "trustedDependencies": [
+ "@vue-office/docx",
+ "@vue-office/excel"
+ ],
"overrides": {
"esbuild": "npm:esbuild-wasm@latest"
},
From 2d1792c6de90e22d66dc6ad16577be14ab33ed11 Mon Sep 17 00:00:00 2001
From: CityFun <31820853+zhengkunwang223@users.noreply.github.com>
Date: Thu, 11 Dec 2025 17:03:03 +0800
Subject: [PATCH 23/40] fix: Fix the issue where the website app deployment
fails (#11301)
---
frontend/src/views/website/website/create/index.vue | 3 +++
1 file changed, 3 insertions(+)
diff --git a/frontend/src/views/website/website/create/index.vue b/frontend/src/views/website/website/create/index.vue
index 4a7aca33efc3..8149fe120f73 100644
--- a/frontend/src/views/website/website/create/index.vue
+++ b/frontend/src/views/website/website/create/index.vue
@@ -482,6 +482,9 @@ const initData = () => ({
memoryUnit: 'MB',
containerName: '',
allowPort: false,
+
+ format: 'utf8mb4',
+ collation: '',
},
IPV6: false,
enableFtp: false,
From ab13ddec488944d967f34870cc2e0bc7bf84e9d9 Mon Sep 17 00:00:00 2001
From: CityFun <31820853+zhengkunwang223@users.noreply.github.com>
Date: Thu, 11 Dec 2025 18:33:03 +0800
Subject: [PATCH 24/40] feat: The website supports TCP/UDP proxying. (#11302)
Refs https://github.com/1Panel-dev/1Panel/issues/5135
Refs https://github.com/1Panel-dev/1Panel/issues/6976
---
agent/app/api/v2/website.go | 20 +
agent/app/dto/request/website.go | 15 +
agent/app/dto/response/website.go | 4 +
agent/app/model/website.go | 2 +
agent/app/service/nginx_utils.go | 2 +-
agent/app/service/website.go | 1517 +++--------------
agent/app/service/website_auth_basic.go | 312 ++++
agent/app/service/website_domain.go | 185 ++
agent/app/service/website_lb.go | 229 +++
agent/app/service/website_proxy.go | 408 +++++
agent/app/service/website_rewrite.go | 124 ++
agent/app/service/website_utils.go | 286 ++--
.../cmd/server/nginx_conf/stream_default.conf | 5 +
agent/constant/website.go | 6 +-
agent/go.mod | 10 +-
agent/go.sum | 10 +
agent/init/migration/migrate.go | 1 +
agent/init/migration/migrations/init.go | 7 +
agent/router/ro_website.go | 1 +
frontend/src/api/interface/website.ts | 15 +
frontend/src/api/modules/website.ts | 4 +
frontend/src/global/mimetype.ts | 46 +-
frontend/src/lang/modules/en.ts | 3 +
frontend/src/lang/modules/es-es.ts | 3 +
frontend/src/lang/modules/ja.ts | 3 +
frontend/src/lang/modules/ko.ts | 3 +
frontend/src/lang/modules/ms.ts | 3 +
frontend/src/lang/modules/pt-br.ts | 3 +
frontend/src/lang/modules/ru.ts | 3 +
frontend/src/lang/modules/tr.ts | 3 +
frontend/src/lang/modules/zh-Hant.ts | 3 +
frontend/src/lang/modules/zh.ts | 3 +
.../website/components/website-ssl/index.vue | 38 +
.../website/config/basic/https/index.vue | 33 +-
.../website/website/config/basic/index.vue | 34 +-
.../config/basic/load-balance/form/index.vue | 319 ++++
.../config/basic/load-balance/index.vue | 2 +-
.../basic/load-balance/operate/index.vue | 360 +---
.../website/config/basic/stream/index.vue | 73 +
.../views/website/website/config/index.vue | 26 +-
.../views/website/website/create/index.vue | 171 +-
.../views/website/website/domain/index.vue | 10 +-
frontend/src/views/website/website/index.vue | 19 +-
43 files changed, 2498 insertions(+), 1826 deletions(-)
create mode 100644 agent/app/service/website_auth_basic.go
create mode 100644 agent/app/service/website_domain.go
create mode 100644 agent/app/service/website_lb.go
create mode 100644 agent/app/service/website_proxy.go
create mode 100644 agent/app/service/website_rewrite.go
create mode 100644 agent/cmd/server/nginx_conf/stream_default.conf
create mode 100644 frontend/src/views/website/website/components/website-ssl/index.vue
create mode 100644 frontend/src/views/website/website/config/basic/load-balance/form/index.vue
create mode 100644 frontend/src/views/website/website/config/basic/stream/index.vue
diff --git a/agent/app/api/v2/website.go b/agent/app/api/v2/website.go
index f1a517199cf4..059aaf97f01b 100644
--- a/agent/app/api/v2/website.go
+++ b/agent/app/api/v2/website.go
@@ -1223,3 +1223,23 @@ func (b *BaseApi) UpdateCORSConfig(c *gin.Context) {
}
helper.Success(c)
}
+
+// @Tags Website
+// @Summary Update Stream Config
+// @Accept json
+// @Param request body request.StreamUpdate true "request"
+// @Success 200
+// @Security ApiKeyAuth
+// @Security Timestamp
+// @Router /websites/stream/update [post]
+func (b *BaseApi) UpdateStreamConfig(c *gin.Context) {
+ var req request.StreamUpdate
+ if err := helper.CheckBindAndValidate(&req, c); err != nil {
+ return
+ }
+ if err := websiteService.UpdateStream(req); err != nil {
+ helper.InternalServer(c, err)
+ return
+ }
+ helper.Success(c)
+}
diff --git a/agent/app/dto/request/website.go b/agent/app/dto/request/website.go
index 88849023752b..bdc8b5b5fcfc 100644
--- a/agent/app/dto/request/website.go
+++ b/agent/app/dto/request/website.go
@@ -38,6 +38,21 @@ type WebsiteCreate struct {
FtpConfig
DataBaseConfig
SSLConfig
+ StreamConfig
+}
+
+type StreamConfig struct {
+ StreamPorts string `json:"streamPorts" validate:"required"`
+ Name string `json:"name"`
+ Algorithm string `json:"algorithm"`
+
+ Servers []dto.NginxUpstreamServer `json:"servers"`
+}
+
+type StreamUpdate struct {
+ WebsiteID uint `json:"websiteID" validate:"required"`
+
+ StreamConfig
}
type WebsiteOptionReq struct {
diff --git a/agent/app/dto/response/website.go b/agent/app/dto/response/website.go
index 0f6dcb6bacb8..15aee9650d56 100644
--- a/agent/app/dto/response/website.go
+++ b/agent/app/dto/response/website.go
@@ -3,6 +3,7 @@ package response
import (
"time"
+ "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
)
@@ -16,6 +17,9 @@ type WebsiteDTO struct {
RuntimeType string `json:"runtimeType"`
SiteDir string `json:"siteDir"`
OpenBaseDir bool `json:"openBaseDir"`
+ Algorithm string `json:"algorithm"`
+
+ Servers []dto.NginxUpstreamServer `json:"servers"`
}
type WebsiteRes struct {
diff --git a/agent/app/model/website.go b/agent/app/model/website.go
index 27426c40bbca..10b2b58261f2 100644
--- a/agent/app/model/website.go
+++ b/agent/app/model/website.go
@@ -37,6 +37,8 @@ type Website struct {
Favorite bool `json:"favorite"`
+ StreamPorts string `json:"streamPorts"`
+
Domains []WebsiteDomain `json:"domains" gorm:"-:migration"`
WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"`
}
diff --git a/agent/app/service/nginx_utils.go b/agent/app/service/nginx_utils.go
index 3a77f5060f28..8e665efb9bd1 100644
--- a/agent/app/service/nginx_utils.go
+++ b/agent/app/service/nginx_utils.go
@@ -51,7 +51,7 @@ func getNginxFull(website *model.Website) (dto.NginxFull, error) {
if website != nil {
nginxFull.Website = *website
var siteNginxConfig dto.NginxConfig
- siteConfigPath := GetSitePath(*website, SiteConf)
+ siteConfigPath := GetWebsiteConfigPath(*website)
siteNginxConfig.FilePath = siteConfigPath
siteNginxContent, err := os.ReadFile(siteConfigPath)
if err != nil {
diff --git a/agent/app/service/website.go b/agent/app/service/website.go
index d86d81a07c0d..cd570f09408e 100644
--- a/agent/app/service/website.go
+++ b/agent/app/service/website.go
@@ -1,12 +1,10 @@
package service
import (
- "bufio"
"bytes"
"context"
"crypto/x509"
"encoding/base64"
- "encoding/json"
"encoding/pem"
"errors"
"fmt"
@@ -46,7 +44,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
"github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
"github.com/1Panel-dev/1Panel/agent/utils/re"
- "golang.org/x/crypto/bcrypt"
)
type WebsiteService struct {
@@ -62,42 +59,29 @@ type IWebsiteService interface {
DeleteWebsite(req request.WebsiteDelete) error
GetWebsite(id uint) (response.WebsiteDTO, error)
BatchOpWebsite(req request.BatchWebsiteOp) error
- BatchSetGroup(req request.BatchWebsiteGroup) error
- CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error)
- GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error)
- DeleteWebsiteDomain(domainId uint) error
- UpdateWebsiteDomain(req request.WebsiteDomainUpdate) error
+ BatchSetGroup(req request.BatchWebsiteGroup) error
+ ChangePHPVersion(req request.WebsitePHPVersionReq) error
+ OperateCrossSiteAccess(req request.CrossSiteAccessOp) error
+ ExecComposer(req request.ExecComposerReq) error
+ ChangeGroup(group, newGroup uint) error
+ ChangeDefaultServer(id uint) error
+ PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error)
+ OpWebsiteLog(req request.WebsiteLogReq) (*response.WebsiteLog, error)
+ UpdateStream(req request.StreamUpdate) error
GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error)
UpdateNginxConfigByScope(req request.NginxConfigUpdate) error
GetWebsiteNginxConfig(websiteId uint, configType string) (*response.FileInfo, error)
UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error
+
GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error)
OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error)
- OpWebsiteLog(req request.WebsiteLogReq) (*response.WebsiteLog, error)
- ChangeDefaultServer(id uint) error
- PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error)
-
- ChangePHPVersion(req request.WebsitePHPVersionReq) error
-
- GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error)
- UpdateRewriteConfig(req request.NginxRewriteUpdate) error
- OperateCustomRewrite(req request.CustomRewriteOperate) error
- ListCustomRewrite() ([]string, error)
LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error)
UpdateSiteDir(req request.WebsiteUpdateDir) error
UpdateSitePermission(req request.WebsiteUpdateDirPermission) error
- OperateProxy(req request.WebsiteProxyConfig) (err error)
- GetProxies(id uint) (res []request.WebsiteProxyConfig, err error)
- UpdateProxyFile(req request.NginxProxyUpdate) (err error)
- UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error)
- GetProxyCache(id uint) (res response.NginxProxyCache, err error)
- ClearProxyCache(req request.NginxCommonReq) error
- DeleteProxy(req request.WebsiteProxyDel) (err error)
-
UpdateCors(req request.CorsConfigReq) error
GetCors(websiteID uint) (*request.CorsConfig, error)
@@ -108,32 +92,44 @@ type IWebsiteService interface {
GetRedirect(id uint) (res []response.NginxRedirectConfig, err error)
UpdateRedirectFile(req request.NginxRedirectUpdate) (err error)
- GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error)
- UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
- GetPathAuthBasics(req request.NginxAuthReq) (res []response.NginxPathAuthRes, err error)
- UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error
-
UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error
GetDefaultHtml(resourceType string) (*response.WebsiteHtmlRes, error)
+ SetRealIPConfig(req request.WebsiteRealIP) error
+ GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error)
+
+ GetWebsiteResource(websiteID uint) ([]response.Resource, error)
+ ListDatabases() ([]response.Database, error)
+ ChangeDatabase(req request.ChangeDatabase) error
+
GetLoadBalances(id uint) ([]dto.NginxUpstream, error)
CreateLoadBalance(req request.WebsiteLBCreate) error
DeleteLoadBalance(req request.WebsiteLBDelete) error
UpdateLoadBalance(req request.WebsiteLBUpdate) error
UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error
- SetRealIPConfig(req request.WebsiteRealIP) error
- GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error)
-
- ChangeGroup(group, newGroup uint) error
+ OperateProxy(req request.WebsiteProxyConfig) (err error)
+ GetProxies(id uint) (res []request.WebsiteProxyConfig, err error)
+ UpdateProxyFile(req request.NginxProxyUpdate) (err error)
+ UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error)
+ GetProxyCache(id uint) (res response.NginxProxyCache, err error)
+ ClearProxyCache(req request.NginxCommonReq) error
+ DeleteProxy(req request.WebsiteProxyDel) (err error)
- GetWebsiteResource(websiteID uint) ([]response.Resource, error)
- ListDatabases() ([]response.Database, error)
- ChangeDatabase(req request.ChangeDatabase) error
+ CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error)
+ GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error)
+ DeleteWebsiteDomain(domainId uint) error
+ UpdateWebsiteDomain(req request.WebsiteDomainUpdate) error
- OperateCrossSiteAccess(req request.CrossSiteAccessOp) error
+ GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error)
+ UpdateRewriteConfig(req request.NginxRewriteUpdate) error
+ OperateCustomRewrite(req request.CustomRewriteOperate) error
+ ListCustomRewrite() ([]string, error)
- ExecComposer(req request.ExecComposerReq) error
+ GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error)
+ UpdateAuthBasic(req request.NginxAuthUpdate) (err error)
+ GetPathAuthBasics(req request.NginxAuthReq) (res []response.NginxPathAuthRes, err error)
+ UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error
}
func NewIWebsiteService() IWebsiteService {
@@ -263,28 +259,15 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
return err
}
defaultHttpPort := nginxInstall.HttpPort
- var (
- domains []model.WebsiteDomain
- )
- domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, nginxInstall.HttpsPort, 0)
- if err != nil {
- return err
- }
- primaryDomain := domains[0].Domain
- if domains[0].Port != defaultHttpPort {
- primaryDomain = fmt.Sprintf("%s:%v", domains[0].Domain, domains[0].Port)
- }
-
defaultDate, _ := time.Parse(constant.DateLayout, constant.WebsiteDefaultExpireDate)
+
website := &model.Website{
- PrimaryDomain: primaryDomain,
Type: create.Type,
Alias: alias,
Remark: create.Remark,
Status: constant.WebRunning,
ExpireDate: defaultDate,
WebsiteGroupID: create.WebsiteGroupID,
- Protocol: constant.ProtocolHTTP,
Proxy: create.Proxy,
SiteDir: "/",
AccessLog: true,
@@ -293,9 +276,34 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
}
var (
- appInstall *model.AppInstall
- runtime *model.Runtime
+ domains []model.WebsiteDomain
+ appInstall *model.AppInstall
+ runtime *model.Runtime
+ primaryDomain string
)
+ if website.Type == constant.Stream {
+ website.PrimaryDomain = create.Name
+ website.Protocol = constant.ProtocolStream
+ website.StreamPorts = create.StreamConfig.StreamPorts
+ ports := strings.Split(create.StreamConfig.StreamPorts, ",")
+ for _, port := range ports {
+ portNum, _ := strconv.Atoi(port)
+ if err = checkWebsitePort(nginxInstall.HttpsPort, portNum, website.Type); err != nil {
+ return err
+ }
+ }
+ } else {
+ domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, nginxInstall.HttpsPort, 0)
+ if err != nil {
+ return err
+ }
+ primaryDomain = domains[0].Domain
+ if domains[0].Port != defaultHttpPort {
+ primaryDomain = fmt.Sprintf("%s:%v", domains[0].Domain, domains[0].Port)
+ }
+ website.PrimaryDomain = primaryDomain
+ website.Protocol = constant.ProtocolHTTP
+ }
createTask, err := task.NewTaskWithOps(primaryDomain, task.TaskCreate, task.TaskScopeWebsite, create.TaskID, 0)
if err != nil {
@@ -429,33 +437,39 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
}
configNginx := func(t *task.Task) error {
- if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil {
- return err
- }
- if err = createWafConfig(website, domains); err != nil {
+ if err = configDefaultNginx(website, domains, appInstall, runtime, create.StreamConfig); err != nil {
return err
}
- if create.Type == constant.Runtime {
- runtime, err = runtimeRepo.GetFirst(context.Background(), repo.WithByID(create.RuntimeID))
- if err != nil {
+ if website.Type != constant.Stream {
+ if err = createWafConfig(website, domains); err != nil {
return err
}
- if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceAppstore {
- createOpenBasedirConfig(website)
+ if create.Type == constant.Runtime {
+ runtime, err = runtimeRepo.GetFirst(context.Background(), repo.WithByID(create.RuntimeID))
+ if err != nil {
+ return err
+ }
+ if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceAppstore {
+ createOpenBasedirConfig(website)
+ }
}
}
+
tx, ctx := helper.GetTxAndContext()
defer tx.Rollback()
if err = websiteRepo.Create(ctx, website); err != nil {
return err
}
t.Task.ResourceID = website.ID
- for i := range domains {
- domains[i].WebsiteID = website.ID
- }
- if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil {
- return err
+ if len(domains) > 0 {
+ for i := range domains {
+ domains[i].WebsiteID = website.ID
+ }
+ if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil {
+ return err
+ }
}
+
tx.Commit()
return nil
}
@@ -637,7 +651,9 @@ func (w WebsiteService) GetWebsite(id uint) (response.WebsiteDTO, error) {
res.AccessLogPath = GetSitePath(website, SiteAccessLog)
res.SitePath = GetSitePath(website, SiteDir)
res.SiteDir = website.SiteDir
- if website.Type == constant.Runtime {
+ fileOp := files.NewFileOp()
+ switch website.Type {
+ case constant.Runtime:
runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID))
if err != nil {
return res, err
@@ -645,7 +661,28 @@ func (w WebsiteService) GetWebsite(id uint) (response.WebsiteDTO, error) {
res.RuntimeType = runtime.Type
res.RuntimeName = runtime.Name
if runtime.Type == constant.RuntimePHP {
- res.OpenBaseDir = files.NewFileOp().Stat(path.Join(GetSitePath(website, SiteIndexDir), ".user.ini"))
+ res.OpenBaseDir = fileOp.Stat(path.Join(GetSitePath(website, SiteIndexDir), ".user.ini"))
+ }
+ case constant.Stream:
+ nginxParser, err := parser.NewParser(GetSitePath(website, StreamConf))
+ if err != nil {
+ return res, err
+ }
+ config, err := nginxParser.Parse()
+ if err != nil {
+ return res, err
+ }
+ upstreams := config.FindUpstreams()
+ for _, up := range upstreams {
+ directives := up.GetDirectives()
+ for _, d := range directives {
+ dName := d.GetName()
+ if _, ok := dto.LBAlgorithms[dName]; ok {
+ res.Algorithm = dName
+ }
+ }
+ res.Servers = getNginxUpstreamServers(up.UpstreamServers)
+ break
}
}
return res, nil
@@ -700,8 +737,10 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
return err
}
- if err = delWafConfig(website, req.ForceDelete); err != nil {
- return err
+ if website.Type != constant.Stream {
+ if err = delWafConfig(website, req.ForceDelete); err != nil {
+ return err
+ }
}
if checkIsLinkApp(website) && req.DeleteApp {
@@ -773,177 +812,6 @@ func (w WebsiteService) UpdateWebsiteDomain(req request.WebsiteDomainUpdate) err
return websiteDomainRepo.Save(context.TODO(), &domain)
}
-func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) {
- var (
- domainModels []model.WebsiteDomain
- addPorts []int
- )
- httpPort, httpsPort, err := getAppInstallPort(constant.AppOpenresty)
- if err != nil {
- return nil, err
- }
- website, err := websiteRepo.GetFirst(repo.WithByID(create.WebsiteID))
- if err != nil {
- return nil, err
- }
-
- domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, httpsPort, create.WebsiteID)
- if err != nil {
- return nil, err
- }
- go func() {
- _ = OperateFirewallPort(nil, addPorts)
- }()
-
- nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
- if err != nil {
- return nil, err
- }
- wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
- fileOp := files.NewFileOp()
- if fileOp.Stat(wafDataPath) {
- websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
- content, err := fileOp.GetContent(websitesConfigPath)
- if err != nil {
- return nil, err
- }
- var websitesArray []request.WafWebsite
- if content != nil {
- if err := json.Unmarshal(content, &websitesArray); err != nil {
- return nil, err
- }
- }
- for index, wafWebsite := range websitesArray {
- if wafWebsite.Key == website.Alias {
- wafSite := request.WafWebsite{
- Key: website.Alias,
- Domains: wafWebsite.Domains,
- Host: wafWebsite.Host,
- }
- for _, domain := range domainModels {
- wafSite.Domains = append(wafSite.Domains, domain.Domain)
- wafSite.Host = append(wafSite.Host, domain.Domain+":"+strconv.Itoa(domain.Port))
- }
- if len(wafSite.Host) == 0 {
- wafSite.Host = []string{}
- }
- websitesArray[index] = wafSite
- break
- }
- }
- websitesContent, err := json.Marshal(websitesArray)
- if err != nil {
- return nil, err
- }
- if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
- return nil, err
- }
- }
-
- if err = addListenAndServerName(website, domainModels); err != nil {
- return nil, err
- }
-
- return domainModels, websiteDomainRepo.BatchCreate(context.TODO(), domainModels)
-}
-
-func (w WebsiteService) GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) {
- return websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteId))
-}
-
-func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
- webSiteDomain, err := websiteDomainRepo.GetFirst(repo.WithByID(domainId))
- if err != nil {
- return err
- }
-
- if websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID)); len(websiteDomains) == 1 {
- return fmt.Errorf("can not delete last domain")
- }
- website, err := websiteRepo.GetFirst(repo.WithByID(webSiteDomain.WebsiteID))
- if err != nil {
- return err
- }
- var ports []int
- if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithPort(webSiteDomain.Port)); len(oldDomains) == 1 {
- ports = append(ports, webSiteDomain.Port)
- }
-
- var domains []string
- if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithDomain(webSiteDomain.Domain)); len(oldDomains) == 1 {
- domains = append(domains, webSiteDomain.Domain)
- }
-
- if len(ports) > 0 || len(domains) > 0 {
- stringBinds := make([]string, len(ports))
- for i := 0; i < len(ports); i++ {
- stringBinds[i] = strconv.Itoa(ports[i])
- }
- if err := deleteListenAndServerName(website, stringBinds, domains); err != nil {
- return err
- }
- }
-
- nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
- if err != nil {
- return err
- }
- wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
- fileOp := files.NewFileOp()
- if fileOp.Stat(wafDataPath) {
- websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
- content, err := fileOp.GetContent(websitesConfigPath)
- if err != nil {
- return err
- }
- var websitesArray []request.WafWebsite
- var newWebsitesArray []request.WafWebsite
- if content != nil {
- if err := json.Unmarshal(content, &websitesArray); err != nil {
- return err
- }
- }
- for _, wafWebsite := range websitesArray {
- if wafWebsite.Key == website.Alias {
- wafSite := wafWebsite
- oldDomains := wafSite.Domains
- var newDomains []string
- for _, domain := range oldDomains {
- if domain == webSiteDomain.Domain {
- continue
- }
- newDomains = append(newDomains, domain)
- }
- wafSite.Domains = newDomains
- oldHostArray := wafSite.Host
- var newHostArray []string
- for _, host := range oldHostArray {
- if host == webSiteDomain.Domain+":"+strconv.Itoa(webSiteDomain.Port) {
- continue
- }
- newHostArray = append(newHostArray, host)
- }
- wafSite.Host = newHostArray
- if len(wafSite.Host) == 0 {
- wafSite.Host = []string{}
- }
- newWebsitesArray = append(newWebsitesArray, wafSite)
- } else {
- newWebsitesArray = append(newWebsitesArray, wafWebsite)
- }
- }
- websitesContent, err := json.Marshal(newWebsitesArray)
- if err != nil {
- return err
- }
- if err = fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
- return err
- }
- }
-
- return websiteDomainRepo.DeleteBy(context.TODO(), repo.WithByID(domainId))
-}
-
func (w WebsiteService) GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error) {
keys, ok := dto.ScopeKeyMap[req.Scope]
if !ok || len(keys) == 0 {
@@ -1000,7 +868,7 @@ func (w WebsiteService) GetWebsiteNginxConfig(websiteID uint, configType string)
configPath := ""
switch configType {
case constant.AppOpenresty:
- configPath = GetSitePath(website, SiteConf)
+ configPath = GetWebsiteConfigPath(website)
}
info, err := files.NewFileInfo(files.FileOption{
Path: configPath,
@@ -1524,876 +1392,95 @@ func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error
return websiteRepo.Save(context.Background(), &website)
}
-func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+func (w WebsiteService) UpdateSiteDir(req request.WebsiteUpdateDir) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
return err
}
- includePath := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias)
- absolutePath := GetSitePath(website, SiteReWritePath)
- fileOp := files.NewFileOp()
- var oldRewriteContent []byte
- if !fileOp.Stat(path.Dir(absolutePath)) {
- if err := fileOp.CreateDir(path.Dir(absolutePath), constant.DirPerm); err != nil {
- return err
- }
- }
- if !fileOp.Stat(absolutePath) {
- if err := fileOp.CreateFile(absolutePath); err != nil {
- return err
- }
- } else {
- oldRewriteContent, err = fileOp.GetContent(absolutePath)
- if err != nil {
- return err
- }
+ runDir := req.SiteDir
+ siteDir := path.Join("/www/sites", website.Alias, "index")
+ if req.SiteDir != "/" {
+ siteDir = fmt.Sprintf("%s%s", siteDir, req.SiteDir)
}
- if err := fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
+ if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "root", Params: []string{siteDir}}}, &website); err != nil {
return err
}
+ website.SiteDir = runDir
+ return websiteRepo.Save(context.Background(), &website)
+}
- if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{includePath}}}, &website); err != nil {
- _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm)
+func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermission) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
+ if err != nil {
+ return err
+ }
+ absoluteIndexPath := GetSitePath(website, SiteIndexDir)
+ cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
+ if err := cmdMgr.RunBashCf("%s chown -R %s:%s %s", cmd.SudoHandleCmd(), req.User, req.Group, absoluteIndexPath); err != nil {
return err
}
- website.Rewrite = req.Name
+ website.User = req.User
+ website.Group = req.Group
return websiteRepo.Save(context.Background(), &website)
}
-func (w WebsiteService) GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) {
+func (w WebsiteService) UpdateCors(req request.CorsConfigReq) error {
website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
if err != nil {
- return nil, err
- }
- var contentByte []byte
- if req.Name == "current" {
- rewriteConfPath := GetSitePath(website, SiteReWritePath)
- fileOp := files.NewFileOp()
- if fileOp.Stat(rewriteConfPath) {
- contentByte, err = fileOp.GetContent(rewriteConfPath)
- if err != nil {
- return nil, err
- }
- }
- } else {
- rewriteFile := fmt.Sprintf("rewrite/%s.conf", strings.ToLower(req.Name))
- contentByte, _ = nginx_conf.Rewrites.ReadFile(rewriteFile)
- if contentByte == nil {
- customRewriteDir := GetOpenrestyDir(DefaultRewriteDir)
- customRewriteFile := path.Join(customRewriteDir, fmt.Sprintf("%s.conf", strings.ToLower(req.Name)))
- contentByte, err = files.NewFileOp().GetContent(customRewriteFile)
- }
- }
- return &response.NginxRewriteRes{
- Content: string(contentByte),
- }, err
-}
-
-func (w WebsiteService) OperateCustomRewrite(req request.CustomRewriteOperate) error {
- rewriteDir := GetOpenrestyDir(DefaultRewriteDir)
- fileOp := files.NewFileOp()
- if !fileOp.Stat(rewriteDir) {
- if err := fileOp.CreateDir(rewriteDir, constant.DirPerm); err != nil {
- return err
- }
- }
- rewriteFile := path.Join(rewriteDir, fmt.Sprintf("%s.conf", req.Name))
- switch req.Operate {
- case "create":
- if fileOp.Stat(rewriteFile) {
- return buserr.New("ErrNameIsExist")
- }
- return fileOp.WriteFile(rewriteFile, strings.NewReader(req.Content), constant.DirPerm)
- case "delete":
- return fileOp.DeleteFile(rewriteFile)
- }
- return nil
-}
-
-func (w WebsiteService) ListCustomRewrite() ([]string, error) {
- rewriteDir := GetOpenrestyDir(DefaultRewriteDir)
- fileOp := files.NewFileOp()
- if !fileOp.Stat(rewriteDir) {
- return nil, nil
- }
- entries, err := os.ReadDir(rewriteDir)
- if err != nil {
- return nil, err
- }
- var res []string
- for _, entry := range entries {
- if entry.IsDir() {
- continue
- }
- res = append(res, strings.TrimSuffix(entry.Name(), ".conf"))
- }
- return res, nil
-}
-
-func (w WebsiteService) UpdateSiteDir(req request.WebsiteUpdateDir) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
- if err != nil {
- return err
- }
- runDir := req.SiteDir
- siteDir := path.Join("/www/sites", website.Alias, "index")
- if 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
- }
- website.SiteDir = runDir
- return websiteRepo.Save(context.Background(), &website)
-}
-
-func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermission) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
- if err != nil {
- return err
- }
- absoluteIndexPath := GetSitePath(website, SiteIndexDir)
- cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second))
- if err := cmdMgr.RunBashCf("%s chown -R %s:%s %s", cmd.SudoHandleCmd(), req.User, req.Group, absoluteIndexPath); err != nil {
- return err
- }
- website.User = req.User
- website.Group = req.Group
- return websiteRepo.Save(context.Background(), &website)
-}
-
-func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) {
- var (
- website model.Website
- par *parser.Parser
- oldContent []byte
- )
-
- website, err = websiteRepo.GetFirst(repo.WithByID(req.ID))
- if err != nil {
- return
- }
- fileOp := files.NewFileOp()
- includeDir := GetSitePath(website, SiteProxyDir)
- if !fileOp.Stat(includeDir) {
- _ = fileOp.CreateDir(includeDir, constant.DirPerm)
- }
- fileName := fmt.Sprintf("%s.conf", req.Name)
- includePath := path.Join(includeDir, fileName)
- backName := fmt.Sprintf("%s.bak", req.Name)
- backPath := path.Join(includeDir, backName)
-
- if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) {
- err = buserr.New("ErrNameIsExist")
- return
- }
-
- defer func() {
- if err != nil {
- switch req.Operate {
- case "create":
- _ = fileOp.DeleteFile(includePath)
- case "edit":
- _ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), constant.DirPerm)
- }
- }
- }()
-
- var config *components.Config
-
- switch req.Operate {
- case "create":
- config, err = parser.NewStringParser(string(nginx_conf.GetWebsiteFile("proxy.conf"))).Parse()
- if err != nil {
- return
- }
- case "edit":
- par, err = parser.NewParser(includePath)
- if err != nil {
- return
- }
- config, err = par.Parse()
- if err != nil {
- return
- }
- oldContent, err = fileOp.GetContent(includePath)
- if err != nil {
- return
- }
- case "delete":
- _ = fileOp.DeleteFile(includePath)
- _ = fileOp.DeleteFile(backPath)
- return updateNginxConfig(constant.NginxScopeServer, nil, &website)
- case "disable":
- _ = fileOp.Rename(includePath, backPath)
- return updateNginxConfig(constant.NginxScopeServer, nil, &website)
- case "enable":
- _ = fileOp.Rename(backPath, includePath)
- return updateNginxConfig(constant.NginxScopeServer, nil, &website)
- }
-
- config.FilePath = includePath
- directives := config.Directives
-
- var location *components.Location
- for _, directive := range directives {
- if loc, ok := directive.(*components.Location); ok {
- location = loc
- break
- }
- }
- if location == nil {
- err = errors.New("invalid proxy config, no location found")
- return
- }
- location.UpdateDirective("proxy_pass", []string{req.ProxyPass})
- location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost})
- location.ChangePath(req.Modifier, req.Match)
- // Server Cache Settings
- if req.Cache {
- if err = openProxyCache(website); err != nil {
- return
- }
- location.AddServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias), req.ServerCacheTime, req.ServerCacheUnit)
- } else {
- location.RemoveServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias))
- }
- // Browser Cache Settings
- if req.CacheTime > 0 {
- location.AddBrowserCache(req.CacheTime, req.CacheUnit)
- } else if req.CacheTime == 0 {
- location.RemoveBrowserCache()
- } else {
- location.AddBroswerNoCache()
- }
- // Content Replace Settings
- if len(req.Replaces) > 0 {
- location.AddSubFilter(req.Replaces)
- } else {
- location.RemoveSubFilter()
- }
- // SSL Settings
- if req.SNI {
- location.UpdateDirective("proxy_ssl_server_name", []string{"on"})
- if req.ProxySSLName != "" {
- location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName})
- }
- } else {
- location.UpdateDirective("proxy_ssl_server_name", []string{"off"})
- }
- // CORS Settings
- if req.Cors {
- location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"})
- if req.AllowMethods != "" {
- location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"})
- } else {
- location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"})
- }
- if req.AllowHeaders != "" {
- location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"})
- } else {
- location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"})
- }
- if req.AllowCredentials {
- location.UpdateDirective("add_header", []string{"Access-Control-Allow-Credentials", "true", "always"})
- } else {
- location.RemoveDirective("add_header", []string{"Access-Control-Allow-Credentials"})
- }
- if req.Preflight {
- location.AddCorsOption()
- } else {
- location.RemoveCorsOption()
- }
- } else {
- location.RemoveDirective("add_header", []string{"Access-Control-Allow-Origin"})
- location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"})
- location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"})
- location.RemoveDirective("add_header", []string{"Access-Control-Allow-Credentials"})
- location.RemoveDirectiveByFullParams("if", []string{"(", "$request_method", "=", "'OPTIONS'", ")"})
- }
- if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
- return buserr.WithErr("ErrUpdateBuWebsite", err)
- }
- nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
- return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website)
-}
-
-func (w WebsiteService) UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error) {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return
- }
- cacheDir := GetSitePath(website, SiteCacheDir)
- fileOp := files.NewFileOp()
- if !fileOp.Stat(cacheDir) {
- _ = fileOp.CreateDir(cacheDir, constant.DirPerm)
- }
- if req.Open {
- proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:%d%s max_size=%d%s inactive=%d%s", website.Alias, website.Alias, req.ShareCache, req.ShareCacheUnit, req.CacheLimit, req.CacheLimitUnit, req.CacheExpire, req.CacheExpireUnit)
- return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website)
- }
- return deleteNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path"}}, &website)
-}
-
-func (w WebsiteService) GetProxyCache(id uint) (res response.NginxProxyCache, err error) {
- var (
- website model.Website
- )
- website, err = websiteRepo.GetFirst(repo.WithByID(id))
- if err != nil {
- return
- }
-
- parser, err := parser.NewParser(GetSitePath(website, SiteConf))
- if err != nil {
- return
- }
- config, err := parser.Parse()
- if err != nil {
- return
- }
- var params []string
- for _, d := range config.GetDirectives() {
- if d.GetName() == "proxy_cache_path" {
- params = d.GetParameters()
- }
- }
- if len(params) == 0 {
- return
- }
- for _, param := range params {
- if re.GetRegex(re.ProxyCacheZonePattern).MatchString(param) {
- matches := re.GetRegex(re.ProxyCacheZonePattern).FindStringSubmatch(param)
- if len(matches) > 0 {
- res.ShareCache, _ = strconv.Atoi(matches[1])
- res.ShareCacheUnit = matches[2]
- }
- }
-
- if re.GetRegex(re.ProxyCacheMaxSizeValidationPattern).MatchString(param) {
- matches := re.GetRegex(re.ProxyCacheMaxSizePattern).FindStringSubmatch(param)
- if len(matches) > 0 {
- res.CacheLimit, _ = strconv.ParseFloat(matches[1], 64)
- res.CacheLimitUnit = matches[2]
- }
- }
- if re.GetRegex(re.ProxyCacheInactivePattern).MatchString(param) {
- matches := re.GetRegex(re.ProxyCacheInactivePattern).FindStringSubmatch(param)
- if len(matches) > 0 {
- res.CacheExpire, _ = strconv.Atoi(matches[1])
- res.CacheExpireUnit = matches[2]
- }
- }
- }
- res.Open = true
- return
-}
-
-func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) {
- var (
- website model.Website
- fileList response.FileInfo
- )
- website, err = websiteRepo.GetFirst(repo.WithByID(id))
- if err != nil {
- return
- }
- includeDir := GetSitePath(website, SiteProxyDir)
- fileOp := files.NewFileOp()
- if !fileOp.Stat(includeDir) {
- return
- }
- fileList, err = NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: includeDir, Expand: true, Page: 1, PageSize: 100}})
- if len(fileList.Items) == 0 {
- return
- }
- var (
- content []byte
- config *components.Config
- )
- for _, configFile := range fileList.Items {
- proxyConfig := request.WebsiteProxyConfig{
- ID: website.ID,
- }
- parts := strings.Split(configFile.Name, ".")
- proxyConfig.Name = parts[0]
- if parts[1] == "conf" {
- proxyConfig.Enable = true
- } else {
- proxyConfig.Enable = false
- }
- proxyConfig.FilePath = configFile.Path
- content, err = fileOp.GetContent(configFile.Path)
- if err != nil {
- return
- }
- proxyConfig.Content = string(content)
- config, err = parser.NewStringParser(string(content)).Parse()
- if err != nil {
- return nil, err
- }
- directives := config.GetDirectives()
-
- var location *components.Location
- for _, directive := range directives {
- if loc, ok := directive.(*components.Location); ok {
- location = loc
- break
- }
- }
- if location == nil {
- err = errors.New("invalid proxy config, no location found")
- return
- }
- proxyConfig.ProxyPass = location.ProxyPass
- proxyConfig.Cache = location.Cache
- proxyConfig.CacheTime = location.CacheTime
- proxyConfig.CacheUnit = location.CacheUint
- if location.ServerCacheTime > 0 {
- proxyConfig.ServerCacheTime = location.ServerCacheTime
- proxyConfig.ServerCacheUnit = location.ServerCacheUint
- }
- proxyConfig.Match = location.Match
- proxyConfig.Modifier = location.Modifier
- proxyConfig.ProxyHost = location.Host
- proxyConfig.Replaces = location.Replaces
- for _, directive := range location.Directives {
- if directive.GetName() == "proxy_ssl_server_name" {
- proxyConfig.SNI = directive.GetParameters()[0] == "on"
- }
- if directive.GetName() == "proxy_ssl_name" && len(directive.GetParameters()) > 0 {
- proxyConfig.ProxySSLName = directive.GetParameters()[0]
- }
- }
- proxyConfig.Cors = location.Cors
- proxyConfig.AllowCredentials = location.AllowCredentials
- proxyConfig.AllowHeaders = location.AllowHeaders
- proxyConfig.AllowOrigins = location.AllowOrigins
- proxyConfig.AllowMethods = location.AllowMethods
- proxyConfig.Preflight = location.Preflight
- res = append(res, proxyConfig)
- }
- return
-}
-
-func (w WebsiteService) UpdateProxyFile(req request.NginxProxyUpdate) (err error) {
- var (
- website model.Website
- oldRewriteContent []byte
- )
- website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return err
- }
- absolutePath := fmt.Sprintf("%s/%s.conf", GetSitePath(website, SiteProxyDir), req.Name)
- fileOp := files.NewFileOp()
- oldRewriteContent, err = fileOp.GetContent(absolutePath)
- if err != nil {
- return err
- }
- if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
- return err
- }
- defer func() {
- if err != nil {
- _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm)
- }
- }()
- return updateNginxConfig(constant.NginxScopeServer, nil, &website)
-}
-
-func (w WebsiteService) ClearProxyCache(req request.NginxCommonReq) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return err
- }
- cacheDir := GetSitePath(website, SiteCacheDir)
- fileOp := files.NewFileOp()
- if fileOp.Stat(cacheDir) {
- if err = fileOp.CleanDir(cacheDir); err != nil {
- return err
- }
- }
- nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
- if err != nil {
- return err
- }
- if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
- return err
- }
- return nil
-}
-
-func (w WebsiteService) DeleteProxy(req request.WebsiteProxyDel) (err error) {
- fileOp := files.NewFileOp()
- website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
- if err != nil {
- return
- }
- nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
- if err != nil {
- return
- }
- includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
- if !fileOp.Stat(includeDir) {
- _ = fileOp.CreateDir(includeDir, 0755)
- }
- fileName := fmt.Sprintf("%s.conf", req.Name)
- includePath := path.Join(includeDir, fileName)
- backName := fmt.Sprintf("%s.bak", req.Name)
- backPath := path.Join(includeDir, backName)
- _ = fileOp.DeleteFile(includePath)
- _ = fileOp.DeleteFile(backPath)
- return updateNginxConfig(constant.NginxScopeServer, nil, &website)
-}
-
-func (w WebsiteService) UpdateCors(req request.CorsConfigReq) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return err
+ return err
}
params := []dto.NginxParam{
{Name: "add_header", Params: []string{"Access-Control-Allow-Origin"}},
{Name: "add_header", Params: []string{"Access-Control-Allow-Methods"}},
- {Name: "add_header", Params: []string{"Access-Control-Allow-Headers"}},
- {Name: "add_header", Params: []string{"Access-Control-Allow-Credentials"}},
- {Name: "if", Params: []string{"(", "$request_method", "=", "'OPTIONS'", ")"}},
- }
- if err := deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil {
- return err
- }
- if req.Cors {
- return updateWebsiteConfig(website, func(server *components.Server) error {
- server.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"})
- if req.AllowMethods != "" {
- server.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"})
- }
- if req.AllowHeaders != "" {
- server.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"})
- }
- if req.AllowCredentials {
- server.UpdateDirective("add_header", []string{"Access-Control-Allow-Credentials", "true", "always"})
- }
- if req.Preflight {
- server.AddCorsOption()
- }
- return nil
- })
- }
- return nil
-}
-
-func (w WebsiteService) GetCors(websiteID uint) (*request.CorsConfig, error) {
- website, err := websiteRepo.GetFirst(repo.WithByID(websiteID))
- if err != nil {
- return nil, err
- }
- server, err := getServer(website)
- if err != nil {
- return nil, err
- }
- if server == nil {
- return nil, nil
- }
- cors := &request.CorsConfig{
- Cors: server.Cors,
- AllowOrigins: server.AllowOrigins,
- AllowMethods: server.AllowMethods,
- AllowHeaders: server.AllowHeaders,
- AllowCredentials: server.AllowCredentials,
- Preflight: server.Preflight,
- }
- return cors, nil
-}
-
-func (w WebsiteService) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) {
- var (
- website model.Website
- authContent []byte
- nginxParams []response.NginxParam
- )
- website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return
- }
- absoluteAuthPath := GetSitePath(website, SiteRootAuthBasicPath)
- fileOp := files.NewFileOp()
- if !fileOp.Stat(absoluteAuthPath) {
- return
- }
- nginxParams, err = getNginxParamsByKeys(constant.NginxScopeServer, []string{"auth_basic"}, &website)
- if err != nil {
- return
- }
- res.Enable = len(nginxParams[0].Params) > 0
- authContent, err = fileOp.GetContent(absoluteAuthPath)
- authArray := strings.Split(string(authContent), "\n")
- for _, line := range authArray {
- if line == "" {
- continue
- }
- params := strings.Split(line, ":")
- auth := dto.NginxAuth{
- Username: params[0],
- }
- if len(params) == 3 {
- auth.Remark = params[2]
- }
- res.Items = append(res.Items, auth)
- }
- return
-}
-
-func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error) {
- var (
- website model.Website
- params []dto.NginxParam
- authContent []byte
- authArray []string
- )
- website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return err
- }
- authPath := fmt.Sprintf("/www/sites/%s/auth_basic/auth.pass", website.Alias)
- absoluteAuthPath := GetSitePath(website, SiteRootAuthBasicPath)
- fileOp := files.NewFileOp()
- if !fileOp.Stat(path.Dir(absoluteAuthPath)) {
- _ = fileOp.CreateDir(path.Dir(absoluteAuthPath), constant.DirPerm)
- }
- if !fileOp.Stat(absoluteAuthPath) {
- _ = fileOp.CreateFile(absoluteAuthPath)
- }
-
- params = append(params, dto.NginxParam{Name: "auth_basic", Params: []string{`"Authentication"`}})
- params = append(params, dto.NginxParam{Name: "auth_basic_user_file", Params: []string{authPath}})
- authContent, err = fileOp.GetContent(absoluteAuthPath)
- if err != nil {
- return
- }
- if len(authContent) > 0 {
- authArray = strings.Split(string(authContent), "\n")
- }
- switch req.Operate {
- case "disable":
- return deleteNginxConfig(constant.NginxScopeServer, params, &website)
- case "enable":
- return updateNginxConfig(constant.NginxScopeServer, params, &website)
- case "create":
- for _, line := range authArray {
- authParams := strings.Split(line, ":")
- username := authParams[0]
- if username == req.Username {
- err = buserr.New("ErrUsernameIsExist")
- return
- }
- }
- var passwdHash []byte
- passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
- if err != nil {
- return
- }
- line := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
- if req.Remark != "" {
- line = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
- }
- authArray = append(authArray, line)
- case "edit":
- userExist := false
- for index, line := range authArray {
- authParams := strings.Split(line, ":")
- username := authParams[0]
- if username == req.Username {
- userExist = true
- var passwdHash []byte
- passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
- if err != nil {
- return
- }
- userPasswd := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
- if req.Remark != "" {
- userPasswd = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
- }
- authArray[index] = userPasswd
- }
- }
- if !userExist {
- err = buserr.New("ErrUsernameIsNotExist")
- return
- }
- case "delete":
- deleteIndex := -1
- for index, line := range authArray {
- authParams := strings.Split(line, ":")
- username := authParams[0]
- if username == req.Username {
- deleteIndex = index
- }
- }
- if deleteIndex < 0 {
- return
- }
- authArray = append(authArray[:deleteIndex], authArray[deleteIndex+1:]...)
- }
-
- var passFile *os.File
- passFile, err = os.Create(absoluteAuthPath)
- if err != nil {
- return
- }
- defer passFile.Close()
- writer := bufio.NewWriter(passFile)
- for _, line := range authArray {
- if line == "" {
- continue
- }
- _, err = writer.WriteString(line + "\n")
- if err != nil {
- return
- }
- }
- err = writer.Flush()
- if err != nil {
- return
- }
- authContent, err = fileOp.GetContent(absoluteAuthPath)
- if err != nil {
- return
- }
- if len(authContent) == 0 {
- if err = deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil {
- return
- }
- }
- return
-}
-
-func (w WebsiteService) GetPathAuthBasics(req request.NginxAuthReq) (res []response.NginxPathAuthRes, err error) {
- var (
- website model.Website
- authContent []byte
- )
- website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return
- }
- fileOp := files.NewFileOp()
- absoluteAuthDir := GetSitePath(website, SitePathAuthBasicDir)
- passDir := path.Join(absoluteAuthDir, "pass")
- if !fileOp.Stat(absoluteAuthDir) || !fileOp.Stat(passDir) {
- return
+ {Name: "add_header", Params: []string{"Access-Control-Allow-Headers"}},
+ {Name: "add_header", Params: []string{"Access-Control-Allow-Credentials"}},
+ {Name: "if", Params: []string{"(", "$request_method", "=", "'OPTIONS'", ")"}},
}
-
- entries, err := os.ReadDir(absoluteAuthDir)
- if err != nil {
- return nil, err
+ if err := deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil {
+ return err
}
-
- for _, entry := range entries {
- if !entry.IsDir() {
- name := strings.TrimSuffix(entry.Name(), ".conf")
- pathAuth := dto.NginxPathAuth{
- Name: name,
- }
- configPath := path.Join(absoluteAuthDir, entry.Name())
- content, err := fileOp.GetContent(configPath)
- if err != nil {
- return nil, err
+ if req.Cors {
+ return updateWebsiteConfig(website, func(server *components.Server) error {
+ server.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"})
+ if req.AllowMethods != "" {
+ server.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"})
}
- config, err := parser.NewStringParser(string(content)).Parse()
- if err != nil {
- return nil, err
+ if req.AllowHeaders != "" {
+ server.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"})
}
- directives := config.Directives
- location, _ := directives[0].(*components.Location)
- pathAuth.Path = strings.TrimPrefix(location.Match, "^")
- passPath := path.Join(passDir, fmt.Sprintf("%s.pass", name))
- authContent, err = fileOp.GetContent(passPath)
- if err != nil {
- return nil, err
+ if req.AllowCredentials {
+ server.UpdateDirective("add_header", []string{"Access-Control-Allow-Credentials", "true", "always"})
}
- authArray := strings.Split(string(authContent), "\n")
- for _, line := range authArray {
- if line == "" {
- continue
- }
- params := strings.Split(line, ":")
- pathAuth.Username = params[0]
- if len(params) == 3 {
- pathAuth.Remark = params[2]
- }
+ if req.Preflight {
+ server.AddCorsOption()
}
- res = append(res, response.NginxPathAuthRes{
- NginxPathAuth: pathAuth,
- })
- }
+ return nil
+ })
}
- return
+ return nil
}
-func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+func (w WebsiteService) GetCors(websiteID uint) (*request.CorsConfig, error) {
+ website, err := websiteRepo.GetFirst(repo.WithByID(websiteID))
if err != nil {
- return err
- }
- fileOp := files.NewFileOp()
- authDir := GetSitePath(website, SitePathAuthBasicDir)
- if !fileOp.Stat(authDir) {
- _ = fileOp.CreateDir(authDir, constant.DirPerm)
- }
- passDir := path.Join(authDir, "pass")
- if !fileOp.Stat(passDir) {
- _ = fileOp.CreateDir(passDir, constant.DirPerm)
- }
- confPath := path.Join(authDir, fmt.Sprintf("%s.conf", req.Name))
- passPath := path.Join(passDir, fmt.Sprintf("%s.pass", req.Name))
- var config *components.Config
- switch req.Operate {
- case "delete":
- _ = fileOp.DeleteFile(confPath)
- _ = fileOp.DeleteFile(passPath)
- return updateNginxConfig(constant.NginxScopeServer, nil, &website)
- case "create":
- config, err = parser.NewStringParser(string(nginx_conf.PathAuth)).Parse()
- if err != nil {
- return err
- }
- if fileOp.Stat(confPath) || fileOp.Stat(passPath) {
- return buserr.New("ErrNameIsExist")
- }
- case "edit":
- par, err := parser.NewParser(confPath)
- if err != nil {
- return err
- }
- config, err = par.Parse()
- if err != nil {
- return err
- }
+ return nil, err
}
- config.FilePath = confPath
- directives := config.Directives
- location, _ := directives[0].(*components.Location)
- location.UpdateDirective("auth_basic_user_file", []string{fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name))})
- location.ChangePath("~*", fmt.Sprintf("^%s", req.Path))
- var passwdHash []byte
- passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
+ server, err := getServer(website)
if err != nil {
- return err
- }
- line := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
- if req.Remark != "" {
- line = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
+ return nil, err
}
- _ = fileOp.SaveFile(passPath, line, constant.DirPerm)
- if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
- return buserr.WithErr("ErrUpdateBuWebsite", err)
+ if server == nil {
+ return nil, nil
}
- nginxInclude := fmt.Sprintf("/www/sites/%s/path_auth/*.conf", website.Alias)
- if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
- return nil
+ cors := &request.CorsConfig{
+ Cors: server.Cors,
+ AllowOrigins: server.AllowOrigins,
+ AllowMethods: server.AllowMethods,
+ AllowHeaders: server.AllowHeaders,
+ AllowCredentials: server.AllowCredentials,
+ Preflight: server.Preflight,
}
- return nil
+ return cors, nil
}
func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error) {
@@ -3049,216 +2136,6 @@ func (w WebsiteService) UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error {
return fileOp.SaveFile(resourcePath, req.Content, constant.DirPerm)
}
-func (w WebsiteService) GetLoadBalances(id uint) ([]dto.NginxUpstream, error) {
- website, err := websiteRepo.GetFirst(repo.WithByID(id))
- if err != nil {
- return nil, err
- }
- includeDir := GetSitePath(website, SiteUpstreamDir)
- fileOp := files.NewFileOp()
- if !fileOp.Stat(includeDir) {
- return nil, nil
- }
- entries, err := os.ReadDir(includeDir)
- if err != nil {
- return nil, err
- }
- var res []dto.NginxUpstream
- for _, entry := range entries {
- if entry.IsDir() {
- continue
- }
- name := entry.Name()
- if !strings.HasSuffix(name, ".conf") {
- continue
- }
- upstreamName := strings.TrimSuffix(name, ".conf")
- upstream := dto.NginxUpstream{
- Name: upstreamName,
- }
- upstreamPath := path.Join(includeDir, name)
- content, err := fileOp.GetContent(upstreamPath)
- if err != nil {
- return nil, err
- }
- upstream.Content = string(content)
- nginxParser, err := parser.NewParser(upstreamPath)
- if err != nil {
- return nil, err
- }
- config, err := nginxParser.Parse()
- if err != nil {
- return nil, err
- }
- upstreams := config.FindUpstreams()
- for _, up := range upstreams {
- if up.UpstreamName == upstreamName {
- directives := up.GetDirectives()
- for _, d := range directives {
- dName := d.GetName()
- if _, ok := dto.LBAlgorithms[dName]; ok {
- upstream.Algorithm = dName
- }
- }
- upstream.Servers = getNginxUpstreamServers(up.UpstreamServers)
- }
- }
- res = append(res, upstream)
- }
- return res, nil
-}
-
-func (w WebsiteService) CreateLoadBalance(req request.WebsiteLBCreate) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return err
- }
- includeDir := GetSitePath(website, SiteUpstreamDir)
- fileOp := files.NewFileOp()
- if !fileOp.Stat(includeDir) {
- _ = fileOp.CreateDir(includeDir, constant.DirPerm)
- }
- filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
- if fileOp.Stat(filePath) {
- return buserr.New("ErrNameIsExist")
- }
- config, err := parser.NewStringParser(string(nginx_conf.Upstream)).Parse()
- if err != nil {
- return err
- }
- config.Block = &components.Block{}
- config.FilePath = filePath
- upstream := components.Upstream{
- UpstreamName: req.Name,
- }
- if req.Algorithm != "default" {
- upstream.UpdateDirective(req.Algorithm, []string{})
- }
- upstream.UpstreamServers = parseUpstreamServers(req.Servers)
- config.Block.Directives = append(config.Block.Directives, &upstream)
-
- defer func() {
- if err != nil {
- _ = fileOp.DeleteFile(filePath)
- }
- }()
-
- if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
- return buserr.WithErr("ErrUpdateBuWebsite", err)
- }
- nginxInclude := fmt.Sprintf("/www/sites/%s/upstream/*.conf", website.Alias)
- if err = updateNginxConfig("", []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
- return err
- }
- return nil
-}
-
-func (w WebsiteService) UpdateLoadBalance(req request.WebsiteLBUpdate) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return err
- }
- nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
- if err != nil {
- return err
- }
- includeDir := GetSitePath(website, SiteUpstreamDir)
- fileOp := files.NewFileOp()
- filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
- if !fileOp.Stat(filePath) {
- return nil
- }
- oldContent, err := fileOp.GetContent(filePath)
- if err != nil {
- return err
- }
- parser, err := parser.NewParser(filePath)
- if err != nil {
- return err
- }
- config, err := parser.Parse()
- if err != nil {
- return err
- }
- upstreams := config.FindUpstreams()
- for _, up := range upstreams {
- if up.UpstreamName == req.Name {
- directives := up.GetDirectives()
- for _, d := range directives {
- dName := d.GetName()
- if _, ok := dto.LBAlgorithms[dName]; ok {
- up.RemoveDirective(dName, nil)
- }
- }
- if req.Algorithm != "default" {
- up.UpdateDirective(req.Algorithm, []string{})
- }
- up.UpstreamServers = parseUpstreamServers(req.Servers)
- }
- }
- if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
- return buserr.WithErr("ErrUpdateBuWebsite", err)
- }
- return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName)
-}
-
-func (w WebsiteService) DeleteLoadBalance(req request.WebsiteLBDelete) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return err
- }
- nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
- if err != nil {
- return err
- }
- proxies, _ := w.GetProxies(website.ID)
- if len(proxies) > 0 {
- for _, proxy := range proxies {
- if strings.HasSuffix(proxy.ProxyPass, fmt.Sprintf("://%s", req.Name)) {
- return buserr.New("ErrProxyIsUsed")
- }
- }
- }
-
- includeDir := GetSitePath(website, SiteUpstreamDir)
- fileOp := files.NewFileOp()
- filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
- if !fileOp.Stat(filePath) {
- return nil
- }
- if err = fileOp.DeleteFile(filePath); err != nil {
- return err
- }
- return opNginx(nginxInstall.ContainerName, constant.NginxReload)
-}
-
-func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error {
- website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
- if err != nil {
- return err
- }
- nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
- if err != nil {
- return err
- }
- includeDir := GetSitePath(website, SiteUpstreamDir)
- filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
- fileOp := files.NewFileOp()
- oldContent, err := fileOp.GetContent(filePath)
- if err != nil {
- return err
- }
- if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
- return err
- }
- defer func() {
- if err != nil {
- _ = fileOp.WriteFile(filePath, bytes.NewReader(oldContent), constant.DirPerm)
- }
- }()
- return opNginx(nginxInstall.ContainerName, constant.NginxReload)
-}
-
func (w WebsiteService) ChangeGroup(group, newGroup uint) error {
return websiteRepo.UpdateGroup(group, newGroup)
}
@@ -3521,3 +2398,57 @@ func (w WebsiteService) ExecComposer(req request.ExecComposerReq) error {
}()
return nil
}
+
+func (w WebsiteService) UpdateStream(req request.StreamUpdate) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ nginxFull, err := getNginxFull(&website)
+ if err != nil {
+ return nil
+ }
+ website.StreamPorts = req.StreamConfig.StreamPorts
+ ports := strings.Split(req.StreamConfig.StreamPorts, ",")
+ for _, port := range ports {
+ portNum, _ := strconv.Atoi(port)
+ if err = checkWebsitePort(nginxFull.Install.HttpsPort, portNum, website.Type); err != nil {
+ return err
+ }
+ }
+
+ config := nginxFull.SiteConfig.Config
+ servers := config.FindServers()
+ if len(servers) == 0 {
+ return errors.New("nginx config is not valid")
+ }
+ server := servers[0]
+ server.Listens = []*components.ServerListen{}
+ for _, port := range ports {
+ server.UpdateListen(port, false)
+ if website.IPV6 {
+ server.UpdateListen("[::]:"+port, false)
+ }
+ }
+ upstream := components.Upstream{
+ UpstreamName: website.Alias,
+ }
+ if req.Algorithm != "default" {
+ upstream.UpdateDirective(req.Algorithm, []string{})
+ }
+ upstream.UpstreamServers = parseUpstreamServers(req.Servers)
+ for i, dir := range config.Block.Directives {
+ if dir.GetName() == "upstream" && dir.GetParameters()[0] == website.Alias {
+ config.Block.Directives[i] = &upstream
+ }
+ }
+
+ if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
+ return err
+ }
+ if err = nginxCheckAndReload(nginxFull.SiteConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName); err != nil {
+ return err
+ }
+ website.StreamPorts = req.StreamConfig.StreamPorts
+ return websiteRepo.Save(context.Background(), &website)
+}
diff --git a/agent/app/service/website_auth_basic.go b/agent/app/service/website_auth_basic.go
new file mode 100644
index 000000000000..3d1c803528ca
--- /dev/null
+++ b/agent/app/service/website_auth_basic.go
@@ -0,0 +1,312 @@
+package service
+
+import (
+ "bufio"
+ "fmt"
+ "github.com/1Panel-dev/1Panel/agent/app/dto"
+ "github.com/1Panel-dev/1Panel/agent/app/dto/request"
+ "github.com/1Panel-dev/1Panel/agent/app/dto/response"
+ "github.com/1Panel-dev/1Panel/agent/app/model"
+ "github.com/1Panel-dev/1Panel/agent/app/repo"
+ "github.com/1Panel-dev/1Panel/agent/buserr"
+ "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
+ "github.com/1Panel-dev/1Panel/agent/constant"
+ "github.com/1Panel-dev/1Panel/agent/utils/files"
+ "github.com/1Panel-dev/1Panel/agent/utils/nginx"
+ "github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
+ "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
+ "golang.org/x/crypto/bcrypt"
+ "os"
+ "path"
+ "strings"
+)
+
+func (w WebsiteService) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) {
+ var (
+ website model.Website
+ authContent []byte
+ nginxParams []response.NginxParam
+ )
+ website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return
+ }
+ absoluteAuthPath := GetSitePath(website, SiteRootAuthBasicPath)
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(absoluteAuthPath) {
+ return
+ }
+ nginxParams, err = getNginxParamsByKeys(constant.NginxScopeServer, []string{"auth_basic"}, &website)
+ if err != nil {
+ return
+ }
+ res.Enable = len(nginxParams[0].Params) > 0
+ authContent, err = fileOp.GetContent(absoluteAuthPath)
+ authArray := strings.Split(string(authContent), "\n")
+ for _, line := range authArray {
+ if line == "" {
+ continue
+ }
+ params := strings.Split(line, ":")
+ auth := dto.NginxAuth{
+ Username: params[0],
+ }
+ if len(params) == 3 {
+ auth.Remark = params[2]
+ }
+ res.Items = append(res.Items, auth)
+ }
+ return
+}
+
+func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error) {
+ var (
+ website model.Website
+ params []dto.NginxParam
+ authContent []byte
+ authArray []string
+ )
+ website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ authPath := fmt.Sprintf("/www/sites/%s/auth_basic/auth.pass", website.Alias)
+ absoluteAuthPath := GetSitePath(website, SiteRootAuthBasicPath)
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(path.Dir(absoluteAuthPath)) {
+ _ = fileOp.CreateDir(path.Dir(absoluteAuthPath), constant.DirPerm)
+ }
+ if !fileOp.Stat(absoluteAuthPath) {
+ _ = fileOp.CreateFile(absoluteAuthPath)
+ }
+
+ params = append(params, dto.NginxParam{Name: "auth_basic", Params: []string{`"Authentication"`}})
+ params = append(params, dto.NginxParam{Name: "auth_basic_user_file", Params: []string{authPath}})
+ authContent, err = fileOp.GetContent(absoluteAuthPath)
+ if err != nil {
+ return
+ }
+ if len(authContent) > 0 {
+ authArray = strings.Split(string(authContent), "\n")
+ }
+ switch req.Operate {
+ case "disable":
+ return deleteNginxConfig(constant.NginxScopeServer, params, &website)
+ case "enable":
+ return updateNginxConfig(constant.NginxScopeServer, params, &website)
+ case "create":
+ for _, line := range authArray {
+ authParams := strings.Split(line, ":")
+ username := authParams[0]
+ if username == req.Username {
+ err = buserr.New("ErrUsernameIsExist")
+ return
+ }
+ }
+ var passwdHash []byte
+ passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
+ if err != nil {
+ return
+ }
+ line := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
+ if req.Remark != "" {
+ line = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
+ }
+ authArray = append(authArray, line)
+ case "edit":
+ userExist := false
+ for index, line := range authArray {
+ authParams := strings.Split(line, ":")
+ username := authParams[0]
+ if username == req.Username {
+ userExist = true
+ var passwdHash []byte
+ passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
+ if err != nil {
+ return
+ }
+ userPasswd := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
+ if req.Remark != "" {
+ userPasswd = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
+ }
+ authArray[index] = userPasswd
+ }
+ }
+ if !userExist {
+ err = buserr.New("ErrUsernameIsNotExist")
+ return
+ }
+ case "delete":
+ deleteIndex := -1
+ for index, line := range authArray {
+ authParams := strings.Split(line, ":")
+ username := authParams[0]
+ if username == req.Username {
+ deleteIndex = index
+ }
+ }
+ if deleteIndex < 0 {
+ return
+ }
+ authArray = append(authArray[:deleteIndex], authArray[deleteIndex+1:]...)
+ }
+
+ var passFile *os.File
+ passFile, err = os.Create(absoluteAuthPath)
+ if err != nil {
+ return
+ }
+ defer passFile.Close()
+ writer := bufio.NewWriter(passFile)
+ for _, line := range authArray {
+ if line == "" {
+ continue
+ }
+ _, err = writer.WriteString(line + "\n")
+ if err != nil {
+ return
+ }
+ }
+ err = writer.Flush()
+ if err != nil {
+ return
+ }
+ authContent, err = fileOp.GetContent(absoluteAuthPath)
+ if err != nil {
+ return
+ }
+ if len(authContent) == 0 {
+ if err = deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil {
+ return
+ }
+ }
+ return
+}
+
+func (w WebsiteService) GetPathAuthBasics(req request.NginxAuthReq) (res []response.NginxPathAuthRes, err error) {
+ var (
+ website model.Website
+ authContent []byte
+ )
+ website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return
+ }
+ fileOp := files.NewFileOp()
+ absoluteAuthDir := GetSitePath(website, SitePathAuthBasicDir)
+ passDir := path.Join(absoluteAuthDir, "pass")
+ if !fileOp.Stat(absoluteAuthDir) || !fileOp.Stat(passDir) {
+ return
+ }
+
+ entries, err := os.ReadDir(absoluteAuthDir)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, entry := range entries {
+ if !entry.IsDir() {
+ name := strings.TrimSuffix(entry.Name(), ".conf")
+ pathAuth := dto.NginxPathAuth{
+ Name: name,
+ }
+ configPath := path.Join(absoluteAuthDir, entry.Name())
+ content, err := fileOp.GetContent(configPath)
+ if err != nil {
+ return nil, err
+ }
+ config, err := parser.NewStringParser(string(content)).Parse()
+ if err != nil {
+ return nil, err
+ }
+ directives := config.Directives
+ location, _ := directives[0].(*components.Location)
+ pathAuth.Path = strings.TrimPrefix(location.Match, "^")
+ passPath := path.Join(passDir, fmt.Sprintf("%s.pass", name))
+ authContent, err = fileOp.GetContent(passPath)
+ if err != nil {
+ return nil, err
+ }
+ authArray := strings.Split(string(authContent), "\n")
+ for _, line := range authArray {
+ if line == "" {
+ continue
+ }
+ params := strings.Split(line, ":")
+ pathAuth.Username = params[0]
+ if len(params) == 3 {
+ pathAuth.Remark = params[2]
+ }
+ }
+ res = append(res, response.NginxPathAuthRes{
+ NginxPathAuth: pathAuth,
+ })
+ }
+ }
+ return
+}
+
+func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ fileOp := files.NewFileOp()
+ authDir := GetSitePath(website, SitePathAuthBasicDir)
+ if !fileOp.Stat(authDir) {
+ _ = fileOp.CreateDir(authDir, constant.DirPerm)
+ }
+ passDir := path.Join(authDir, "pass")
+ if !fileOp.Stat(passDir) {
+ _ = fileOp.CreateDir(passDir, constant.DirPerm)
+ }
+ confPath := path.Join(authDir, fmt.Sprintf("%s.conf", req.Name))
+ passPath := path.Join(passDir, fmt.Sprintf("%s.pass", req.Name))
+ var config *components.Config
+ switch req.Operate {
+ case "delete":
+ _ = fileOp.DeleteFile(confPath)
+ _ = fileOp.DeleteFile(passPath)
+ return updateNginxConfig(constant.NginxScopeServer, nil, &website)
+ case "create":
+ config, err = parser.NewStringParser(string(nginx_conf.PathAuth)).Parse()
+ if err != nil {
+ return err
+ }
+ if fileOp.Stat(confPath) || fileOp.Stat(passPath) {
+ return buserr.New("ErrNameIsExist")
+ }
+ case "edit":
+ par, err := parser.NewParser(confPath)
+ if err != nil {
+ return err
+ }
+ config, err = par.Parse()
+ if err != nil {
+ return err
+ }
+ }
+ config.FilePath = confPath
+ directives := config.Directives
+ location, _ := directives[0].(*components.Location)
+ location.UpdateDirective("auth_basic_user_file", []string{fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name))})
+ location.ChangePath("~*", fmt.Sprintf("^%s", req.Path))
+ var passwdHash []byte
+ passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
+ if err != nil {
+ return err
+ }
+ line := fmt.Sprintf("%s:%s\n", req.Username, passwdHash)
+ if req.Remark != "" {
+ line = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark)
+ }
+ _ = fileOp.SaveFile(passPath, line, constant.DirPerm)
+ if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
+ return buserr.WithErr("ErrUpdateBuWebsite", err)
+ }
+ nginxInclude := fmt.Sprintf("/www/sites/%s/path_auth/*.conf", website.Alias)
+ if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
+ return nil
+ }
+ return nil
+}
diff --git a/agent/app/service/website_domain.go b/agent/app/service/website_domain.go
new file mode 100644
index 000000000000..1bf2e0076146
--- /dev/null
+++ b/agent/app/service/website_domain.go
@@ -0,0 +1,185 @@
+package service
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "github.com/1Panel-dev/1Panel/agent/app/dto/request"
+ "github.com/1Panel-dev/1Panel/agent/app/model"
+ "github.com/1Panel-dev/1Panel/agent/app/repo"
+ "github.com/1Panel-dev/1Panel/agent/constant"
+ "github.com/1Panel-dev/1Panel/agent/utils/files"
+ "path"
+ "strconv"
+)
+
+func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) {
+ var (
+ domainModels []model.WebsiteDomain
+ addPorts []int
+ )
+ httpPort, httpsPort, err := getAppInstallPort(constant.AppOpenresty)
+ if err != nil {
+ return nil, err
+ }
+ website, err := websiteRepo.GetFirst(repo.WithByID(create.WebsiteID))
+ if err != nil {
+ return nil, err
+ }
+
+ domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, httpsPort, create.WebsiteID)
+ if err != nil {
+ return nil, err
+ }
+ go func() {
+ _ = OperateFirewallPort(nil, addPorts)
+ }()
+
+ nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
+ if err != nil {
+ return nil, err
+ }
+ wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
+ fileOp := files.NewFileOp()
+ if fileOp.Stat(wafDataPath) {
+ websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
+ content, err := fileOp.GetContent(websitesConfigPath)
+ if err != nil {
+ return nil, err
+ }
+ var websitesArray []request.WafWebsite
+ if content != nil {
+ if err := json.Unmarshal(content, &websitesArray); err != nil {
+ return nil, err
+ }
+ }
+ for index, wafWebsite := range websitesArray {
+ if wafWebsite.Key == website.Alias {
+ wafSite := request.WafWebsite{
+ Key: website.Alias,
+ Domains: wafWebsite.Domains,
+ Host: wafWebsite.Host,
+ }
+ for _, domain := range domainModels {
+ wafSite.Domains = append(wafSite.Domains, domain.Domain)
+ wafSite.Host = append(wafSite.Host, domain.Domain+":"+strconv.Itoa(domain.Port))
+ }
+ if len(wafSite.Host) == 0 {
+ wafSite.Host = []string{}
+ }
+ websitesArray[index] = wafSite
+ break
+ }
+ }
+ websitesContent, err := json.Marshal(websitesArray)
+ if err != nil {
+ return nil, err
+ }
+ if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
+ return nil, err
+ }
+ }
+
+ if err = addListenAndServerName(website, domainModels); err != nil {
+ return nil, err
+ }
+
+ return domainModels, websiteDomainRepo.BatchCreate(context.TODO(), domainModels)
+}
+
+func (w WebsiteService) GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) {
+ return websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteId))
+}
+
+func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
+ webSiteDomain, err := websiteDomainRepo.GetFirst(repo.WithByID(domainId))
+ if err != nil {
+ return err
+ }
+
+ if websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID)); len(websiteDomains) == 1 {
+ return fmt.Errorf("can not delete last domain")
+ }
+ website, err := websiteRepo.GetFirst(repo.WithByID(webSiteDomain.WebsiteID))
+ if err != nil {
+ return err
+ }
+ var ports []int
+ if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithPort(webSiteDomain.Port)); len(oldDomains) == 1 {
+ ports = append(ports, webSiteDomain.Port)
+ }
+
+ var domains []string
+ if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithDomain(webSiteDomain.Domain)); len(oldDomains) == 1 {
+ domains = append(domains, webSiteDomain.Domain)
+ }
+
+ if len(ports) > 0 || len(domains) > 0 {
+ stringBinds := make([]string, len(ports))
+ for i := 0; i < len(ports); i++ {
+ stringBinds[i] = strconv.Itoa(ports[i])
+ }
+ if err := deleteListenAndServerName(website, stringBinds, domains); err != nil {
+ return err
+ }
+ }
+
+ nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
+ if err != nil {
+ return err
+ }
+ wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data")
+ fileOp := files.NewFileOp()
+ if fileOp.Stat(wafDataPath) {
+ websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json")
+ content, err := fileOp.GetContent(websitesConfigPath)
+ if err != nil {
+ return err
+ }
+ var websitesArray []request.WafWebsite
+ var newWebsitesArray []request.WafWebsite
+ if content != nil {
+ if err := json.Unmarshal(content, &websitesArray); err != nil {
+ return err
+ }
+ }
+ for _, wafWebsite := range websitesArray {
+ if wafWebsite.Key == website.Alias {
+ wafSite := wafWebsite
+ oldDomains := wafSite.Domains
+ var newDomains []string
+ for _, domain := range oldDomains {
+ if domain == webSiteDomain.Domain {
+ continue
+ }
+ newDomains = append(newDomains, domain)
+ }
+ wafSite.Domains = newDomains
+ oldHostArray := wafSite.Host
+ var newHostArray []string
+ for _, host := range oldHostArray {
+ if host == webSiteDomain.Domain+":"+strconv.Itoa(webSiteDomain.Port) {
+ continue
+ }
+ newHostArray = append(newHostArray, host)
+ }
+ wafSite.Host = newHostArray
+ if len(wafSite.Host) == 0 {
+ wafSite.Host = []string{}
+ }
+ newWebsitesArray = append(newWebsitesArray, wafSite)
+ } else {
+ newWebsitesArray = append(newWebsitesArray, wafWebsite)
+ }
+ }
+ websitesContent, err := json.Marshal(newWebsitesArray)
+ if err != nil {
+ return err
+ }
+ if err = fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil {
+ return err
+ }
+ }
+
+ return websiteDomainRepo.DeleteBy(context.TODO(), repo.WithByID(domainId))
+}
diff --git a/agent/app/service/website_lb.go b/agent/app/service/website_lb.go
new file mode 100644
index 000000000000..9094d5961e93
--- /dev/null
+++ b/agent/app/service/website_lb.go
@@ -0,0 +1,229 @@
+package service
+
+import (
+ "bytes"
+ "fmt"
+ "github.com/1Panel-dev/1Panel/agent/app/dto"
+ "github.com/1Panel-dev/1Panel/agent/app/dto/request"
+ "github.com/1Panel-dev/1Panel/agent/app/repo"
+ "github.com/1Panel-dev/1Panel/agent/buserr"
+ "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
+ "github.com/1Panel-dev/1Panel/agent/constant"
+ "github.com/1Panel-dev/1Panel/agent/utils/files"
+ "github.com/1Panel-dev/1Panel/agent/utils/nginx"
+ "github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
+ "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
+ "os"
+ "path"
+ "strings"
+)
+
+func (w WebsiteService) GetLoadBalances(id uint) ([]dto.NginxUpstream, error) {
+ website, err := websiteRepo.GetFirst(repo.WithByID(id))
+ if err != nil {
+ return nil, err
+ }
+ includeDir := GetSitePath(website, SiteUpstreamDir)
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(includeDir) {
+ return nil, nil
+ }
+ entries, err := os.ReadDir(includeDir)
+ if err != nil {
+ return nil, err
+ }
+ var res []dto.NginxUpstream
+ for _, entry := range entries {
+ if entry.IsDir() {
+ continue
+ }
+ name := entry.Name()
+ if !strings.HasSuffix(name, ".conf") {
+ continue
+ }
+ upstreamName := strings.TrimSuffix(name, ".conf")
+ upstream := dto.NginxUpstream{
+ Name: upstreamName,
+ }
+ upstreamPath := path.Join(includeDir, name)
+ content, err := fileOp.GetContent(upstreamPath)
+ if err != nil {
+ return nil, err
+ }
+ upstream.Content = string(content)
+ nginxParser, err := parser.NewParser(upstreamPath)
+ if err != nil {
+ return nil, err
+ }
+ config, err := nginxParser.Parse()
+ if err != nil {
+ return nil, err
+ }
+ upstreams := config.FindUpstreams()
+ for _, up := range upstreams {
+ if up.UpstreamName == upstreamName {
+ directives := up.GetDirectives()
+ for _, d := range directives {
+ dName := d.GetName()
+ if _, ok := dto.LBAlgorithms[dName]; ok {
+ upstream.Algorithm = dName
+ }
+ }
+ upstream.Servers = getNginxUpstreamServers(up.UpstreamServers)
+ }
+ }
+ res = append(res, upstream)
+ }
+ return res, nil
+}
+
+func (w WebsiteService) CreateLoadBalance(req request.WebsiteLBCreate) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ includeDir := GetSitePath(website, SiteUpstreamDir)
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(includeDir) {
+ _ = fileOp.CreateDir(includeDir, constant.DirPerm)
+ }
+ filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
+ if fileOp.Stat(filePath) {
+ return buserr.New("ErrNameIsExist")
+ }
+ config, err := parser.NewStringParser(string(nginx_conf.Upstream)).Parse()
+ if err != nil {
+ return err
+ }
+ config.Block = &components.Block{}
+ config.FilePath = filePath
+ upstream := components.Upstream{
+ UpstreamName: req.Name,
+ }
+ if req.Algorithm != "default" {
+ upstream.UpdateDirective(req.Algorithm, []string{})
+ }
+ upstream.UpstreamServers = parseUpstreamServers(req.Servers)
+ config.Block.Directives = append(config.Block.Directives, &upstream)
+
+ defer func() {
+ if err != nil {
+ _ = fileOp.DeleteFile(filePath)
+ }
+ }()
+
+ if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
+ return buserr.WithErr("ErrUpdateBuWebsite", err)
+ }
+ nginxInclude := fmt.Sprintf("/www/sites/%s/upstream/*.conf", website.Alias)
+ if err = updateNginxConfig("", []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (w WebsiteService) UpdateLoadBalance(req request.WebsiteLBUpdate) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
+ if err != nil {
+ return err
+ }
+ includeDir := GetSitePath(website, SiteUpstreamDir)
+ fileOp := files.NewFileOp()
+ filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
+ if !fileOp.Stat(filePath) {
+ return nil
+ }
+ oldContent, err := fileOp.GetContent(filePath)
+ if err != nil {
+ return err
+ }
+ parser, err := parser.NewParser(filePath)
+ if err != nil {
+ return err
+ }
+ config, err := parser.Parse()
+ if err != nil {
+ return err
+ }
+ upstreams := config.FindUpstreams()
+ for _, up := range upstreams {
+ if up.UpstreamName == req.Name {
+ directives := up.GetDirectives()
+ for _, d := range directives {
+ dName := d.GetName()
+ if _, ok := dto.LBAlgorithms[dName]; ok {
+ up.RemoveDirective(dName, nil)
+ }
+ }
+ if req.Algorithm != "default" {
+ up.UpdateDirective(req.Algorithm, []string{})
+ }
+ up.UpstreamServers = parseUpstreamServers(req.Servers)
+ }
+ }
+ if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
+ return buserr.WithErr("ErrUpdateBuWebsite", err)
+ }
+ return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName)
+}
+
+func (w WebsiteService) DeleteLoadBalance(req request.WebsiteLBDelete) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
+ if err != nil {
+ return err
+ }
+ proxies, _ := w.GetProxies(website.ID)
+ if len(proxies) > 0 {
+ for _, proxy := range proxies {
+ if strings.HasSuffix(proxy.ProxyPass, fmt.Sprintf("://%s", req.Name)) {
+ return buserr.New("ErrProxyIsUsed")
+ }
+ }
+ }
+
+ includeDir := GetSitePath(website, SiteUpstreamDir)
+ fileOp := files.NewFileOp()
+ filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
+ if !fileOp.Stat(filePath) {
+ return nil
+ }
+ if err = fileOp.DeleteFile(filePath); err != nil {
+ return err
+ }
+ return opNginx(nginxInstall.ContainerName, constant.NginxReload)
+}
+
+func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
+ if err != nil {
+ return err
+ }
+ includeDir := GetSitePath(website, SiteUpstreamDir)
+ filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name))
+ fileOp := files.NewFileOp()
+ oldContent, err := fileOp.GetContent(filePath)
+ if err != nil {
+ return err
+ }
+ if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ _ = fileOp.WriteFile(filePath, bytes.NewReader(oldContent), constant.DirPerm)
+ }
+ }()
+ return opNginx(nginxInstall.ContainerName, constant.NginxReload)
+}
diff --git a/agent/app/service/website_proxy.go b/agent/app/service/website_proxy.go
new file mode 100644
index 000000000000..7ee6f6e25aef
--- /dev/null
+++ b/agent/app/service/website_proxy.go
@@ -0,0 +1,408 @@
+package service
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "github.com/1Panel-dev/1Panel/agent/app/dto"
+ "github.com/1Panel-dev/1Panel/agent/app/dto/request"
+ "github.com/1Panel-dev/1Panel/agent/app/dto/response"
+ "github.com/1Panel-dev/1Panel/agent/app/model"
+ "github.com/1Panel-dev/1Panel/agent/app/repo"
+ "github.com/1Panel-dev/1Panel/agent/buserr"
+ "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
+ "github.com/1Panel-dev/1Panel/agent/constant"
+ "github.com/1Panel-dev/1Panel/agent/utils/files"
+ "github.com/1Panel-dev/1Panel/agent/utils/nginx"
+ "github.com/1Panel-dev/1Panel/agent/utils/nginx/components"
+ "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser"
+ "github.com/1Panel-dev/1Panel/agent/utils/re"
+ "path"
+ "strconv"
+ "strings"
+)
+
+func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) {
+ var (
+ website model.Website
+ par *parser.Parser
+ oldContent []byte
+ )
+
+ website, err = websiteRepo.GetFirst(repo.WithByID(req.ID))
+ if err != nil {
+ return
+ }
+ fileOp := files.NewFileOp()
+ includeDir := GetSitePath(website, SiteProxyDir)
+ if !fileOp.Stat(includeDir) {
+ _ = fileOp.CreateDir(includeDir, constant.DirPerm)
+ }
+ fileName := fmt.Sprintf("%s.conf", req.Name)
+ includePath := path.Join(includeDir, fileName)
+ backName := fmt.Sprintf("%s.bak", req.Name)
+ backPath := path.Join(includeDir, backName)
+
+ if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) {
+ err = buserr.New("ErrNameIsExist")
+ return
+ }
+
+ defer func() {
+ if err != nil {
+ switch req.Operate {
+ case "create":
+ _ = fileOp.DeleteFile(includePath)
+ case "edit":
+ _ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), constant.DirPerm)
+ }
+ }
+ }()
+
+ var config *components.Config
+
+ switch req.Operate {
+ case "create":
+ config, err = parser.NewStringParser(string(nginx_conf.GetWebsiteFile("proxy.conf"))).Parse()
+ if err != nil {
+ return
+ }
+ case "edit":
+ par, err = parser.NewParser(includePath)
+ if err != nil {
+ return
+ }
+ config, err = par.Parse()
+ if err != nil {
+ return
+ }
+ oldContent, err = fileOp.GetContent(includePath)
+ if err != nil {
+ return
+ }
+ case "delete":
+ _ = fileOp.DeleteFile(includePath)
+ _ = fileOp.DeleteFile(backPath)
+ return updateNginxConfig(constant.NginxScopeServer, nil, &website)
+ case "disable":
+ _ = fileOp.Rename(includePath, backPath)
+ return updateNginxConfig(constant.NginxScopeServer, nil, &website)
+ case "enable":
+ _ = fileOp.Rename(backPath, includePath)
+ return updateNginxConfig(constant.NginxScopeServer, nil, &website)
+ }
+
+ config.FilePath = includePath
+ directives := config.Directives
+
+ var location *components.Location
+ for _, directive := range directives {
+ if loc, ok := directive.(*components.Location); ok {
+ location = loc
+ break
+ }
+ }
+ if location == nil {
+ err = errors.New("invalid proxy config, no location found")
+ return
+ }
+ location.UpdateDirective("proxy_pass", []string{req.ProxyPass})
+ location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost})
+ location.ChangePath(req.Modifier, req.Match)
+ // Server Cache Settings
+ if req.Cache {
+ if err = openProxyCache(website); err != nil {
+ return
+ }
+ location.AddServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias), req.ServerCacheTime, req.ServerCacheUnit)
+ } else {
+ location.RemoveServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias))
+ }
+ // Browser Cache Settings
+ if req.CacheTime != 0 {
+ location.AddBrowserCache(req.CacheTime, req.CacheUnit)
+ } else {
+ location.RemoveBrowserCache()
+ }
+ // Content Replace Settings
+ if len(req.Replaces) > 0 {
+ location.AddSubFilter(req.Replaces)
+ } else {
+ location.RemoveSubFilter()
+ }
+ // SSL Settings
+ if req.SNI {
+ location.UpdateDirective("proxy_ssl_server_name", []string{"on"})
+ if req.ProxySSLName != "" {
+ location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName})
+ }
+ } else {
+ location.UpdateDirective("proxy_ssl_server_name", []string{"off"})
+ }
+ // CORS Settings
+ if req.Cors {
+ location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"})
+ if req.AllowMethods != "" {
+ location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"})
+ } else {
+ location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"})
+ }
+ if req.AllowHeaders != "" {
+ location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"})
+ } else {
+ location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"})
+ }
+ if req.AllowCredentials {
+ location.UpdateDirective("add_header", []string{"Access-Control-Allow-Credentials", "true", "always"})
+ } else {
+ location.RemoveDirective("add_header", []string{"Access-Control-Allow-Credentials"})
+ }
+ if req.Preflight {
+ location.AddCorsOption()
+ } else {
+ location.RemoveCorsOption()
+ }
+ } else {
+ location.RemoveDirective("add_header", []string{"Access-Control-Allow-Origin"})
+ location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"})
+ location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"})
+ location.RemoveDirective("add_header", []string{"Access-Control-Allow-Credentials"})
+ location.RemoveDirectiveByFullParams("if", []string{"(", "$request_method", "=", "'OPTIONS'", ")"})
+ }
+ if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
+ return buserr.WithErr("ErrUpdateBuWebsite", err)
+ }
+ nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
+ return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website)
+}
+
+func (w WebsiteService) UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error) {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return
+ }
+ cacheDir := GetSitePath(website, SiteCacheDir)
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(cacheDir) {
+ _ = fileOp.CreateDir(cacheDir, constant.DirPerm)
+ }
+ if req.Open {
+ proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:%d%s max_size=%d%s inactive=%d%s", website.Alias, website.Alias, req.ShareCache, req.ShareCacheUnit, req.CacheLimit, req.CacheLimitUnit, req.CacheExpire, req.CacheExpireUnit)
+ return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website)
+ }
+ return deleteNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path"}}, &website)
+}
+
+func (w WebsiteService) GetProxyCache(id uint) (res response.NginxProxyCache, err error) {
+ var (
+ website model.Website
+ )
+ website, err = websiteRepo.GetFirst(repo.WithByID(id))
+ if err != nil {
+ return
+ }
+
+ parser, err := parser.NewParser(GetSitePath(website, SiteConf))
+ if err != nil {
+ return
+ }
+ config, err := parser.Parse()
+ if err != nil {
+ return
+ }
+ var params []string
+ for _, d := range config.GetDirectives() {
+ if d.GetName() == "proxy_cache_path" {
+ params = d.GetParameters()
+ }
+ }
+ if len(params) == 0 {
+ return
+ }
+ for _, param := range params {
+ if re.GetRegex(re.ProxyCacheZonePattern).MatchString(param) {
+ matches := re.GetRegex(re.ProxyCacheZonePattern).FindStringSubmatch(param)
+ if len(matches) > 0 {
+ res.ShareCache, _ = strconv.Atoi(matches[1])
+ res.ShareCacheUnit = matches[2]
+ }
+ }
+
+ if re.GetRegex(re.ProxyCacheMaxSizeValidationPattern).MatchString(param) {
+ matches := re.GetRegex(re.ProxyCacheMaxSizePattern).FindStringSubmatch(param)
+ if len(matches) > 0 {
+ res.CacheLimit, _ = strconv.ParseFloat(matches[1], 64)
+ res.CacheLimitUnit = matches[2]
+ }
+ }
+ if re.GetRegex(re.ProxyCacheInactivePattern).MatchString(param) {
+ matches := re.GetRegex(re.ProxyCacheInactivePattern).FindStringSubmatch(param)
+ if len(matches) > 0 {
+ res.CacheExpire, _ = strconv.Atoi(matches[1])
+ res.CacheExpireUnit = matches[2]
+ }
+ }
+ }
+ res.Open = true
+ return
+}
+
+func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) {
+ var (
+ website model.Website
+ fileList response.FileInfo
+ )
+ website, err = websiteRepo.GetFirst(repo.WithByID(id))
+ if err != nil {
+ return
+ }
+ includeDir := GetSitePath(website, SiteProxyDir)
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(includeDir) {
+ return
+ }
+ fileList, err = NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: includeDir, Expand: true, Page: 1, PageSize: 100}})
+ if len(fileList.Items) == 0 {
+ return
+ }
+ var (
+ content []byte
+ config *components.Config
+ )
+ for _, configFile := range fileList.Items {
+ proxyConfig := request.WebsiteProxyConfig{
+ ID: website.ID,
+ }
+ parts := strings.Split(configFile.Name, ".")
+ proxyConfig.Name = parts[0]
+ if parts[1] == "conf" {
+ proxyConfig.Enable = true
+ } else {
+ proxyConfig.Enable = false
+ }
+ proxyConfig.FilePath = configFile.Path
+ content, err = fileOp.GetContent(configFile.Path)
+ if err != nil {
+ return
+ }
+ proxyConfig.Content = string(content)
+ config, err = parser.NewStringParser(string(content)).Parse()
+ if err != nil {
+ return nil, err
+ }
+ directives := config.GetDirectives()
+
+ var location *components.Location
+ for _, directive := range directives {
+ if loc, ok := directive.(*components.Location); ok {
+ location = loc
+ break
+ }
+ }
+ if location == nil {
+ err = errors.New("invalid proxy config, no location found")
+ return
+ }
+ proxyConfig.ProxyPass = location.ProxyPass
+ proxyConfig.Cache = location.Cache
+ if location.CacheTime > 0 {
+ proxyConfig.CacheTime = location.CacheTime
+ proxyConfig.CacheUnit = location.CacheUint
+ }
+ if location.ServerCacheTime > 0 {
+ proxyConfig.ServerCacheTime = location.ServerCacheTime
+ proxyConfig.ServerCacheUnit = location.ServerCacheUint
+ }
+ proxyConfig.Match = location.Match
+ proxyConfig.Modifier = location.Modifier
+ proxyConfig.ProxyHost = location.Host
+ proxyConfig.Replaces = location.Replaces
+ for _, directive := range location.Directives {
+ if directive.GetName() == "proxy_ssl_server_name" {
+ proxyConfig.SNI = directive.GetParameters()[0] == "on"
+ }
+ if directive.GetName() == "proxy_ssl_name" && len(directive.GetParameters()) > 0 {
+ proxyConfig.ProxySSLName = directive.GetParameters()[0]
+ }
+ }
+ proxyConfig.Cors = location.Cors
+ proxyConfig.AllowCredentials = location.AllowCredentials
+ proxyConfig.AllowHeaders = location.AllowHeaders
+ proxyConfig.AllowOrigins = location.AllowOrigins
+ proxyConfig.AllowMethods = location.AllowMethods
+ proxyConfig.Preflight = location.Preflight
+ res = append(res, proxyConfig)
+ }
+ return
+}
+
+func (w WebsiteService) UpdateProxyFile(req request.NginxProxyUpdate) (err error) {
+ var (
+ website model.Website
+ oldRewriteContent []byte
+ )
+ website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ absolutePath := fmt.Sprintf("%s/%s.conf", GetSitePath(website, SiteProxyDir), req.Name)
+ fileOp := files.NewFileOp()
+ oldRewriteContent, err = fileOp.GetContent(absolutePath)
+ if err != nil {
+ return err
+ }
+ if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm)
+ }
+ }()
+ return updateNginxConfig(constant.NginxScopeServer, nil, &website)
+}
+
+func (w WebsiteService) ClearProxyCache(req request.NginxCommonReq) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ cacheDir := GetSitePath(website, SiteCacheDir)
+ fileOp := files.NewFileOp()
+ if fileOp.Stat(cacheDir) {
+ if err = fileOp.CleanDir(cacheDir); err != nil {
+ return err
+ }
+ }
+ nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
+ if err != nil {
+ return err
+ }
+ if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (w WebsiteService) DeleteProxy(req request.WebsiteProxyDel) (err error) {
+ fileOp := files.NewFileOp()
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.ID))
+ if err != nil {
+ return
+ }
+ nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
+ if err != nil {
+ return
+ }
+ includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy")
+ if !fileOp.Stat(includeDir) {
+ _ = fileOp.CreateDir(includeDir, 0755)
+ }
+ fileName := fmt.Sprintf("%s.conf", req.Name)
+ includePath := path.Join(includeDir, fileName)
+ backName := fmt.Sprintf("%s.bak", req.Name)
+ backPath := path.Join(includeDir, backName)
+ _ = fileOp.DeleteFile(includePath)
+ _ = fileOp.DeleteFile(backPath)
+ return updateNginxConfig(constant.NginxScopeServer, nil, &website)
+}
diff --git a/agent/app/service/website_rewrite.go b/agent/app/service/website_rewrite.go
new file mode 100644
index 000000000000..04b22e1af4d1
--- /dev/null
+++ b/agent/app/service/website_rewrite.go
@@ -0,0 +1,124 @@
+package service
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "github.com/1Panel-dev/1Panel/agent/app/dto"
+ "github.com/1Panel-dev/1Panel/agent/app/dto/request"
+ "github.com/1Panel-dev/1Panel/agent/app/dto/response"
+ "github.com/1Panel-dev/1Panel/agent/app/repo"
+ "github.com/1Panel-dev/1Panel/agent/buserr"
+ "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
+ "github.com/1Panel-dev/1Panel/agent/constant"
+ "github.com/1Panel-dev/1Panel/agent/utils/files"
+ "os"
+ "path"
+ "strings"
+)
+
+func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return err
+ }
+ includePath := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias)
+ absolutePath := GetSitePath(website, SiteReWritePath)
+ fileOp := files.NewFileOp()
+ var oldRewriteContent []byte
+ if !fileOp.Stat(path.Dir(absolutePath)) {
+ if err := fileOp.CreateDir(path.Dir(absolutePath), constant.DirPerm); err != nil {
+ return err
+ }
+ }
+ if !fileOp.Stat(absolutePath) {
+ if err := fileOp.CreateFile(absolutePath); err != nil {
+ return err
+ }
+ } else {
+ oldRewriteContent, err = fileOp.GetContent(absolutePath)
+ if err != nil {
+ return err
+ }
+ }
+ if err := fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil {
+ return err
+ }
+
+ if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{includePath}}}, &website); err != nil {
+ _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm)
+ return err
+ }
+ website.Rewrite = req.Name
+ return websiteRepo.Save(context.Background(), &website)
+}
+
+func (w WebsiteService) GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) {
+ website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID))
+ if err != nil {
+ return nil, err
+ }
+ var contentByte []byte
+ if req.Name == "current" {
+ rewriteConfPath := GetSitePath(website, SiteReWritePath)
+ fileOp := files.NewFileOp()
+ if fileOp.Stat(rewriteConfPath) {
+ contentByte, err = fileOp.GetContent(rewriteConfPath)
+ if err != nil {
+ return nil, err
+ }
+ }
+ } else {
+ rewriteFile := fmt.Sprintf("rewrite/%s.conf", strings.ToLower(req.Name))
+ contentByte, _ = nginx_conf.Rewrites.ReadFile(rewriteFile)
+ if contentByte == nil {
+ customRewriteDir := GetOpenrestyDir(DefaultRewriteDir)
+ customRewriteFile := path.Join(customRewriteDir, fmt.Sprintf("%s.conf", strings.ToLower(req.Name)))
+ contentByte, err = files.NewFileOp().GetContent(customRewriteFile)
+ }
+ }
+ return &response.NginxRewriteRes{
+ Content: string(contentByte),
+ }, err
+}
+
+func (w WebsiteService) OperateCustomRewrite(req request.CustomRewriteOperate) error {
+ rewriteDir := GetOpenrestyDir(DefaultRewriteDir)
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(rewriteDir) {
+ if err := fileOp.CreateDir(rewriteDir, constant.DirPerm); err != nil {
+ return err
+ }
+ }
+ rewriteFile := path.Join(rewriteDir, fmt.Sprintf("%s.conf", req.Name))
+ switch req.Operate {
+ case "create":
+ if fileOp.Stat(rewriteFile) {
+ return buserr.New("ErrNameIsExist")
+ }
+ return fileOp.WriteFile(rewriteFile, strings.NewReader(req.Content), constant.DirPerm)
+ case "delete":
+ return fileOp.DeleteFile(rewriteFile)
+ }
+ return nil
+}
+
+func (w WebsiteService) ListCustomRewrite() ([]string, error) {
+ rewriteDir := GetOpenrestyDir(DefaultRewriteDir)
+ fileOp := files.NewFileOp()
+ if !fileOp.Stat(rewriteDir) {
+ return nil, nil
+ }
+ entries, err := os.ReadDir(rewriteDir)
+ if err != nil {
+ return nil, err
+ }
+ var res []string
+ for _, entry := range entries {
+ if entry.IsDir() {
+ continue
+ }
+ res = append(res, strings.TrimSuffix(entry.Name(), ".conf"))
+ }
+ return res, nil
+}
diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go
index b92c18e353e5..bf0c9803413c 100644
--- a/agent/app/service/website_utils.go
+++ b/agent/app/service/website_utils.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "github.com/1Panel-dev/1Panel/agent/utils/xpack"
"log"
"net"
"os"
@@ -16,8 +17,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/repo"
- "github.com/1Panel-dev/1Panel/agent/utils/xpack"
-
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/global"
@@ -144,11 +143,13 @@ func createWebsiteFolder(website *model.Website, runtime *model.Runtime) error {
if err := fileOp.CreateFile(path.Join(siteFolder, "log", "error.log")); err != nil {
return err
}
- if err := fileOp.CreateDir(path.Join(siteFolder, "index"), constant.DirPerm); err != nil {
- return err
- }
- if err := fileOp.CreateDir(path.Join(siteFolder, "ssl"), constant.DirPerm); err != nil {
- return err
+ if website.Type != constant.Stream {
+ if err := fileOp.CreateDir(path.Join(siteFolder, "index"), constant.DirPerm); err != nil {
+ return err
+ }
+ if err := fileOp.CreateDir(path.Join(siteFolder, "ssl"), constant.DirPerm); err != nil {
+ return err
+ }
}
if website.Type == constant.Runtime {
if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceLocal {
@@ -175,7 +176,7 @@ func createWebsiteFolder(website *model.Website, runtime *model.Runtime) error {
return nil
}
-func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime) error {
+func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime, streamConfig request.StreamConfig) error {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
@@ -183,72 +184,83 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
if err = createWebsiteFolder(website, runtime); err != nil {
return err
}
- configPath := GetSitePath(*website, SiteConf)
- nginxContent := nginx_conf.GetWebsiteFile("website_default.conf")
- config, err := parser.NewStringParser(string(nginxContent)).Parse()
- if err != nil {
- return err
- }
- servers := config.FindServers()
- if len(servers) == 0 {
- return errors.New("nginx config is not valid")
- }
- server := servers[0]
- server.DeleteListen("80")
- var serverNames []string
- for _, domain := range domains {
- serverNames = append(serverNames, domain.Domain)
- setListen(server, strconv.Itoa(domain.Port), website.IPV6, false, website.DefaultServer, false)
- }
- server.UpdateServerName(serverNames)
-
- siteFolder := path.Join("/www", "sites", website.Alias)
- server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log"), "main"})
- server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")})
-
- rootIndex := path.Join("/www/sites", website.Alias, "index")
- switch website.Type {
- case constant.Deployment:
- proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort)
- server.UpdateRootProxy([]string{proxy})
- case constant.Static:
- server.UpdateRoot(rootIndex)
- server.UpdateDirective("error_page", []string{"404", "/404.html"})
- case constant.Proxy:
- nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
- server.UpdateDirective("include", []string{nginxInclude})
- server.UpdateRoot(rootIndex)
- case constant.Runtime:
- switch runtime.Type {
- case constant.RuntimePHP:
- server.UpdateDirective("error_page", []string{"404", "/404.html"})
- if runtime.Resource == constant.ResourceLocal {
- server.UpdateRoot(rootIndex)
- localPath := path.Join(rootIndex, "index.php")
- server.UpdatePHPProxy([]string{website.Proxy}, localPath)
- } else {
- server.UpdateRoot(rootIndex)
- server.UpdatePHPProxy([]string{website.Proxy}, "")
+ var (
+ configPath string
+ config *components.Config
+ )
+ if website.Type == constant.Stream {
+ nginxContent := nginx_conf.GetWebsiteFile("stream_default.conf")
+ config, err = parser.NewStringParser(string(nginxContent)).Parse()
+ if err != nil {
+ return err
+ }
+ servers := config.FindServers()
+ if len(servers) == 0 {
+ return errors.New("nginx config is not valid")
+ }
+ server := servers[0]
+ ports := strings.Split(streamConfig.StreamPorts, ",")
+ for _, port := range ports {
+ server.UpdateListen(port, false)
+ if website.IPV6 {
+ server.UpdateListen("[::]:"+port, false)
}
- case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
- server.UpdateRootProxy([]string{fmt.Sprintf("http://%s", website.Proxy)})
}
- case constant.Subsite:
- parentWebsite, err := websiteRepo.GetFirst(repo.WithByID(website.ParentWebsiteID))
+ siteFolder := path.Join("/www", "sites", website.Alias)
+ server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log"), "streamlog"})
+ server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")})
+ server.UpdateDirective("proxy_pass", []string{website.Alias})
+
+ upstream := components.Upstream{
+ UpstreamName: website.Alias,
+ }
+ if streamConfig.Algorithm != "default" {
+ upstream.UpdateDirective(streamConfig.Algorithm, []string{})
+ }
+ upstream.UpstreamServers = parseUpstreamServers(streamConfig.Servers)
+ config.Block.Directives = append(config.Block.Directives, &upstream)
+ configPath = GetSitePath(*website, StreamConf)
+ } else {
+ configPath = GetSitePath(*website, SiteConf)
+ nginxContent := nginx_conf.GetWebsiteFile("website_default.conf")
+ config, err = parser.NewStringParser(string(nginxContent)).Parse()
if err != nil {
return err
}
- website.Proxy = parentWebsite.Proxy
- rootIndex = path.Join("/www/sites", parentWebsite.Alias, "index", website.SiteDir)
- server.UpdateDirective("error_page", []string{"404", "/404.html"})
- if parentWebsite.Type == constant.Runtime {
- parentRuntime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(parentWebsite.RuntimeID))
- if err != nil {
- return err
- }
- website.RuntimeID = parentRuntime.ID
- if parentRuntime.Type == constant.RuntimePHP {
- if parentRuntime.Resource == constant.ResourceLocal {
+ servers := config.FindServers()
+ if len(servers) == 0 {
+ return errors.New("nginx config is not valid")
+ }
+ server := servers[0]
+ server.DeleteListen("80")
+ var serverNames []string
+ for _, domain := range domains {
+ serverNames = append(serverNames, domain.Domain)
+ setListen(server, strconv.Itoa(domain.Port), website.IPV6, false, website.DefaultServer, false)
+ }
+ server.UpdateServerName(serverNames)
+
+ siteFolder := path.Join("/www", "sites", website.Alias)
+ server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log"), "main"})
+ server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")})
+
+ rootIndex := path.Join("/www/sites", website.Alias, "index")
+ switch website.Type {
+ case constant.Deployment:
+ proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort)
+ server.UpdateRootProxy([]string{proxy})
+ case constant.Static:
+ server.UpdateRoot(rootIndex)
+ server.UpdateDirective("error_page", []string{"404", "/404.html"})
+ case constant.Proxy:
+ nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias)
+ server.UpdateDirective("include", []string{nginxInclude})
+ server.UpdateRoot(rootIndex)
+ case constant.Runtime:
+ switch runtime.Type {
+ case constant.RuntimePHP:
+ server.UpdateDirective("error_page", []string{"404", "/404.html"})
+ if runtime.Resource == constant.ResourceLocal {
server.UpdateRoot(rootIndex)
localPath := path.Join(rootIndex, "index.php")
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
@@ -256,13 +268,39 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
server.UpdateRoot(rootIndex)
server.UpdatePHPProxy([]string{website.Proxy}, "")
}
+ case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
+ server.UpdateRootProxy([]string{fmt.Sprintf("http://%s", website.Proxy)})
+ }
+ case constant.Subsite:
+ parentWebsite, err := websiteRepo.GetFirst(repo.WithByID(website.ParentWebsiteID))
+ if err != nil {
+ return err
+ }
+ website.Proxy = parentWebsite.Proxy
+ rootIndex = path.Join("/www/sites", parentWebsite.Alias, "index", website.SiteDir)
+ server.UpdateDirective("error_page", []string{"404", "/404.html"})
+ if parentWebsite.Type == constant.Runtime {
+ parentRuntime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(parentWebsite.RuntimeID))
+ if err != nil {
+ return err
+ }
+ website.RuntimeID = parentRuntime.ID
+ if parentRuntime.Type == constant.RuntimePHP {
+ if parentRuntime.Resource == constant.ResourceLocal {
+ server.UpdateRoot(rootIndex)
+ localPath := path.Join(rootIndex, "index.php")
+ server.UpdatePHPProxy([]string{website.Proxy}, localPath)
+ } else {
+ server.UpdateRoot(rootIndex)
+ server.UpdatePHPProxy([]string{website.Proxy}, "")
+ }
+ }
+ }
+ if parentWebsite.Type == constant.Static {
+ server.UpdateRoot(rootIndex)
}
- }
- if parentWebsite.Type == constant.Static {
- server.UpdateRoot(rootIndex)
}
}
-
config.FilePath = configPath
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
return err
@@ -432,14 +470,20 @@ func createWafConfig(website *model.Website, domains []model.WebsiteDomain) erro
}
func delNginxConfig(website model.Website, force bool) error {
- configPath := GetSitePath(website, SiteConf)
fileOp := files.NewFileOp()
+ var (
+ configPath string
+ )
+ if website.Type == constant.Stream {
+ configPath = GetSitePath(website, StreamConf)
- if !fileOp.Stat(configPath) {
- return nil
+ } else {
+ configPath = GetSitePath(website, SiteConf)
}
- if err := fileOp.DeleteFile(configPath); err != nil {
- return err
+ if fileOp.Stat(configPath) {
+ if err := fileOp.DeleteFile(configPath); err != nil {
+ return err
+ }
}
sitePath := GteSiteDir(website.Alias)
if fileOp.Stat(sitePath) {
@@ -880,9 +924,16 @@ func deleteWebsiteFolder(website *model.Website) error {
if fileOp.Stat(siteFolder) {
_ = fileOp.DeleteDir(siteFolder)
}
- nginxFilePath := GetSitePath(*website, SiteConf)
- if fileOp.Stat(nginxFilePath) {
- _ = fileOp.DeleteFile(nginxFilePath)
+ if website.Type == constant.Stream {
+ steamFilePath := GetSitePath(*website, StreamConf)
+ if fileOp.Stat(steamFilePath) {
+ _ = fileOp.DeleteFile(steamFilePath)
+ }
+ } else {
+ nginxFilePath := GetSitePath(*website, SiteConf)
+ if fileOp.Stat(nginxFilePath) {
+ _ = fileOp.DeleteFile(nginxFilePath)
+ }
}
return nil
}
@@ -1089,27 +1140,8 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultHTTPPort, default
addPorts = append(addPorts, port)
continue
}
- if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(port)); len(existPorts) == 0 {
- errMap := make(map[string]interface{})
- errMap["port"] = port
- appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
- if appInstall.ID > 0 {
- errMap["type"] = i18n.GetMsgByKey("TYPE_APP")
- errMap["name"] = appInstall.Name
- err = buserr.WithMap("ErrPortExist", errMap, nil)
- return
- }
- runtime, _ := runtimeRepo.GetFirst(context.Background(), runtimeRepo.WithPort(port))
- if runtime != nil {
- errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME")
- errMap["name"] = runtime.Name
- err = buserr.WithMap("ErrPortExist", errMap, nil)
- return
- }
- if port != defaultHTTPsPort && common.ScanPort(port) {
- err = buserr.WithDetail("ErrPortInUsed", port, nil)
- return
- }
+ if err = checkWebsitePort(defaultHTTPsPort, port, ""); err != nil {
+ return
}
if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteID), websiteDomainRepo.WithPort(port)); len(existPorts) == 0 {
addPorts = append(addPorts, port)
@@ -1119,6 +1151,43 @@ func getWebsiteDomains(domains []request.WebsiteDomain, defaultHTTPPort, default
return
}
+func checkWebsitePort(defaultHTTPsPort, port int, websiteType string) error {
+ if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(port)); len(existPorts) > 0 {
+ return nil
+ }
+ if websiteType == constant.Stream {
+ websites, _ := websiteRepo.List(websiteRepo.WithType(constant.Stream))
+ for _, website := range websites {
+ ports := strings.Split(website.StreamPorts, ",")
+ for _, p := range ports {
+ pInt, _ := strconv.Atoi(p)
+ if pInt == port {
+ return nil
+ }
+ }
+ }
+ }
+
+ errMap := make(map[string]interface{})
+ errMap["port"] = port
+ appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port))
+ if appInstall.ID > 0 {
+ errMap["type"] = i18n.GetMsgByKey("TYPE_APP")
+ errMap["name"] = appInstall.Name
+ return buserr.WithMap("ErrPortExist", errMap, nil)
+ }
+ runtime, _ := runtimeRepo.GetFirst(context.Background(), runtimeRepo.WithPort(port))
+ if runtime != nil {
+ errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME")
+ errMap["name"] = runtime.Name
+ return buserr.WithMap("ErrPortExist", errMap, nil)
+ }
+ if port != defaultHTTPsPort && common.ScanPort(port) {
+ return buserr.WithDetail("ErrPortInUsed", port, nil)
+ }
+ return nil
+}
+
func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
if websiteSSL.PushDir {
fileOp := files.NewFileOp()
@@ -1304,12 +1373,23 @@ const (
SitePathAuthBasicDir = "SitePathAuthBasicDir"
SiteUpstreamDir = "SiteUpstreamDir"
SiteCorsPath = "SiteCorsPath"
+ StreamDir = "StreamDir"
+ StreamConf = "StreamConf"
)
+func GetWebsiteConfigPath(website model.Website) string {
+ if website.Type != constant.Stream {
+ return GetSitePath(website, SiteConf)
+ }
+ return GetSitePath(website, StreamConf)
+}
+
func GetSitePath(website model.Website, confType string) string {
switch confType {
case SiteConf:
return path.Join(GetWebSiteRootDir(), "conf.d", website.Alias+".conf")
+ case StreamConf:
+ return path.Join(GetWebSiteRootDir(), "stream.d", website.Alias+".conf")
case SiteAccessLog:
return path.Join(GteSiteDir(website.Alias), "log", "access.log")
case SiteErrorLog:
@@ -1346,6 +1426,8 @@ func GetOpenrestyDir(confType string) string {
return GetWebSiteRootDir()
case SiteConfDir:
return path.Join(GetWebSiteRootDir(), "conf.d")
+ case StreamDir:
+ return path.Join(GetWebSiteRootDir(), "steam.d")
case SitesRootDir:
return path.Join(GetWebSiteRootDir(), "sites")
case DefaultDir:
diff --git a/agent/cmd/server/nginx_conf/stream_default.conf b/agent/cmd/server/nginx_conf/stream_default.conf
new file mode 100644
index 000000000000..f2429517d0c5
--- /dev/null
+++ b/agent/cmd/server/nginx_conf/stream_default.conf
@@ -0,0 +1,5 @@
+server {
+ proxy_pass mysql_cluster;
+ access_log /www/sites/domain/log/access.log streamlog;
+ error_log /www/sites/domain/log/error.log;
+}
diff --git a/agent/constant/website.go b/agent/constant/website.go
index e4a3d059d238..2f601517691d 100644
--- a/agent/constant/website.go
+++ b/agent/constant/website.go
@@ -4,8 +4,9 @@ const (
WebRunning = "Running"
WebStopped = "Stopped"
- ProtocolHTTP = "HTTP"
- ProtocolHTTPS = "HTTPS"
+ ProtocolHTTP = "HTTP"
+ ProtocolHTTPS = "HTTPS"
+ ProtocolStream = "TCP/UDP"
NewApp = "new"
InstalledApp = "installed"
@@ -15,6 +16,7 @@ const (
Proxy = "proxy"
Runtime = "runtime"
Subsite = "subsite"
+ Stream = "stream"
SSLExisted = "existed"
SSLAuto = "auto"
diff --git a/agent/go.mod b/agent/go.mod
index e362e4b509f0..2f4f6130a3dc 100644
--- a/agent/go.mod
+++ b/agent/go.mod
@@ -42,7 +42,7 @@ require (
github.com/pkg/sftp v1.13.6
github.com/qiniu/go-sdk/v7 v7.21.1
github.com/robfig/cron/v3 v3.0.1
- github.com/shirou/gopsutil/v4 v4.25.7
+ github.com/shirou/gopsutil/v4 v4.25.11
github.com/sirupsen/logrus v1.9.3
github.com/spf13/afero v1.11.0
github.com/spf13/cobra v1.10.1
@@ -121,7 +121,7 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/ebitengine/purego v0.8.4 // indirect
+ github.com/ebitengine/purego v0.9.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
@@ -206,7 +206,7 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
- github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
+ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rs/xid v1.5.0 // indirect
@@ -221,8 +221,8 @@ require (
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.3 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
- github.com/tklauser/go-sysconf v0.3.15 // indirect
- github.com/tklauser/numcpus v0.10.0 // indirect
+ github.com/tklauser/go-sysconf v0.3.16 // indirect
+ github.com/tklauser/numcpus v0.11.0 // indirect
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
diff --git a/agent/go.sum b/agent/go.sum
index c2d8f3d978b5..a5c43a242741 100644
--- a/agent/go.sum
+++ b/agent/go.sum
@@ -310,6 +310,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
+github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -831,6 +833,8 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
@@ -896,6 +900,8 @@ github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
+github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY=
+github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -962,8 +968,12 @@ github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
+github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
+github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
+github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
+github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19 h1:ZCmSnT6CLGhfoQ2lPEhL4nsJstKDCw1F1RfN8/smTCU=
github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19/go.mod h1:SXTY+QvI+KTTKXQdg0zZ7nx0u94QWh8ZAwBQYsW9cqk=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go
index 17f95ac5f27e..a3a712413048 100644
--- a/agent/init/migration/migrate.go
+++ b/agent/init/migration/migrate.go
@@ -57,6 +57,7 @@ func InitAgentDB() {
migrations.AddGPUMonitor,
migrations.UpdateDatabaseMysql,
migrations.InitIptablesStatus,
+ migrations.UpdateWebsite,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go
index f1d192ff41d5..7cb2db373f61 100644
--- a/agent/init/migration/migrations/init.go
+++ b/agent/init/migration/migrations/init.go
@@ -775,3 +775,10 @@ var InitIptablesStatus = &gormigrate.Migration{
return nil
},
}
+
+var UpdateWebsite = &gormigrate.Migration{
+ ID: "20251203-update-website",
+ Migrate: func(tx *gorm.DB) error {
+ return tx.AutoMigrate(&model.Website{})
+ },
+}
diff --git a/agent/router/ro_website.go b/agent/router/ro_website.go
index bbd0f698c3df..876fc1edaea8 100644
--- a/agent/router/ro_website.go
+++ b/agent/router/ro_website.go
@@ -93,5 +93,6 @@ func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) {
websiteRouter.POST("/crosssite", baseApi.OperateCrossSiteAccess)
websiteRouter.POST("/exec/composer", baseApi.ExecComposer)
+ websiteRouter.POST("/stream/update", baseApi.UpdateStreamConfig)
}
}
diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts
index ed998d282943..63cc0e93bf5a 100644
--- a/frontend/src/api/interface/website.ts
+++ b/frontend/src/api/interface/website.ts
@@ -27,6 +27,7 @@ export namespace Website {
dbID: number;
dbType: string;
favorite: boolean;
+ streamPorts: string;
}
export interface WebsiteDTO extends Website {
@@ -37,7 +38,17 @@ export namespace Website {
runtimeName: string;
runtimeType: string;
openBaseDir: boolean;
+ algorithm: string;
+ servers: NginxUpstreamServer[];
}
+
+ export interface WebsiteStreamUpdate {
+ websiteID: number;
+ algorithm: string;
+ streamPorts?: string;
+ servers: NginxUpstreamServer[];
+ }
+
export interface WebsiteRes extends CommonModel {
protocol: string;
primaryDomain: string;
@@ -93,6 +104,10 @@ export namespace Website {
dbUser?: string;
dbHost?: string;
domains: SubDomain[];
+ streamPorts?: string;
+ name: string;
+ algorithm: string;
+ servers: NginxUpstreamServer[];
}
export interface WebSiteUpdateReq {
diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts
index 2949a2347fdb..353f0ecbf2cf 100644
--- a/frontend/src/api/modules/website.ts
+++ b/frontend/src/api/modules/website.ts
@@ -371,3 +371,7 @@ export const updateCorsConfig = (req: Website.CorsConfigReq) => {
export const batchSetGroup = (req: Website.BatchSetGroup) => {
return http.post(`/websites/batch/group`, req);
};
+
+export const updateWebsiteStream = (req: Website.WebsiteStreamUpdate) => {
+ return http.post(`/websites/stream/update`, req);
+};
diff --git a/frontend/src/global/mimetype.ts b/frontend/src/global/mimetype.ts
index 3f6b23fd7eba..40391478f74d 100644
--- a/frontend/src/global/mimetype.ts
+++ b/frontend/src/global/mimetype.ts
@@ -412,23 +412,31 @@ export const Actions = [
},
];
-export const getAlgorithms = () => [
- {
- label: i18n.global.t('commons.table.default'),
- value: 'default',
- placeHolder: i18n.global.t('website.defaultHelper'),
- },
- {
- label: i18n.global.t('website.ipHash'),
- value: 'ip_hash',
- placeHolder: i18n.global.t('website.ipHashHelper'),
- },
- {
- label: i18n.global.t('website.leastConn'),
- value: 'least_conn',
- placeHolder: i18n.global.t('website.leastConnHelper'),
- },
-];
+export const getAlgorithms = (type: string) => {
+ const baseAlgorithms = [
+ {
+ label: i18n.global.t('commons.table.default'),
+ value: 'default',
+ placeHolder: i18n.global.t('website.defaultHelper'),
+ },
+ {
+ label: i18n.global.t('website.ipHash'),
+ value: 'ip_hash',
+ placeHolder: i18n.global.t('website.ipHashHelper'),
+ },
+ {
+ label: i18n.global.t('website.leastConn'),
+ value: 'least_conn',
+ placeHolder: i18n.global.t('website.leastConnHelper'),
+ },
+ ];
+
+ if (type === 'stream') {
+ return baseAlgorithms.filter((algo) => algo.value !== 'ip_hash');
+ }
+
+ return baseAlgorithms;
+};
export const getStatusStrategy = () => [
{
@@ -462,4 +470,8 @@ export const getWebsiteTypes = () => [
label: i18n.global.t('website.subsite'),
value: 'subsite',
},
+ {
+ label: i18n.global.t('website.stream'),
+ value: 'stream',
+ },
];
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index e39aa1027ff4..f91b4fe0c584 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -2699,6 +2699,9 @@ const message = {
batchOpreate: 'Batch Operation',
batchOpreateHelper: 'Batch {0} websites, continue operation?',
+ stream: 'TCP/UDP Proxy',
+ streamPorts: 'Listening Ports',
+ streamPortsHelper: 'Separate with commas, e.g., 3306, 3307',
},
php: {
short_open_tag: 'Short tag support',
diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts
index c7f22f19e94d..626f9993576f 100644
--- a/frontend/src/lang/modules/es-es.ts
+++ b/frontend/src/lang/modules/es-es.ts
@@ -2683,6 +2683,9 @@ const message = {
batchOpreate: 'Operación en Lote',
batchOpreateHelper: 'Lote {0} sitios web, ¿continuar operación?',
+ stream: 'Proxy TCP/UDP',
+ streamPorts: 'Puertos de escucha',
+ streamPortsHelper: 'Separe con comas, por ejemplo: 3306, 3307',
},
php: {
short_open_tag: 'Soporte de etiquetas cortas',
diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts
index 9e798b431308..cb9b8f633c21 100644
--- a/frontend/src/lang/modules/ja.ts
+++ b/frontend/src/lang/modules/ja.ts
@@ -2622,6 +2622,9 @@ const message = {
batchOpreate: 'バッチ操作',
batchOpreateHelper: 'ウェブサイトをバッチ{0}しますか?',
+ stream: 'TCP/UDP プロキシ',
+ streamPorts: '待ち受けポート',
+ streamPortsHelper: 'カンマで区切ってください(例:3306, 3307)',
},
php: {
short_open_tag: '短いタグサポート',
diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts
index ffbd5533b779..a23f5e9ebeda 100644
--- a/frontend/src/lang/modules/ko.ts
+++ b/frontend/src/lang/modules/ko.ts
@@ -2576,6 +2576,9 @@ const message = {
batchOpreate: '일괄 작업',
batchOpreateHelper: '웹사이트를 일괄 {0}, 계속 작업하시겠습니까?',
+ stream: 'TCP/UDP 프록시',
+ streamPorts: '수신 포트',
+ streamPortsHelper: '쉼표로 구분하세요. 예: 3306, 3307',
},
php: {
short_open_tag: '짧은 태그 지원',
diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts
index 25ea2c551c7b..a93c79dadfe9 100644
--- a/frontend/src/lang/modules/ms.ts
+++ b/frontend/src/lang/modules/ms.ts
@@ -2677,6 +2677,9 @@ const message = {
batchOpreate: 'Operasi Pukal',
batchOpreateHelper: 'Pukal {0} laman web, teruskan operasi?',
+ stream: 'Proksi TCP/UDP',
+ streamPorts: 'Port Mendengar',
+ streamPortsHelper: 'Sila asingkan dengan koma, contoh: 3306, 3307',
},
php: {
short_open_tag: 'Sokongan tag pendek',
diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts
index 96cef369d23b..4af0a350aa9e 100644
--- a/frontend/src/lang/modules/pt-br.ts
+++ b/frontend/src/lang/modules/pt-br.ts
@@ -2682,6 +2682,9 @@ const message = {
batchOpreate: 'Operação em Lote',
batchOpreateHelper: 'Lote {0} sites, continuar operação?',
+ stream: 'Proxy TCP/UDP',
+ streamPorts: 'Portas de escuta',
+ streamPortsHelper: 'Separe por vírgulas, por exemplo: 3306, 3307',
},
php: {
short_open_tag: 'Suporte para short tags',
diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts
index 7ae6cbee47a3..e433428ce5d3 100644
--- a/frontend/src/lang/modules/ru.ts
+++ b/frontend/src/lang/modules/ru.ts
@@ -2678,6 +2678,9 @@ const message = {
batchOpreate: 'Пакетная операция',
batchOpreateHelper: 'Пакетное {0} веб-сайтов, продолжить операцию?',
+ stream: 'Прокси TCP/UDP',
+ streamPorts: 'Порты прослушивания',
+ streamPortsHelper: 'Разделяйте запятыми, например: 3306, 3307',
},
php: {
short_open_tag: 'Поддержка коротких тегов',
diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts
index 5d4c8b8d46e3..c60ece4ec5ef 100644
--- a/frontend/src/lang/modules/tr.ts
+++ b/frontend/src/lang/modules/tr.ts
@@ -2735,6 +2735,9 @@ const message = {
batchOpreate: 'Toplu İşlem',
batchOpreateHelper: 'Toplu {0} web siteleri, işlemi devam ettir?',
+ stream: 'TCP/UDP Proxy',
+ streamPorts: 'Dinleme Portları',
+ streamPortsHelper: 'Virgülle ayırın, örneğin: 3306, 3307',
},
php: {
short_open_tag: 'Kısa etiket desteği',
diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts
index fdcce3e12037..0666aa833e3e 100644
--- a/frontend/src/lang/modules/zh-Hant.ts
+++ b/frontend/src/lang/modules/zh-Hant.ts
@@ -2510,6 +2510,9 @@ const message = {
batchOpreate: '批次操作',
batchOpreateHelper: '批次{0}網站,是否繼續操作?',
+ stream: 'TCP/UDP 代理',
+ streamPorts: '監聽端口',
+ streamPortsHelper: '請用逗號分隔,例如:3306,3307',
},
php: {
short_open_tag: '短標籤支援',
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index 22e8908344f2..8769674cd66c 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -2506,6 +2506,9 @@ const message = {
batchOpreate: '批量操作',
batchOpreateHelper: '批量{0}网站,是否继续操作?',
+ stream: 'TCP/UDP 代理',
+ streamPorts: '监听端口',
+ streamPortsHelper: '请按逗号分割,例如:3306,3307',
},
php: {
short_open_tag: '短标签支持',
diff --git a/frontend/src/views/website/website/components/website-ssl/index.vue b/frontend/src/views/website/website/components/website-ssl/index.vue
new file mode 100644
index 000000000000..97a8b73ae71f
--- /dev/null
+++ b/frontend/src/views/website/website/components/website-ssl/index.vue
@@ -0,0 +1,38 @@
+
+
+
+ {{ websiteSSL.primaryDomain }}
+
+
+ {{ websiteSSL.domains }}
+
+
+ {{ websiteSSL.organization }}
+
+
+ {{ getProvider(websiteSSL.provider) }}
+
+
+ {{ websiteSSL.acmeAccount.email }}
+
+
+ {{ dateFormatSimple(websiteSSL.expireDate) }}
+
+
+ {{ websiteSSL.description }}
+
+
+
+
+
diff --git a/frontend/src/views/website/website/config/basic/https/index.vue b/frontend/src/views/website/website/config/basic/https/index.vue
index 7f041617483a..6abdca88aded 100644
--- a/frontend/src/views/website/website/config/basic/https/index.vue
+++ b/frontend/src/views/website/website/config/basic/https/index.vue
@@ -133,32 +133,7 @@