package service import ( "bufio" "bytes" "context" "crypto/x509" "encoding/json" "encoding/pem" "errors" "fmt" "os" "path" "reflect" "regexp" "strconv" "strings" "syscall" "time" "github.com/1Panel-dev/1Panel/backend/i18n" "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/spf13/afero" "github.com/1Panel-dev/1Panel/backend/utils/compose" "github.com/1Panel-dev/1Panel/backend/utils/env" "github.com/1Panel-dev/1Panel/backend/app/api/v1/helper" "github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/nginx" "github.com/1Panel-dev/1Panel/backend/utils/nginx/components" "github.com/1Panel-dev/1Panel/backend/utils/nginx/parser" "github.com/1Panel-dev/1Panel/cmd/server/nginx_conf" "golang.org/x/crypto/bcrypt" "gopkg.in/ini.v1" "gorm.io/gorm" "github.com/1Panel-dev/1Panel/backend/app/dto/request" "github.com/1Panel-dev/1Panel/backend/app/dto/response" "github.com/1Panel-dev/1Panel/backend/app/repo" "github.com/1Panel-dev/1Panel/backend/buserr" "github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/model" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/utils/files" ) type WebsiteService struct { } type IWebsiteService interface { PageWebsite(req request.WebsiteSearch) (int64, []response.WebsiteDTO, error) GetWebsites() ([]response.WebsiteDTO, error) CreateWebsite(create request.WebsiteCreate) error OpWebsite(req request.WebsiteOp) error GetWebsiteOptions() ([]string, 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 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) GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error) UpdateWafConfig(req request.WebsiteWafUpdate) error UpdateWafFile(req request.WebsiteWafFileUpdate) (err 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) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) UpdateAuthBasic(req request.NginxAuthUpdate) (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) } func NewIWebsiteService() IWebsiteService { return &WebsiteService{} } func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []response.WebsiteDTO, error) { var ( websiteDTOs []response.WebsiteDTO 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 != "" { 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 ) switch web.Type { case constant.Deployment: appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(web.AppInstallID)) if err != nil { return 0, nil, err } appName = appInstall.Name case constant.Runtime: runtime, err := runtimeRepo.GetFirst(commonRepo.WithByID(web.RuntimeID)) if err != nil { return 0, nil, err } runtimeName = runtime.Name } sitePath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "www", "sites", web.Alias) websiteDTOs = append(websiteDTOs, response.WebsiteDTO{ Website: web, AppName: appName, RuntimeName: runtimeName, SitePath: sitePath, }) } return total, websiteDTOs, nil } func (w WebsiteService) GetWebsites() ([]response.WebsiteDTO, error) { var websiteDTOs []response.WebsiteDTO websites, err := websiteRepo.List() if err != nil { return nil, err } for _, web := range websites { websiteDTOs = append(websiteDTOs, response.WebsiteDTO{ Website: web, }) } return websiteDTOs, nil } func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) { if exist, _ := websiteRepo.GetBy(websiteRepo.WithDomain(create.PrimaryDomain)); len(exist) > 0 { return buserr.New(constant.ErrDomainIsExist) } if exist, _ := websiteRepo.GetBy(websiteRepo.WithAlias(create.Alias)); len(exist) > 0 { return buserr.New(constant.ErrAliasIsExist) } if exist, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithDomain(create.PrimaryDomain)); len(exist) > 0 { return buserr.New(constant.ErrDomainIsExist) } nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } defaultHttpPort := nginxInstall.HttpPort defaultDate, _ := time.Parse(constant.DateLayout, constant.DefaultDate) website := &model.Website{ PrimaryDomain: create.PrimaryDomain, Type: create.Type, Alias: create.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 ) defer func() { if err != nil { if website.AppInstallID > 0 { req := request.AppInstalledOperate{ InstallId: website.AppInstallID, Operate: constant.Delete, ForceDelete: true, } if err := NewIAppInstalledService().Operate(req); err != nil { global.LOG.Errorf(err.Error()) } } } }() 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 tx, installCtx := getTxAndContext() install, err = NewIAppService().Install(installCtx, req) if err != nil { tx.Rollback() return err } tx.Commit() 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 } appInstall = &install website.AppInstallID = appInstall.ID website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) } case constant.Runtime: runtime, err = runtimeRepo.GetFirst(commonRepo.WithByID(create.RuntimeID)) if err != nil { return err } website.RuntimeID = runtime.ID if runtime.Resource == constant.ResourceAppstore { var ( req request.AppInstallCreate nginxInstall model.AppInstall install *model.AppInstall ) reg, _ := regexp.Compile(`[^a-z0-9_-]+`) req.Name = reg.ReplaceAllString(strings.ToLower(create.PrimaryDomain), "") req.AppDetailId = create.AppInstall.AppDetailId req.Params = create.AppInstall.Params req.Params["IMAGE_NAME"] = runtime.Image req.AppContainerConfig = create.AppInstall.AppContainerConfig nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www") tx, installCtx := getTxAndContext() install, err = NewIAppService().Install(installCtx, req) if err != nil { tx.Rollback() return err } tx.Commit() 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 } } var domains []model.WebsiteDomain domains = append(domains, model.WebsiteDomain{Domain: website.PrimaryDomain, Port: defaultHttpPort}) otherDomainArray := strings.Split(create.OtherDomains, "\n") for _, domain := range otherDomainArray { if domain == "" { continue } domainModel, err := getDomain(domain, defaultHttpPort) if err != nil { return err } if reflect.DeepEqual(domainModel, model.WebsiteDomain{}) { continue } domains = append(domains, domainModel) } if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil { return err } tx, ctx := helper.GetTxAndContext() defer tx.Rollback() if err = websiteRepo.Create(ctx, website); err != nil { return err } for i := range domains { domains[i].WebsiteID = website.ID } if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil { return err } tx.Commit() return nil } 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() ([]string, error) { webs, err := websiteRepo.GetBy() if err != nil { return nil, err } var datas []string for _, web := range webs { datas = append(datas, web.PrimaryDomain) } 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 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 err := delNginxConfig(website, req.ForceDelete); err != nil { return err } if checkIsLinkApp(website) && req.DeleteApp { appInstall, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } if !reflect.DeepEqual(model.AppInstall{}, appInstall) { if err := deleteAppInstall(appInstall, true, req.ForceDelete, true); err != nil && !req.ForceDelete { return err } } } tx, ctx := helper.GetTxAndContext() defer tx.Rollback() _ = backupRepo.DeleteRecord(ctx, commonRepo.WithByType("website"), commonRepo.WithByName(website.Alias)) 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() if req.DeleteBackup { localDir, _ := loadLocalDir() backupDir := path.Join(localDir, fmt.Sprintf("website/%s", website.Alias)) if _, err := os.Stat(backupDir); err == nil { _ = os.RemoveAll(backupDir) } global.LOG.Infof("delete website %s backups successful", website.Alias) } 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) CreateWebsiteDomain(create request.WebsiteDomainCreate) (model.WebsiteDomain, error) { var ( domainModel model.WebsiteDomain ports []int domains []string ) httpPort, _, err := getAppInstallPort(constant.AppOpenresty) if err != nil { return domainModel, err } if existDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(create.Port)); len(existDomains) == 0 { if existAppInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(create.Port)); !reflect.DeepEqual(existAppInstall, model.AppInstall{}) { return domainModel, buserr.WithMap(constant.ErrPortInOtherApp, map[string]interface{}{"port": create.Port, "apps": existAppInstall.App.Name}, nil) } if common.ScanPort(create.Port) { return domainModel, buserr.WithDetail(constant.ErrPortInUsed, create.Port, nil) } } website, err := websiteRepo.GetFirst(commonRepo.WithByID(create.WebsiteID)) if err != nil { return domainModel, err } if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(create.WebsiteID), websiteDomainRepo.WithPort(create.Port)); len(oldDomains) == 0 { ports = append(ports, create.Port) } domains = append(domains, create.Domain) if err := addListenAndServerName(website, ports, domains); err != nil { return domainModel, err } domainModel = model.WebsiteDomain{ Domain: create.Domain, Port: create.Port, WebsiteID: create.WebsiteID, } if create.Port != httpPort { go func() { _ = OperateFirewallPort(nil, []int{create.Port}) }() } return domainModel, websiteDomainRepo.Create(context.TODO(), &domainModel) } 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 } } 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 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"}, &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] } } 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 ) res.Enable = req.Enable res.SSLProtocol = req.SSLProtocol res.Algorithm = req.Algorithm if !req.Enable { website.Protocol = constant.ProtocolHTTP website.WebsiteSSLID = 0 _, httpsPort, err := getAppInstallPort(constant.AppOpenresty) if err != nil { return nil, err } httpsPortStr := strconv.Itoa(httpsPort) if err := deleteListenAndServerName(website, []string{httpsPortStr, "[::]:" + httpsPortStr}, []string{}); 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 { websiteSSL, err = websiteSSLRepo.GetFirst(commonRepo.WithByID(req.WebsiteSSLID)) if err != nil { return nil, err } website.WebsiteSSLID = websiteSSL.ID res.SSL = websiteSSL } 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) } for _, id := range checkIds { if err := syncByID(id); err != nil { return nil, err } } if len(checkIds) > 0 { installList, _ := appInstallRepo.ListBy(commonRepo.WithIdsIn(checkIds)) for _, install := range installList { res = append(res, response.WebsitePreInstallCheck{ Name: install.Name, Status: install.Status, Version: install.Version, AppName: install.App.Name, }) if install.Status != constant.StatusRunning { showErr = true } } } if showErr { return res, nil } else { return nil, nil } } func (w WebsiteService) GetWafConfig(req request.WebsiteWafReq) (response.WebsiteWafConfig, error) { var res response.WebsiteWafConfig website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return res, nil } res.Enable = true if req.Key != "" { params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"set"}, &website) if err != nil { return res, nil } for _, param := range params { if param.Params[0] == req.Key { res.Enable = len(param.Params) > 1 && param.Params[1] == "on" break } } } nginxFull, err := getNginxFull(&website) if err != nil { return res, nil } filePath := path.Join(nginxFull.SiteDir, "sites", website.Alias, "waf", "rules", req.Rule+".json") content, err := os.ReadFile(filePath) if err != nil { return res, nil } res.Content = string(content) return res, nil } func (w WebsiteService) UpdateWafConfig(req request.WebsiteWafUpdate) error { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } updateValue := "on" if !req.Enable { updateValue = "off" } return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{ {Name: "set", Params: []string{req.Key, updateValue}}, }, &website) } 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) 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, }) if err := deleteNginxConfig(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) switch req.LogType { case constant.AccessLog: website.AccessLog = true case constant.ErrorLog: key = "error_log" website.ErrorLog = true } if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: key, Params: []string{logPath}}}, &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(req.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) ) 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 } if busErr = fileOp.WriteFile(composePath, strings.NewReader(appDetail.DockerCompose), 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 = appDetail.DockerCompose _ = 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, 1*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 = parser.NewStringParser(string(nginx_conf.Proxy)).Parse() case "edit": par, err = parser.NewParser(includePath) if err != nil { return } config = par.Parse() 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 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 = parser.NewStringParser(string(content)).Parse() 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 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) 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) 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) 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 = oldPar.Parse() 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.KeepPath && !req.RedirectRoot { target = req.Target + "$request_uri" } if req.RedirectRoot { target = "/" } else { if req.KeepPath { target = req.Target + "$request_uri" } } 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{"301", 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 = parser.NewStringParser(string(content)).Parse() 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) UpdateWafFile(req request.WebsiteWafFileUpdate) (err error) { var ( website model.Website nginxInstall model.AppInstall ) website, err = websiteRepo.GetFirst(commonRepo.WithByID(req.WebsiteID)) if err != nil { return err } nginxInstall, err = getAppInstallByKey(constant.AppOpenresty) if err != nil { return } rulePath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "waf", "rules", fmt.Sprintf("%s.json", req.Type)) return files.NewFileOp().WriteFile(rulePath, strings.NewReader(req.Content), 0755) } func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) { website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) if err != nil { return nil, err } res := &response.WebsiteDirConfig{} nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return nil, err } absoluteIndexPath := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index") var appFs = afero.NewOsFs() info, err := appFs.Stat(absoluteIndexPath) if err != nil { return nil, err } res.User = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Uid), 10) res.UserGroup = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Gid), 10) indexFiles, err := os.ReadDir(absoluteIndexPath) if err != nil { return nil, err } res.Dirs = []string{"/"} for _, file := range indexFiles { if !file.IsDir() { continue } res.Dirs = append(res.Dirs, fmt.Sprintf("/%s", file.Name())) fileInfo, _ := file.Info() if fileInfo.Sys().(*syscall.Stat_t).Uid != 1000 || fileInfo.Sys().(*syscall.Stat_t).Gid != 1000 { res.Msg = i18n.GetMsgByKey("ErrPathPermission") } childFiles, _ := os.ReadDir(absoluteIndexPath + "/" + file.Name()) for _, childFile := range childFiles { if !childFile.IsDir() { continue } childInfo, _ := childFile.Info() if childInfo.Sys().(*syscall.Stat_t).Uid != 1000 || childInfo.Sys().(*syscall.Stat_t).Gid != 1000 { res.Msg = i18n.GetMsgByKey("ErrPathPermission") } res.Dirs = append(res.Dirs, fmt.Sprintf("/%s/%s", file.Name(), childFile.Name())) } } return res, nil }