package service import ( "bufio" "bytes" "context" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "errors" "fmt" "github.com/1Panel-dev/1Panel/agent/app/task" "os" "path" "reflect" "regexp" "strconv" "strings" "syscall" "time" "github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/jinzhu/copier" "github.com/1Panel-dev/1Panel/agent/i18n" "github.com/spf13/afero" "github.com/1Panel-dev/1Panel/agent/utils/compose" "github.com/1Panel-dev/1Panel/agent/utils/env" "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "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" "gopkg.in/ini.v1" "gorm.io/gorm" "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/global" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/utils/files" ) type WebsiteService struct { } type IWebsiteService interface { PageWebsite(req request.WebsiteSearch) (int64, []response.WebsiteRes, error) GetWebsites() ([]response.WebsiteDTO, error) CreateWebsite(create request.WebsiteCreate) error OpWebsite(req request.WebsiteOp) error GetWebsiteOptions(req request.WebsiteOptionReq) ([]response.WebsiteOption, error) UpdateWebsite(req request.WebsiteUpdate) error DeleteWebsite(req request.WebsiteDelete) error GetWebsite(id uint) (response.WebsiteDTO, error) CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) DeleteWebsiteDomain(domainId uint) error UpdateWebsiteDomain(req request.WebsiteDomainUpdate) 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) GetPHPConfig(id uint) (*response.PHPConfig, error) UpdatePHPConfig(req request.WebsitePHPConfigUpdate) error UpdatePHPConfigFile(req request.WebsitePHPFileUpdate) error ChangePHPVersion(req request.WebsitePHPVersionReq) error GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) UpdateRewriteConfig(req request.NginxRewriteUpdate) error LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) UpdateSiteDir(req request.WebsiteUpdateDir) error UpdateSitePermission(req request.WebsiteUpdateDirPermission) error OperateProxy(req request.WebsiteProxyConfig) (err error) GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) UpdateProxyFile(req request.NginxProxyUpdate) (err error) GetAntiLeech(id uint) (*response.NginxAntiLeechRes, error) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error) OperateRedirect(req request.NginxRedirectReq) (err error) 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) 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 } func NewIWebsiteService() IWebsiteService { return &WebsiteService{} } func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []response.WebsiteRes, error) { var ( websiteDTOs []response.WebsiteRes opts []repo.DBOption ) nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return 0, nil, nil } return 0, nil, err } opts = append(opts, commonRepo.WithOrderRuleBy(req.OrderBy, req.Order)) if req.Name != "" { domains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithDomainLike(req.Name)) if len(domains) > 0 { var websiteIds []uint for _, domain := range domains { websiteIds = append(websiteIds, domain.WebsiteID) } opts = append(opts, websiteRepo.WithIDs(websiteIds)) } else { opts = append(opts, websiteRepo.WithDomainLike(req.Name)) } } if req.WebsiteGroupID != 0 { opts = append(opts, websiteRepo.WithGroupID(req.WebsiteGroupID)) } total, websites, err := websiteRepo.Page(req.Page, req.PageSize, opts...) if err != nil { return 0, nil, err } for _, web := range websites { var ( appName string runtimeName string appInstallID uint ) switch web.Type { case constant.Deployment: appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(web.AppInstallID)) if err != nil { return 0, nil, err } appName = appInstall.Name appInstallID = appInstall.ID case constant.Runtime: runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(web.RuntimeID)) if err != nil { return 0, nil, err } runtimeName = runtime.Name appInstallID = runtime.ID } sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", web.Alias) siteDTO := response.WebsiteRes{ ID: web.ID, CreatedAt: web.CreatedAt, Protocol: web.Protocol, PrimaryDomain: web.PrimaryDomain, Type: web.Type, Remark: web.Remark, Status: web.Status, Alias: web.Alias, AppName: appName, ExpireDate: web.ExpireDate, SSLExpireDate: web.WebsiteSSL.ExpireDate, SSLStatus: checkSSLStatus(web.WebsiteSSL.ExpireDate), RuntimeName: runtimeName, SitePath: sitePath, AppInstallID: appInstallID, } sites, _ := websiteRepo.List(websiteRepo.WithParentID(web.ID)) if len(sites) > 0 { for _, site := range sites { siteDTO.ChildSites = append(siteDTO.ChildSites, site.PrimaryDomain) } } websiteDTOs = append(websiteDTOs, siteDTO) } return total, websiteDTOs, nil } func (w WebsiteService) GetWebsites() ([]response.WebsiteDTO, error) { var websiteDTOs []response.WebsiteDTO websites, _ := websiteRepo.List(commonRepo.WithOrderRuleBy("primary_domain", "ascending")) for _, web := range websites { res := response.WebsiteDTO{ Website: web, } websiteDTOs = append(websiteDTOs, res) } return websiteDTOs, nil } func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) { alias := create.Alias if alias == "default" { return buserr.New("ErrDefaultAlias") } if common.ContainsChinese(alias) { alias, err = common.PunycodeEncode(alias) if err != nil { return } } if exist, _ := websiteRepo.GetBy(websiteRepo.WithAlias(alias)); len(exist) > 0 { return buserr.New(constant.ErrAliasIsExist) } if len(create.FtpPassword) != 0 { pass, err := base64.StdEncoding.DecodeString(create.FtpPassword) if err != nil { return err } create.FtpPassword = string(pass) } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } defaultHttpPort := nginxInstall.HttpPort var ( domains []model.WebsiteDomain ) domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, 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.DefaultDate) 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, ErrorLog: true, IPV6: create.IPV6, } var ( appInstall *model.AppInstall runtime *model.Runtime ) createTask, err := task.NewTaskWithOps(primaryDomain, task.TaskCreate, task.TaskScopeWebsite, create.TaskID, 0) if err != nil { return err } if create.CreateDb { createDataBase := func(t *task.Task) error { database, _ := databaseRepo.Get(commonRepo.WithByName(create.DbHost)) if database.ID == 0 { return nil } dbConfig := create.DataBaseConfig switch database.Type { case constant.AppPostgresql, constant.AppPostgres: iPostgresqlRepo := repo.NewIPostgresqlRepo() oldPostgresqlDb, _ := iPostgresqlRepo.Get(commonRepo.WithByName(create.DbName), iPostgresqlRepo.WithByFrom(constant.ResourceLocal)) if oldPostgresqlDb.ID > 0 { return buserr.New(constant.ErrDbUserNotValid) } var createPostgresql dto.PostgresqlDBCreate createPostgresql.Name = dbConfig.DbName createPostgresql.Username = dbConfig.DbUser createPostgresql.Database = database.Name createPostgresql.Format = dbConfig.DBFormat createPostgresql.Password = dbConfig.DbPassword createPostgresql.From = database.From createPostgresql.SuperUser = true pgDB, err := NewIPostgresqlService().Create(context.Background(), createPostgresql) if err != nil { return err } website.DbID = pgDB.ID website.DbType = database.Type case constant.AppMysql, constant.AppMariaDB: iMysqlRepo := repo.NewIMysqlRepo() oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal)) if oldMysqlDb.ID > 0 { return buserr.New(constant.ErrDbUserNotValid) } var createMysql dto.MysqlDBCreate createMysql.Name = dbConfig.DbName createMysql.Username = dbConfig.DbUser createMysql.Database = database.Name createMysql.Format = dbConfig.DBFormat createMysql.Permission = "%" createMysql.Password = dbConfig.DbPassword createMysql.From = database.From mysqlDB, err := NewIMysqlService().Create(context.Background(), createMysql) if err != nil { return err } website.DbID = mysqlDB.ID website.DbType = database.Type } return nil } createTask.AddSubTask(task.GetTaskName(create.DbName, task.TaskCreate, task.TaskScopeDatabase), createDataBase, nil) } var proxy string switch create.Type { case constant.Deployment: if create.AppType == constant.NewApp { var ( req request.AppInstallCreate install *model.AppInstall ) req.Name = create.AppInstall.Name req.AppDetailId = create.AppInstall.AppDetailId req.Params = create.AppInstall.Params req.AppContainerConfig = create.AppInstall.AppContainerConfig install, err = NewIAppService().Install(req) if err != nil { return err } appInstall = install website.AppInstallID = install.ID website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) } else { var install model.AppInstall install, err = appInstallRepo.GetFirst(commonRepo.WithByID(create.AppInstallID)) if err != nil { return err } configApp := func(t *task.Task) error { appInstall = &install website.AppInstallID = appInstall.ID website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) return nil } createTask.AddSubTask(i18n.GetMsgByKey("ConfigApp"), configApp, nil) } case constant.Runtime: runtime, err = runtimeRepo.GetFirst(commonRepo.WithByID(create.RuntimeID)) if err != nil { return err } website.RuntimeID = runtime.ID switch runtime.Type { case constant.RuntimePHP: if runtime.Resource == constant.ResourceAppstore { var ( req request.AppInstallCreate install *model.AppInstall ) reg, _ := regexp.Compile(`[^a-z0-9_-]+`) req.Name = reg.ReplaceAllString(strings.ToLower(alias), "") req.AppDetailId = create.AppInstall.AppDetailId req.Params = create.AppInstall.Params req.Params["IMAGE_NAME"] = runtime.Image req.AppContainerConfig = create.AppInstall.AppContainerConfig req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www") install, err = NewIAppService().Install(req) if err != nil { return err } website.AppInstallID = install.ID appInstall = install website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) } else { website.ProxyType = create.ProxyType if website.ProxyType == constant.RuntimeProxyUnix { proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock")) } if website.ProxyType == constant.RuntimeProxyTcp { proxy = fmt.Sprintf("127.0.0.1:%d", create.Port) } website.Proxy = proxy } case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo: website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port) } case constant.Subsite: parentWebsite, err := websiteRepo.GetFirst(commonRepo.WithByID(create.ParentWebsiteID)) if err != nil { return err } website.ParentWebsiteID = parentWebsite.ID } if len(create.FtpUser) != 0 && len(create.FtpPassword) != 0 { createFtpUser := func(t *task.Task) error { indexDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index") itemID, err := NewIFtpService().Create(dto.FtpCreate{User: create.FtpUser, Password: create.FtpPassword, Path: indexDir}) if err != nil { createTask.Log(fmt.Sprintf("create ftp for website failed, err: %v", err)) } website.FtpID = itemID return nil } deleteFtpUser := func(t *task.Task) { if website.FtpID > 0 { if err = NewIFtpService().Delete(dto.BatchDeleteReq{Ids: []uint{website.FtpID}}); err != nil { createTask.Log(err.Error()) } } } createTask.AddSubTask(i18n.GetWithName("ConfigFTP", create.FtpUser), createFtpUser, deleteFtpUser) } configNginx := func(t *task.Task) error { if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil { return err } if err = createWafConfig(website, domains); err != nil { return err } 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 } tx.Commit() return nil } deleteWebsite := func(t *task.Task) { _ = deleteWebsiteFolder(nginxInstall, website) } createTask.AddSubTask(i18n.GetMsgByKey("ConfigOpenresty"), configNginx, deleteWebsite) if create.EnableSSL { enableSSL := func(t *task.Task) error { websiteModel, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(create.WebsiteSSLID)) if err != nil { return err } website.Protocol = constant.ProtocolHTTPS website.WebsiteSSLID = create.WebsiteSSLID appSSLReq := request.WebsiteHTTPSOp{ WebsiteID: website.ID, Enable: true, WebsiteSSLID: websiteModel.ID, Type: "existed", HttpConfig: "HTTPToHTTPS", SSLProtocol: []string{"TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1"}, Algorithm: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED", Hsts: true, } if err = applySSL(website, *websiteModel, appSSLReq); err != nil { return err } if err = websiteRepo.Save(context.Background(), website); err != nil { return err } return nil } createTask.AddSubTaskWithIgnoreErr(i18n.GetMsgByKey("EnableSSL"), enableSSL) } return createTask.Execute() } func (w WebsiteService) OpWebsite(req request.WebsiteOp) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return err } if err := opWebsite(&website, req.Operate); err != nil { return err } return websiteRepo.Save(context.Background(), &website) } func (w WebsiteService) GetWebsiteOptions(req request.WebsiteOptionReq) ([]response.WebsiteOption, error) { var options []repo.DBOption if len(req.Types) > 0 { options = append(options, websiteRepo.WithTypes(req.Types)) } webs, _ := websiteRepo.List(options...) var datas []response.WebsiteOption for _, web := range webs { var item response.WebsiteOption if err := copier.Copy(&item, &web); err != nil { return nil, err } datas = append(datas, item) } return datas, nil } func (w WebsiteService) UpdateWebsite(req request.WebsiteUpdate) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return err } if website.IPV6 != req.IPV6 { if err := changeIPV6(website, req.IPV6); err != nil { return err } } website.PrimaryDomain = req.PrimaryDomain website.WebsiteGroupID = req.WebsiteGroupID website.Remark = req.Remark website.IPV6 = req.IPV6 if req.ExpireDate != "" { expireDate, err := time.Parse(constant.DateLayout, req.ExpireDate) if err != nil { return err } website.ExpireDate = expireDate } return websiteRepo.Save(context.TODO(), &website) } func (w WebsiteService) GetWebsite(id uint) (response.WebsiteDTO, error) { var res response.WebsiteDTO website, err := websiteRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { return res, err } res.Website = website nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return res, err } sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", website.Alias) res.ErrorLogPath = path.Join(sitePath, "log", "error.log") res.AccessLogPath = path.Join(sitePath, "log", "access.log") res.SitePath = sitePath res.SiteDir = website.SiteDir return res, nil } func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return err } if website.Type != constant.Subsite { parentWebsites, _ := websiteRepo.List(websiteRepo.WithParentID(website.ID)) if len(parentWebsites) > 0 { var names []string for _, site := range parentWebsites { names = append(names, site.PrimaryDomain) } return buserr.WithName("ErrParentWebsite", strings.Join(names, ",")) } } if err = delNginxConfig(website, req.ForceDelete); err != nil { return err } if err = delWafConfig(website, req.ForceDelete); err != nil { return err } if checkIsLinkApp(website) && req.DeleteApp { appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if appInstall.ID > 0 { deleteReq := request.AppInstallDelete{ Install: appInstall, ForceDelete: req.ForceDelete, DeleteBackup: true, DeleteDB: true, } if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete { return err } } } tx, ctx := helper.GetTxAndContext() defer tx.Rollback() go func() { _ = NewIBackupService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup) }() if err := websiteRepo.DeleteBy(ctx, commonRepo.WithByID(req.ID)); err != nil { return err } if err := websiteDomainRepo.DeleteBy(ctx, websiteDomainRepo.WithWebsiteId(req.ID)); err != nil { return err } tx.Commit() uploadDir := path.Join(global.CONF.System.BaseDir, fmt.Sprintf("1panel/uploads/website/%s", website.Alias)) if _, err := os.Stat(uploadDir); err == nil { _ = os.RemoveAll(uploadDir) } return nil } func (w WebsiteService) UpdateWebsiteDomain(req request.WebsiteDomainUpdate) error { domain, err := websiteDomainRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return err } domain.SSL = req.SSL website, err := websiteRepo.GetFirst(commonRepo.WithByID(domain.WebsiteID)) if err != nil { return err } if website.Protocol == constant.ProtocolHTTPS { nginxFull, err := getNginxFull(&website) if err != nil { return nil } nginxConfig := nginxFull.SiteConfig config := nginxFull.SiteConfig.Config server := config.FindServers()[0] var params []string if domain.SSL { params = append(params, "ssl", "http2") } server.UpdateListen(strconv.Itoa(domain.Port), false, params...) if website.IPV6 { server.UpdateListen("[::]:"+strconv.Itoa(domain.Port), false, params...) } if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return err } if err = nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName); err != nil { return err } } return websiteDomainRepo.Save(context.TODO(), &domain) } func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) { var ( domainModels []model.WebsiteDomain addPorts []int ) httpPort, _, err := getAppInstallPort(constant.AppOpenresty) if err != nil { return nil, err } website, err := websiteRepo.GetFirst(commonRepo.WithByID(create.WebsiteID)) if err != nil { return nil, err } domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, create.WebsiteID) if err != nil { return nil, err } go func() { _ = OperateFirewallPort(nil, addPorts) }() if err = addListenAndServerName(website, domainModels); err != nil { return nil, err } 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, 0644); 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(commonRepo.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(commonRepo.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, 0644); err != nil { return err } } return websiteDomainRepo.DeleteBy(context.TODO(), commonRepo.WithByID(domainId)) } func (w WebsiteService) GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error) { keys, ok := dto.ScopeKeyMap[req.Scope] if !ok || len(keys) == 0 { return nil, nil } website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return nil, err } var config response.WebsiteNginxConfig params, err := getNginxParamsByKeys(constant.NginxScopeServer, keys, &website) if err != nil { return nil, err } config.Params = params config.Enable = len(params[0].Params) > 0 return &config, nil } func (w WebsiteService) UpdateNginxConfigByScope(req request.NginxConfigUpdate) error { keys, ok := dto.ScopeKeyMap[req.Scope] if !ok || len(keys) == 0 { return nil } website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } if req.Operate == constant.ConfigDel { var nginxParams []dto.NginxParam for _, key := range keys { nginxParams = append(nginxParams, dto.NginxParam{ Name: key, }) } return deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website) } params := getNginxParams(req.Params, keys) if req.Operate == constant.ConfigNew { if _, ok := dto.StaticFileKeyMap[req.Scope]; ok { params = getNginxParamsFromStaticFile(req.Scope, params) } } return updateNginxConfig(constant.NginxScopeServer, params, &website) } func (w WebsiteService) GetWebsiteNginxConfig(websiteId uint, configType string) (response.FileInfo, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteId)) if err != nil { return response.FileInfo{}, err } configPath := "" switch configType { case constant.AppOpenresty: nginxApp, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenresty)) if err != nil { return response.FileInfo{}, err } nginxInstall, err := appInstallRepo.GetFirst(appInstallRepo.WithAppId(nginxApp.ID)) if err != nil { return response.FileInfo{}, err } configPath = path.Join(nginxInstall.GetPath(), "conf", "conf.d", website.Alias+".conf") case constant.ConfigFPM: runtimeInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if err != nil { return response.FileInfo{}, err } configPath = path.Join(runtimeInstall.GetPath(), "conf", "php-fpm.conf") case constant.ConfigPHP: runtimeInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if err != nil { return response.FileInfo{}, err } configPath = path.Join(runtimeInstall.GetPath(), "conf", "php.ini") } info, err := files.NewFileInfo(files.FileOption{ Path: configPath, Expand: true, }) if err != nil { return response.FileInfo{}, err } return response.FileInfo{FileInfo: *info}, nil } func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(websiteId)) if err != nil { return response.WebsiteHTTPS{}, err } var ( res response.WebsiteHTTPS httpsPorts []string ) websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteId)) for _, domain := range websiteDomains { if domain.SSL { httpsPorts = append(httpsPorts, strconv.Itoa(domain.Port)) } } if len(httpsPorts) == 0 { nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) res.HttpsPort = strconv.Itoa(nginxInstall.HttpsPort) } else { res.HttpsPort = strings.Join(httpsPorts, ",") } if website.WebsiteSSLID == 0 { res.Enable = false return res, nil } websiteSSL, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(website.WebsiteSSLID)) if err != nil { return response.WebsiteHTTPS{}, err } res.SSL = *websiteSSL res.Enable = true if website.HttpConfig != "" { res.HttpConfig = website.HttpConfig } else { res.HttpConfig = constant.HTTPToHTTPS } params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"ssl_protocols", "ssl_ciphers", "add_header"}, &website) if err != nil { return res, err } for _, p := range params { if p.Name == "ssl_protocols" { res.SSLProtocol = p.Params } if p.Name == "ssl_ciphers" { res.Algorithm = p.Params[0] } if p.Name == "add_header" && len(p.Params) > 0 && p.Params[0] == "Strict-Transport-Security" { res.Hsts = true } } return res, nil } func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return nil, err } var ( res response.WebsiteHTTPS websiteSSL model.WebsiteSSL ) nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return nil, err } if err = ChangeHSTSConfig(req.Hsts, nginxInstall, website); err != nil { return nil, err } res.Enable = req.Enable res.SSLProtocol = req.SSLProtocol res.Algorithm = req.Algorithm if !req.Enable { website.Protocol = constant.ProtocolHTTP website.WebsiteSSLID = 0 httpsPorts, err := getHttpsPort(&website) if err != nil { return nil, err } if len(httpsPorts) == 1 && httpsPorts[0] == nginxInstall.HttpsPort { httpsPortStr := strconv.Itoa(httpsPorts[0]) if err = deleteListenAndServerName(website, []string{httpsPortStr, "[::]:" + httpsPortStr}, []string{}); err != nil { return nil, err } } else { for _, port := range httpsPorts { httpsPortStr := strconv.Itoa(port) if err = removeSSLListen(website, []string{httpsPortStr}); err != nil { return nil, err } } } nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil) nginxParams = append(nginxParams, dto.NginxParam{ Name: "if", Params: []string{"($scheme", "=", "http)"}, }, dto.NginxParam{ Name: "ssl_certificate", }, dto.NginxParam{ Name: "ssl_certificate_key", }, dto.NginxParam{ Name: "ssl_protocols", }, dto.NginxParam{ Name: "ssl_ciphers", }, ) if err = deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil { return nil, err } if err = websiteRepo.Save(ctx, &website); err != nil { return nil, err } return nil, nil } if req.Type == constant.SSLExisted { websiteModel, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.WebsiteSSLID)) if err != nil { return nil, err } website.WebsiteSSLID = websiteModel.ID res.SSL = *websiteModel websiteSSL = *websiteModel } if req.Type == constant.SSLManual { var ( certificate string privateKey string ) switch req.ImportType { case "paste": certificate = req.Certificate privateKey = req.PrivateKey case "local": fileOp := files.NewFileOp() if !fileOp.Stat(req.PrivateKeyPath) { return nil, buserr.New("ErrSSLKeyNotFound") } if !fileOp.Stat(req.CertificatePath) { return nil, buserr.New("ErrSSLCertificateNotFound") } if content, err := fileOp.GetContent(req.PrivateKeyPath); err != nil { return nil, err } else { privateKey = string(content) } if content, err := fileOp.GetContent(req.CertificatePath); err != nil { return nil, err } else { certificate = string(content) } } privateKeyCertBlock, _ := pem.Decode([]byte(privateKey)) if privateKeyCertBlock == nil { return nil, buserr.New("ErrSSLKeyFormat") } certBlock, _ := pem.Decode([]byte(certificate)) if certBlock == nil { return nil, buserr.New("ErrSSLCertificateFormat") } cert, err := x509.ParseCertificate(certBlock.Bytes) if err != nil { return nil, err } websiteSSL.ExpireDate = cert.NotAfter websiteSSL.StartDate = cert.NotBefore websiteSSL.Type = cert.Issuer.CommonName if len(cert.Issuer.Organization) > 0 { websiteSSL.Organization = cert.Issuer.Organization[0] } else { websiteSSL.Organization = cert.Issuer.CommonName } if len(cert.DNSNames) > 0 { websiteSSL.PrimaryDomain = cert.DNSNames[0] websiteSSL.Domains = strings.Join(cert.DNSNames, ",") } websiteSSL.Provider = constant.Manual websiteSSL.PrivateKey = privateKey websiteSSL.Pem = certificate res.SSL = websiteSSL } website.Protocol = constant.ProtocolHTTPS if err = applySSL(&website, websiteSSL, req); err != nil { return nil, err } website.HttpConfig = req.HttpConfig if websiteSSL.ID == 0 { if err = websiteSSLRepo.Create(ctx, &websiteSSL); err != nil { return nil, err } website.WebsiteSSLID = websiteSSL.ID } if err = websiteRepo.Save(ctx, &website); err != nil { return nil, err } return &res, nil } func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) { var ( res []response.WebsitePreInstallCheck checkIds []uint showErr = false ) app, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenresty)) if err != nil { return nil, err } appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID)) if reflect.DeepEqual(appInstall, model.AppInstall{}) { res = append(res, response.WebsitePreInstallCheck{ Name: appInstall.Name, AppName: app.Name, Status: buserr.WithDetail(constant.ErrNotInstall, app.Name, nil).Error(), Version: appInstall.Version, }) showErr = true } else { checkIds = append(req.InstallIds, appInstall.ID) } if len(checkIds) > 0 { installList, _ := appInstallRepo.ListBy(commonRepo.WithIdsIn(checkIds)) for _, install := range installList { if err = syncAppInstallStatus(&install, false); err != nil { return nil, err } res = append(res, response.WebsitePreInstallCheck{ Name: install.Name, Status: install.Status, Version: install.Version, AppName: install.App.Name, }) if install.Status != constant.Running { showErr = true } } } if showErr { return res, nil } return nil, nil } func (w WebsiteService) UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return err } nginxFull, err := getNginxFull(&website) if err != nil { return err } filePath := nginxFull.SiteConfig.FilePath if err := files.NewFileOp().WriteFile(filePath, strings.NewReader(req.Content), 0755); err != nil { return err } return nginxCheckAndReload(nginxFull.SiteConfig.OldContent, filePath, nginxFull.Install.ContainerName) } func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.WebsiteLog, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return nil, err } nginx, err := getNginxFull(&website) if err != nil { return nil, err } sitePath := path.Join(nginx.SiteDir, "sites", website.Alias) res := &response.WebsiteLog{ Content: "", } switch req.Operate { case constant.GetLog: switch req.LogType { case constant.AccessLog: res.Enable = website.AccessLog if !website.AccessLog { return res, nil } case constant.ErrorLog: res.Enable = website.ErrorLog if !website.ErrorLog { return res, nil } } filePath := path.Join(sitePath, "log", req.LogType) lines, end, _, err := files.ReadFileByLine(filePath, req.Page, req.PageSize, false) if err != nil { return nil, err } res.End = end res.Path = filePath res.Content = strings.Join(lines, "\n") return res, nil case constant.DisableLog: key := "access_log" switch req.LogType { case constant.AccessLog: website.AccessLog = false case constant.ErrorLog: key = "error_log" website.ErrorLog = false } var nginxParams []dto.NginxParam nginxParams = append(nginxParams, dto.NginxParam{ Name: key, Params: []string{"off"}, }) if err := updateNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil { return nil, err } if err := websiteRepo.Save(context.Background(), &website); err != nil { return nil, err } case constant.EnableLog: key := "access_log" logPath := path.Join("/www", "sites", website.Alias, "log", req.LogType) params := []string{logPath} switch req.LogType { case constant.AccessLog: params = append(params, "main") website.AccessLog = true case constant.ErrorLog: key = "error_log" website.ErrorLog = true } if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: key, Params: params}}, &website); err != nil { return nil, err } if err := websiteRepo.Save(context.Background(), &website); err != nil { return nil, err } case constant.DeleteLog: logPath := path.Join(nginx.Install.GetPath(), "www", "sites", website.Alias, "log", req.LogType) if err := files.NewFileOp().WriteFile(logPath, strings.NewReader(""), 0755); err != nil { return nil, err } } return res, nil } func (w WebsiteService) ChangeDefaultServer(id uint) error { defaultWebsite, _ := websiteRepo.GetFirst(websiteRepo.WithDefaultServer()) if defaultWebsite.ID > 0 { params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"listen"}, &defaultWebsite) if err != nil { return err } var changeParams []dto.NginxParam for _, param := range params { paramLen := len(param.Params) var newParam []string if paramLen > 1 && param.Params[paramLen-1] == components.DefaultServer { newParam = param.Params[:paramLen-1] } changeParams = append(changeParams, dto.NginxParam{ Name: param.Name, Params: newParam, }) } if err := updateNginxConfig(constant.NginxScopeServer, changeParams, &defaultWebsite); err != nil { return err } defaultWebsite.DefaultServer = false if err := websiteRepo.Save(context.Background(), &defaultWebsite); err != nil { return err } } if id > 0 { website, err := websiteRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { return err } params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"listen"}, &website) if err != nil { return err } httpPort, httpsPort, err := getAppInstallPort(constant.AppOpenresty) if err != nil { return err } var changeParams []dto.NginxParam for _, param := range params { paramLen := len(param.Params) bind := param.Params[0] var newParam []string if bind == strconv.Itoa(httpPort) || bind == strconv.Itoa(httpsPort) || bind == "[::]:"+strconv.Itoa(httpPort) || bind == "[::]:"+strconv.Itoa(httpsPort) { if param.Params[paramLen-1] == components.DefaultServer { newParam = param.Params } else { newParam = append(param.Params, components.DefaultServer) } } changeParams = append(changeParams, dto.NginxParam{ Name: param.Name, Params: newParam, }) } if err := updateNginxConfig(constant.NginxScopeServer, changeParams, &website); err != nil { return err } website.DefaultServer = true return websiteRepo.Save(context.Background(), &website) } return nil } func (w WebsiteService) GetPHPConfig(id uint) (*response.PHPConfig, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { return nil, err } appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if err != nil { return nil, err } phpConfigPath := path.Join(appInstall.GetPath(), "conf", "php.ini") fileOp := files.NewFileOp() if !fileOp.Stat(phpConfigPath) { return nil, buserr.WithMap("ErrFileNotFound", map[string]interface{}{"name": "php.ini"}, nil) } params := make(map[string]string) configFile, err := fileOp.OpenFile(phpConfigPath) if err != nil { return nil, err } defer configFile.Close() scanner := bufio.NewScanner(configFile) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if strings.HasPrefix(line, ";") { continue } matches := regexp.MustCompile(`^\s*([a-z_]+)\s*=\s*(.*)$`).FindStringSubmatch(line) if len(matches) == 3 { params[matches[1]] = matches[2] } } cfg, err := ini.Load(phpConfigPath) if err != nil { return nil, err } phpConfig, err := cfg.GetSection("PHP") if err != nil { return nil, err } disableFunctionStr := phpConfig.Key("disable_functions").Value() res := &response.PHPConfig{Params: params} if disableFunctionStr != "" { disableFunctions := strings.Split(disableFunctionStr, ",") if len(disableFunctions) > 0 { res.DisableFunctions = disableFunctions } } uploadMaxSize := phpConfig.Key("upload_max_filesize").Value() if uploadMaxSize != "" { res.UploadMaxSize = uploadMaxSize } return res, nil } func (w WebsiteService) UpdatePHPConfig(req request.WebsitePHPConfigUpdate) (err error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return err } appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if err != nil { return err } phpConfigPath := path.Join(appInstall.GetPath(), "conf", "php.ini") fileOp := files.NewFileOp() if !fileOp.Stat(phpConfigPath) { return buserr.WithMap("ErrFileNotFound", map[string]interface{}{"name": "php.ini"}, nil) } configFile, err := fileOp.OpenFile(phpConfigPath) if err != nil { return err } defer configFile.Close() contentBytes, err := fileOp.GetContent(phpConfigPath) if err != nil { return err } content := string(contentBytes) lines := strings.Split(content, "\n") for i, line := range lines { if strings.HasPrefix(line, ";") { continue } switch req.Scope { case "params": for key, value := range req.Params { pattern := "^" + regexp.QuoteMeta(key) + "\\s*=\\s*.*$" if matched, _ := regexp.MatchString(pattern, line); matched { lines[i] = key + " = " + value } } case "disable_functions": pattern := "^" + regexp.QuoteMeta("disable_functions") + "\\s*=\\s*.*$" if matched, _ := regexp.MatchString(pattern, line); matched { lines[i] = "disable_functions" + " = " + strings.Join(req.DisableFunctions, ",") break } case "upload_max_filesize": pattern := "^" + regexp.QuoteMeta("post_max_size") + "\\s*=\\s*.*$" if matched, _ := regexp.MatchString(pattern, line); matched { lines[i] = "post_max_size" + " = " + req.UploadMaxSize } patternUpload := "^" + regexp.QuoteMeta("upload_max_filesize") + "\\s*=\\s*.*$" if matched, _ := regexp.MatchString(patternUpload, line); matched { lines[i] = "upload_max_filesize" + " = " + req.UploadMaxSize } } } updatedContent := strings.Join(lines, "\n") if err := fileOp.WriteFile(phpConfigPath, strings.NewReader(updatedContent), 0755); err != nil { return err } appInstallReq := request.AppInstalledOperate{ InstallId: appInstall.ID, Operate: constant.Restart, } if err = NewIAppInstalledService().Operate(appInstallReq); err != nil { _ = fileOp.WriteFile(phpConfigPath, strings.NewReader(string(contentBytes)), 0755) return err } return nil } func (w WebsiteService) UpdatePHPConfigFile(req request.WebsitePHPFileUpdate) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return err } if website.Type != constant.Runtime { return nil } runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)) if err != nil { return err } if runtime.Resource != constant.ResourceAppstore { return nil } runtimeInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if err != nil { return err } configPath := "" if req.Type == constant.ConfigFPM { configPath = path.Join(runtimeInstall.GetPath(), "conf", "php-fpm.conf") } else { configPath = path.Join(runtimeInstall.GetPath(), "conf", "php.ini") } if err := files.NewFileOp().WriteFile(configPath, strings.NewReader(req.Content), 0755); err != nil { return err } if _, err := compose.Restart(runtimeInstall.GetComposePath()); err != nil { return err } return nil } func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.RuntimeID)) if err != nil { return err } oldRuntime, err := runtimeRepo.GetFirst(commonRepo.WithByID(website.RuntimeID)) if err != nil { return err } if runtime.Resource == constant.ResourceLocal || oldRuntime.Resource == constant.ResourceLocal { return buserr.New("ErrPHPResource") } appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if err != nil { return err } appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(runtime.AppDetailID)) if err != nil { return err } envs := make(map[string]interface{}) if err = json.Unmarshal([]byte(appInstall.Env), &envs); err != nil { return err } if out, err := compose.Down(appInstall.GetComposePath()); err != nil { if out != "" { return errors.New(out) } return err } var ( busErr error fileOp = files.NewFileOp() envPath = appInstall.GetEnvPath() composePath = appInstall.GetComposePath() confDir = path.Join(appInstall.GetPath(), "conf") backupConfDir = path.Join(appInstall.GetPath(), "conf_bak") fpmConfDir = path.Join(confDir, "php-fpm.conf") phpDir = path.Join(constant.RuntimeDir, runtime.Type, runtime.Name, "php") oldFmContent, _ = fileOp.GetContent(fpmConfDir) newComposeByte []byte ) envParams := make(map[string]string, len(envs)) handleMap(envs, envParams) envParams["IMAGE_NAME"] = runtime.Image defer func() { if busErr != nil { envParams["IMAGE_NAME"] = oldRuntime.Image _ = env.Write(envParams, envPath) _ = fileOp.WriteFile(composePath, strings.NewReader(appInstall.DockerCompose), 0775) if fileOp.Stat(backupConfDir) { _ = fileOp.DeleteDir(confDir) _ = fileOp.Rename(backupConfDir, confDir) } } }() if busErr = env.Write(envParams, envPath); busErr != nil { return busErr } newComposeByte, busErr = changeServiceName(appDetail.DockerCompose, appInstall.ServiceName) if busErr != nil { return err } if busErr = fileOp.WriteFile(composePath, bytes.NewReader(newComposeByte), 0775); busErr != nil { return busErr } if !req.RetainConfig { if busErr = fileOp.Rename(confDir, backupConfDir); busErr != nil { return busErr } _ = fileOp.CreateDir(confDir, 0755) if busErr = fileOp.CopyFile(path.Join(phpDir, "php-fpm.conf"), confDir); busErr != nil { return busErr } if busErr = fileOp.CopyFile(path.Join(phpDir, "php.ini"), confDir); busErr != nil { _ = fileOp.WriteFile(fpmConfDir, bytes.NewReader(oldFmContent), 0775) return busErr } } if out, err := compose.Up(appInstall.GetComposePath()); err != nil { if out != "" { busErr = errors.New(out) return busErr } busErr = err return busErr } _ = fileOp.DeleteDir(backupConfDir) appInstall.AppDetailId = runtime.AppDetailID appInstall.AppId = appDetail.AppId appInstall.Version = appDetail.Version appInstall.DockerCompose = string(newComposeByte) _ = appInstallRepo.Save(context.Background(), &appInstall) website.RuntimeID = req.RuntimeID return websiteRepo.Save(context.Background(), &website) } func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxFull, err := getNginxFull(&website) if err != nil { return err } includePath := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.PrimaryDomain) absolutePath := path.Join(nginxFull.Install.GetPath(), includePath) fileOp := files.NewFileOp() var oldRewriteContent []byte if !fileOp.Stat(path.Dir(absolutePath)) { if err := fileOp.CreateDir(path.Dir(absolutePath), 0755); 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), 0755); 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), 0755) 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(commonRepo.WithByID(req.WebsiteID)) if err != nil { return nil, err } var contentByte []byte if req.Name == "current" { nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return nil, err } rewriteConfPath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "rewrite", fmt.Sprintf("%s.conf", website.PrimaryDomain)) 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, err = nginx_conf.Rewrites.ReadFile(rewriteFile) if err != nil { return nil, err } } return &response.NginxRewriteRes{ Content: string(contentByte), }, err } func (w WebsiteService) UpdateSiteDir(req request.WebsiteUpdateDir) error { website, err := websiteRepo.GetFirst(commonRepo.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(commonRepo.WithByID(req.ID)) if err != nil { return err } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } absoluteIndexPath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index") chownCmd := fmt.Sprintf("chown -R %s:%s %s", req.User, req.Group, absoluteIndexPath) if cmd.HasNoPasswordSudo() { chownCmd = fmt.Sprintf("sudo %s", chownCmd) } if out, err := cmd.ExecWithTimeOut(chownCmd, 10*time.Second); err != nil { if out != "" { return errors.New(out) } 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 params []response.NginxParam nginxInstall model.AppInstall par *parser.Parser oldContent []byte ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return } params, err = getNginxParamsByKeys(constant.NginxScopeHttp, []string{"proxy_cache"}, &website) if err != nil { return } nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return } fileOp := files.NewFileOp() if len(params) == 0 || len(params[0].Params) == 0 { commonDir := path.Join(nginxInstall.GetPath(), "www", "common", "proxy") proxyTempPath := path.Join(commonDir, "proxy_temp_dir") if !fileOp.Stat(proxyTempPath) { _ = fileOp.CreateDir(proxyTempPath, 0755) } proxyCacheDir := path.Join(commonDir, "proxy_temp_dir") if !fileOp.Stat(proxyCacheDir) { _ = fileOp.CreateDir(proxyCacheDir, 0755) } nginxParams := getNginxParamsFromStaticFile(dto.CACHE, nil) if err = updateNginxConfig(constant.NginxScopeHttp, nginxParams, &website); 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) if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) { err = buserr.New(constant.ErrNameIsExist) return } defer func() { if err != nil { switch req.Operate { case "create": _ = fileOp.DeleteFile(includePath) case "edit": _ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), 0755) } } }() var config *components.Config switch req.Operate { case "create": config, err = parser.NewStringParser(string(nginx_conf.Proxy)).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 location, ok := directives[0].(*components.Location) if !ok { err = errors.New("error") return } location.UpdateDirective("proxy_pass", []string{req.ProxyPass}) location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost}) location.ChangePath(req.Modifier, req.Match) if req.Cache { location.AddCache(req.CacheTime, req.CacheUnit) } else { location.RemoveCache() } if len(req.Replaces) > 0 { location.AddSubFilter(req.Replaces) } else { location.RemoveSubFilter() } if req.SNI { location.UpdateDirective("proxy_ssl_server_name", []string{"on"}) } else { location.UpdateDirective("proxy_ssl_server_name", []string{"off"}) } if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return buserr.WithErr(constant.ErrUpdateBuWebsite, err) } nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias) if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil { return } return } func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) { var ( website model.Website nginxInstall model.AppInstall fileList response.FileInfo ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { return } nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return } includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy") 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() location, ok := directives[0].(*components.Location) if !ok { err = errors.New("error") return } proxyConfig.ProxyPass = location.ProxyPass proxyConfig.Cache = location.Cache if location.CacheTime > 0 { proxyConfig.CacheTime = location.CacheTime proxyConfig.CacheUnit = location.CacheUint } 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" } } res = append(res, proxyConfig) } return } func (w WebsiteService) UpdateProxyFile(req request.NginxProxyUpdate) (err error) { var ( website model.Website nginxFull dto.NginxFull oldRewriteContent []byte ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxFull, err = getNginxFull(&website) if err != nil { return err } includePath := fmt.Sprintf("/www/sites/%s/proxy/%s.conf", website.Alias, req.Name) absolutePath := path.Join(nginxFull.Install.GetPath(), includePath) fileOp := files.NewFileOp() oldRewriteContent, err = fileOp.GetContent(absolutePath) if err != nil { return err } if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), 0755); err != nil { return err } defer func() { if err != nil { _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), 0755) } }() return updateNginxConfig(constant.NginxScopeServer, nil, &website) } func (w WebsiteService) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) { var ( website model.Website nginxInstall model.AppInstall authContent []byte nginxParams []response.NginxParam ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return } nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return } authPath := fmt.Sprintf("/www/sites/%s/auth_basic/auth.pass", website.Alias) absoluteAuthPath := path.Join(nginxInstall.GetPath(), authPath) 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 nginxInstall model.AppInstall params []dto.NginxParam authContent []byte authArray []string ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return } authPath := fmt.Sprintf("/www/sites/%s/auth_basic/auth.pass", website.Alias) absoluteAuthPath := path.Join(nginxInstall.GetPath(), authPath) fileOp := files.NewFileOp() if !fileOp.Stat(path.Dir(absoluteAuthPath)) { _ = fileOp.CreateDir(path.Dir(absoluteAuthPath), 0755) } 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(constant.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(constant.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 nginxInstall model.AppInstall authContent []byte ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return } nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return } fileOp := files.NewFileOp() authDir := fmt.Sprintf("/www/sites/%s/path_auth", website.Alias) absoluteAuthDir := path.Join(nginxInstall.GetPath(), authDir) 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 = location.Match passPath := path.Join(passDir, fmt.Sprintf("%s.pass", name)) authContent, err = fileOp.GetContent(passPath) 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(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } fileOp := files.NewFileOp() authDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "path_auth") if !fileOp.Stat(authDir) { _ = fileOp.CreateDir(authDir, 0755) } passDir := path.Join(authDir, "pass") if !fileOp.Stat(passDir) { _ = fileOp.CreateDir(passDir, 0755) } 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(constant.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("~*", 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, 0644) if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return buserr.WithErr(constant.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 } func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return } nginxFull, err := getNginxFull(&website) if err != nil { return } fileOp := files.NewFileOp() backupContent, err := fileOp.GetContent(nginxFull.SiteConfig.Config.FilePath) if err != nil { return } block := nginxFull.SiteConfig.Config.FindServers()[0] locations := block.FindDirectives("location") for _, location := range locations { loParams := location.GetParameters() if len(loParams) > 1 || loParams[0] == "~" { extendStr := loParams[1] if strings.HasPrefix(extendStr, `.*\.(`) && strings.HasSuffix(extendStr, `)$`) { block.RemoveDirective("location", loParams) } } } if req.Enable { exts := strings.Split(req.Extends, ",") newDirective := components.Directive{ Name: "location", Parameters: []string{"~", fmt.Sprintf(`.*\.(%s)$`, strings.Join(exts, "|"))}, } newBlock := &components.Block{} newBlock.Directives = make([]components.IDirective, 0) if req.Cache { newBlock.Directives = append(newBlock.Directives, &components.Directive{ Name: "expires", Parameters: []string{strconv.Itoa(req.CacheTime) + req.CacheUint}, }) } newBlock.Directives = append(newBlock.Directives, &components.Directive{ Name: "log_not_found", Parameters: []string{"off"}, }) validDir := &components.Directive{ Name: "valid_referers", Parameters: []string{}, } if req.NoneRef { validDir.Parameters = append(validDir.Parameters, "none") } if len(req.ServerNames) > 0 { validDir.Parameters = append(validDir.Parameters, strings.Join(req.ServerNames, " ")) } newBlock.Directives = append(newBlock.Directives, validDir) ifDir := &components.Directive{ Name: "if", Parameters: []string{"($invalid_referer)"}, } ifDir.Block = &components.Block{ Directives: []components.IDirective{ &components.Directive{ Name: "return", Parameters: []string{req.Return}, }, &components.Directive{ Name: "access_log", Parameters: []string{"off"}, }, }, } newBlock.Directives = append(newBlock.Directives, ifDir) newDirective.Block = newBlock block.Directives = append(block.Directives, &newDirective) } if err = nginx.WriteConfig(nginxFull.SiteConfig.Config, nginx.IndentedStyle); err != nil { return } if err = updateNginxConfig(constant.NginxScopeServer, nil, &website); err != nil { _ = fileOp.WriteFile(nginxFull.SiteConfig.Config.FilePath, bytes.NewReader(backupContent), 0755) return } return } func (w WebsiteService) GetAntiLeech(id uint) (*response.NginxAntiLeechRes, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { return nil, err } nginxFull, err := getNginxFull(&website) if err != nil { return nil, err } res := &response.NginxAntiLeechRes{ LogEnable: true, ServerNames: []string{}, } block := nginxFull.SiteConfig.Config.FindServers()[0] locations := block.FindDirectives("location") for _, location := range locations { loParams := location.GetParameters() if len(loParams) > 1 || loParams[0] == "~" { extendStr := loParams[1] if strings.HasPrefix(extendStr, `.*\.(`) && strings.HasSuffix(extendStr, `)$`) { str1 := strings.TrimPrefix(extendStr, `.*\.(`) str2 := strings.TrimSuffix(str1, ")$") res.Extends = strings.Join(strings.Split(str2, "|"), ",") } } lDirectives := location.GetBlock().GetDirectives() for _, lDir := range lDirectives { if lDir.GetName() == "valid_referers" { res.Enable = true params := lDir.GetParameters() for _, param := range params { if param == "none" { res.NoneRef = true continue } if param == "blocked" { res.Blocked = true continue } if param == "server_names" { continue } res.ServerNames = append(res.ServerNames, param) } } if lDir.GetName() == "if" && lDir.GetParameters()[0] == "($invalid_referer)" { directives := lDir.GetBlock().GetDirectives() for _, dir := range directives { if dir.GetName() == "return" { res.Return = strings.Join(dir.GetParameters(), " ") } if dir.GetName() == "access_log" { if strings.Join(dir.GetParameters(), "") == "off" { res.LogEnable = false } } } } if lDir.GetName() == "expires" { res.Cache = true re := regexp.MustCompile(`^(\d+)(\w+)$`) matches := re.FindStringSubmatch(lDir.GetParameters()[0]) if matches == nil { continue } cacheTime, err := strconv.Atoi(matches[1]) if err != nil { continue } unit := matches[2] res.CacheUint = unit res.CacheTime = cacheTime } } } return res, nil } func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error) { var ( website model.Website nginxInstall model.AppInstall oldContent []byte ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return } includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "redirect") fileOp := files.NewFileOp() 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) if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) { err = buserr.New(constant.ErrNameIsExist) return } defer func() { if err != nil { switch req.Operate { case "create": _ = fileOp.DeleteFile(includePath) case "edit": _ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), 0755) } } }() var ( config *components.Config oldPar *parser.Parser ) switch req.Operate { case "create": config = &components.Config{} case "edit": oldPar, err = parser.NewParser(includePath) if err != nil { return } config, err = oldPar.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) } target := req.Target block := &components.Block{} switch req.Type { case "path": if req.KeepPath { target = req.Target + "$1" } else { target = req.Target + "?" } redirectKey := "permanent" if req.Redirect == "302" { redirectKey = "redirect" } block = &components.Block{ Directives: []components.IDirective{ &components.Directive{ Name: "rewrite", Parameters: []string{fmt.Sprintf("^%s(.*)", req.Path), target, redirectKey}, }, }, } case "domain": if req.KeepPath { target = req.Target + "$request_uri" } returnBlock := &components.Block{ Directives: []components.IDirective{ &components.Directive{ Name: "return", Parameters: []string{req.Redirect, target}, }, }, } for _, domain := range req.Domains { block.Directives = append(block.Directives, &components.Directive{ Name: "if", Parameters: []string{"($host", "~", fmt.Sprintf("'^%s')", domain)}, Block: returnBlock, }) } case "404": if req.RedirectRoot { target = "/" } block = &components.Block{ Directives: []components.IDirective{ &components.Directive{ Name: "error_page", Parameters: []string{"404", "=", "@notfound"}, }, &components.Directive{ Name: "location", Parameters: []string{"@notfound"}, Block: &components.Block{ Directives: []components.IDirective{ &components.Directive{ Name: "return", Parameters: []string{req.Redirect, target}, }, }, }, }, }, } } config.FilePath = includePath config.Block = block if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return buserr.WithErr(constant.ErrUpdateBuWebsite, err) } nginxInclude := fmt.Sprintf("/www/sites/%s/redirect/*.conf", website.Alias) if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil { return } return } func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig, err error) { var ( website model.Website nginxInstall model.AppInstall fileList response.FileInfo ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { return } nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return } includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "redirect") 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 { redirectConfig := response.NginxRedirectConfig{ WebsiteID: website.ID, } parts := strings.Split(configFile.Name, ".") redirectConfig.Name = parts[0] if parts[1] == "conf" { redirectConfig.Enable = true } else { redirectConfig.Enable = false } redirectConfig.FilePath = configFile.Path content, err = fileOp.GetContent(configFile.Path) if err != nil { return } redirectConfig.Content = string(content) config, err = parser.NewStringParser(string(content)).Parse() if err != nil { return } dirs := config.GetDirectives() if len(dirs) > 0 { firstName := dirs[0].GetName() switch firstName { case "if": for _, ifDir := range dirs { params := ifDir.GetParameters() if len(params) > 2 && params[0] == "($host" { domain := strings.Trim(strings.Trim(params[2], "'"), "^") redirectConfig.Domains = append(redirectConfig.Domains, domain) if len(redirectConfig.Domains) > 1 { continue } redirectConfig.Type = "domain" } childDirs := ifDir.GetBlock().GetDirectives() for _, dir := range childDirs { if dir.GetName() == "return" { dirParams := dir.GetParameters() if len(dirParams) > 1 { redirectConfig.Redirect = dirParams[0] if strings.HasSuffix(dirParams[1], "$request_uri") { redirectConfig.KeepPath = true redirectConfig.Target = strings.TrimSuffix(dirParams[1], "$request_uri") } else { redirectConfig.KeepPath = false redirectConfig.Target = dirParams[1] } } } } } case "rewrite": redirectConfig.Type = "path" for _, pathDir := range dirs { if pathDir.GetName() == "rewrite" { params := pathDir.GetParameters() if len(params) > 2 { redirectConfig.Path = strings.Trim(strings.Trim(params[0], "^"), "(.*)") if strings.HasSuffix(params[1], "$1") { redirectConfig.KeepPath = true redirectConfig.Target = strings.TrimSuffix(params[1], "$1") } else { redirectConfig.KeepPath = false redirectConfig.Target = strings.TrimSuffix(params[1], "?") } if params[2] == "permanent" { redirectConfig.Redirect = "301" } else { redirectConfig.Redirect = "302" } } } } case "error_page": redirectConfig.Type = "404" for _, errDir := range dirs { if errDir.GetName() == "location" { childDirs := errDir.GetBlock().GetDirectives() for _, dir := range childDirs { if dir.GetName() == "return" { dirParams := dir.GetParameters() if len(dirParams) > 1 { redirectConfig.Redirect = dirParams[0] if strings.HasSuffix(dirParams[1], "$request_uri") { redirectConfig.KeepPath = true redirectConfig.Target = strings.TrimSuffix(dirParams[1], "$request_uri") redirectConfig.RedirectRoot = false } else { redirectConfig.KeepPath = false redirectConfig.Target = dirParams[1] redirectConfig.RedirectRoot = redirectConfig.Target == "/" } } } } } } } } res = append(res, redirectConfig) } return } func (w WebsiteService) UpdateRedirectFile(req request.NginxRedirectUpdate) (err error) { var ( website model.Website nginxFull dto.NginxFull oldRewriteContent []byte ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxFull, err = getNginxFull(&website) if err != nil { return err } includePath := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, req.Name) absolutePath := path.Join(nginxFull.Install.GetPath(), includePath) fileOp := files.NewFileOp() oldRewriteContent, err = fileOp.GetContent(absolutePath) if err != nil { return err } if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), 0755); err != nil { return err } defer func() { if err != nil { _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), 0755) } }() return updateNginxConfig(constant.NginxScopeServer, nil, &website) } func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return nil, err } res := &response.WebsiteDirConfig{} nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return nil, err } absoluteIndexPath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index") var appFs = afero.NewOsFs() info, err := appFs.Stat(absoluteIndexPath) if err != nil { return nil, err } res.User = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Uid), 10) res.UserGroup = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Gid), 10) indexFiles, err := os.ReadDir(absoluteIndexPath) if err != nil { return nil, err } res.Dirs = []string{"/"} for _, file := range indexFiles { if !file.IsDir() || file.Name() == "node_modules" || file.Name() == "vendor" { continue } res.Dirs = append(res.Dirs, fmt.Sprintf("/%s", file.Name())) fileInfo, _ := file.Info() if fileInfo.Sys().(*syscall.Stat_t).Uid != 1000 || fileInfo.Sys().(*syscall.Stat_t).Gid != 1000 { res.Msg = i18n.GetMsgByKey("ErrPathPermission") } childFiles, _ := os.ReadDir(absoluteIndexPath + "/" + file.Name()) for _, childFile := range childFiles { if !childFile.IsDir() { continue } childInfo, _ := childFile.Info() if childInfo.Sys().(*syscall.Stat_t).Uid != 1000 || childInfo.Sys().(*syscall.Stat_t).Gid != 1000 { res.Msg = i18n.GetMsgByKey("ErrPathPermission") } res.Dirs = append(res.Dirs, fmt.Sprintf("/%s/%s", file.Name(), childFile.Name())) } } return res, nil } func (w WebsiteService) GetDefaultHtml(resourceType string) (*response.WebsiteHtmlRes, error) { nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return nil, err } rootPath := path.Join(nginxInstall.GetPath(), "root") fileOp := files.NewFileOp() defaultPath := path.Join(rootPath, "default") if !fileOp.Stat(defaultPath) { _ = fileOp.CreateDir(defaultPath, 0755) } res := &response.WebsiteHtmlRes{} switch resourceType { case "404": resourcePath := path.Join(defaultPath, "404.html") if content, _ := getResourceContent(fileOp, resourcePath); content != "" { res.Content = content return res, nil } res.Content = string(nginx_conf.NotFoundHTML) return res, nil case "php": resourcePath := path.Join(defaultPath, "index.php") if content, _ := getResourceContent(fileOp, resourcePath); content != "" { res.Content = content return res, nil } res.Content = string(nginx_conf.IndexPHP) return res, nil case "index": resourcePath := path.Join(defaultPath, "index.html") if content, _ := getResourceContent(fileOp, resourcePath); content != "" { res.Content = content return res, nil } res.Content = string(nginx_conf.Index) return res, nil case "domain404": resourcePath := path.Join(rootPath, "404.html") if content, _ := getResourceContent(fileOp, resourcePath); content != "" { res.Content = content return res, nil } res.Content = string(nginx_conf.DomainNotFoundHTML) return res, nil case "stop": resourcePath := path.Join(rootPath, "stop", "index.html") if content, _ := getResourceContent(fileOp, resourcePath); content != "" { res.Content = content return res, nil } res.Content = string(nginx_conf.StopHTML) return res, nil } return res, nil } func (w WebsiteService) UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error { nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } rootPath := path.Join(nginxInstall.GetPath(), "root") fileOp := files.NewFileOp() defaultPath := path.Join(rootPath, "default") if !fileOp.Stat(defaultPath) { _ = fileOp.CreateDir(defaultPath, 0755) } var resourcePath string switch req.Type { case "404": resourcePath = path.Join(defaultPath, "404.html") case "php": resourcePath = path.Join(defaultPath, "index.php") case "index": resourcePath = path.Join(defaultPath, "index.html") case "domain404": resourcePath = path.Join(rootPath, "404.html") case "stop": resourcePath = path.Join(rootPath, "stop", "index.html") default: return nil } return fileOp.SaveFile(resourcePath, req.Content, 0644) } func (w WebsiteService) GetLoadBalances(id uint) ([]dto.NginxUpstream, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(id)) if err != nil { return nil, err } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return nil, err } includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "upstream") 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 } } var servers []dto.NginxUpstreamServer for _, ups := range up.UpstreamServers { server := dto.NginxUpstreamServer{ Server: ups.Address, } parameters := ups.Parameters if weight, ok := parameters["weight"]; ok { num, err := strconv.Atoi(weight) if err == nil { server.Weight = num } } if maxFails, ok := parameters["max_fails"]; ok { num, err := strconv.Atoi(maxFails) if err == nil { server.MaxFails = num } } if failTimeout, ok := parameters["fail_timeout"]; ok { server.FailTimeout = failTimeout } if maxConns, ok := parameters["max_conns"]; ok { num, err := strconv.Atoi(maxConns) if err == nil { server.MaxConns = num } } for _, flag := range ups.Flags { server.Flag = flag } servers = append(servers, server) } upstream.Servers = servers } } res = append(res, upstream) } return res, nil } func (w WebsiteService) CreateLoadBalance(req request.WebsiteLBCreate) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "upstream") fileOp := files.NewFileOp() if !fileOp.Stat(includeDir) { _ = fileOp.CreateDir(includeDir, 0644) } filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) if fileOp.Stat(filePath) { return buserr.New(constant.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{}) } servers := make([]*components.UpstreamServer, 0) for _, server := range req.Servers { upstreamServer := &components.UpstreamServer{ Address: server.Server, } parameters := make(map[string]string) if server.Weight > 0 { parameters["weight"] = strconv.Itoa(server.Weight) } if server.MaxFails > 0 { parameters["max_fails"] = strconv.Itoa(server.MaxFails) } if server.FailTimeout != "" { parameters["fail_timeout"] = server.FailTimeout } if server.MaxConns > 0 { parameters["max_conns"] = strconv.Itoa(server.MaxConns) } if server.Flag != "" { upstreamServer.Flags = []string{server.Flag} } upstreamServer.Parameters = parameters servers = append(servers, upstreamServer) } upstream.UpstreamServers = 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(constant.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(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "upstream") fileOp := files.NewFileOp() filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) if !fileOp.Stat(filePath) { return nil } 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{}) } var servers []*components.UpstreamServer for _, server := range req.Servers { upstreamServer := &components.UpstreamServer{ Address: server.Server, } parameters := make(map[string]string) if server.Weight > 0 { parameters["weight"] = strconv.Itoa(server.Weight) } if server.MaxFails > 0 { parameters["max_fails"] = strconv.Itoa(server.MaxFails) } if server.FailTimeout != "" { parameters["fail_timeout"] = server.FailTimeout } if server.MaxConns > 0 { parameters["max_conns"] = strconv.Itoa(server.MaxConns) } if server.Flag != "" { upstreamServer.Flags = []string{server.Flag} } upstreamServer.Parameters = parameters servers = append(servers, upstreamServer) } up.UpstreamServers = servers } } if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { return buserr.WithErr(constant.ErrUpdateBuWebsite, err) } if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { return err } return nil } func (w WebsiteService) DeleteLoadBalance(req request.WebsiteLBDelete) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "upstream") 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(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "upstream") 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), 0755); err != nil { return err } defer func() { if err != nil { _ = fileOp.WriteFile(filePath, bytes.NewReader(oldContent), 0755) } }() return opNginx(nginxInstall.ContainerName, constant.NginxReload) }