package service import ( "fmt" "github.com/1Panel-dev/1Panel/agent/utils/nginx" "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" "io" "net/http" "os" "path" "strconv" "strings" "time" "github.com/1Panel-dev/1Panel/agent/app/task" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/global" "github.com/subosito/gotenv" "github.com/1Panel-dev/1Panel/agent/utils/compose" "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/dto" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/utils/files" ) type NginxService struct { } type INginxService interface { GetNginxConfig() (*response.NginxFile, error) GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error) UpdateConfigByScope(req request.NginxConfigUpdate) error GetStatus() (response.NginxStatus, error) UpdateConfigFile(req request.NginxConfigFileUpdate) error Build(req request.NginxBuildReq) error GetModules() (*response.NginxBuildConfig, error) UpdateModule(req request.NginxModuleUpdate) error OperateDefaultHTTPs(req request.NginxDefaultHTTPSUpdate) error GetDefaultHttpsStatus() (*response.NginxConfigRes, error) } func NewINginxService() INginxService { return &NginxService{} } func (n NginxService) GetNginxConfig() (*response.NginxFile, error) { nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return nil, err } configPath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, nginxModuleConfDir, "nginx.conf") byteContent, err := files.NewFileOp().GetContent(configPath) if err != nil { return nil, err } return &response.NginxFile{Content: string(byteContent)}, nil } func (n NginxService) GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error) { keys, ok := dto.ScopeKeyMap[req.Scope] if !ok || len(keys) == 0 { return nil, nil } return getNginxParamsByKeys(constant.NginxScopeHttp, keys, nil) } func (n NginxService) UpdateConfigByScope(req request.NginxConfigUpdate) error { keys, ok := dto.ScopeKeyMap[req.Scope] if !ok || len(keys) == 0 { return nil } return updateNginxConfig(constant.NginxScopeHttp, getNginxParams(req.Params, keys), nil) } func (n NginxService) GetStatus() (response.NginxStatus, error) { httpPort, _, err := getAppInstallPort(constant.AppOpenresty) if err != nil { return response.NginxStatus{}, err } url := "http://127.0.0.1/nginx_status" if httpPort != 80 { url = fmt.Sprintf("http://127.0.0.1:%v/nginx_status", httpPort) } res, err := http.Get(url) if err != nil || res.StatusCode > 300 { return response.NginxStatus{}, err } defer res.Body.Close() content, err := io.ReadAll(res.Body) if err != nil { return response.NginxStatus{}, err } var status response.NginxStatus resArray := strings.Split(string(content), " ") active, err := strconv.Atoi(resArray[2]) if err == nil { status.Active = active } accepts, err := strconv.Atoi(resArray[7]) if err == nil { status.Accepts = accepts } handled, err := strconv.Atoi(resArray[8]) if err == nil { status.Handled = handled } requests, err := strconv.Atoi(resArray[9]) if err == nil { status.Requests = requests } reading, err := strconv.Atoi(resArray[11]) if err == nil { status.Reading = reading } writing, err := strconv.Atoi(resArray[13]) if err == nil { status.Writing = writing } waiting, err := strconv.Atoi(resArray[15]) if err == nil { status.Waiting = waiting } return status, nil } func (n NginxService) UpdateConfigFile(req request.NginxConfigFileUpdate) error { fileOp := files.NewFileOp() nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } filePath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, nginxModuleConfDir, "nginx.conf") if req.Backup { backupPath := path.Join(path.Dir(filePath), "bak") if !fileOp.Stat(backupPath) { if err := fileOp.CreateDir(backupPath, constant.DirPerm); err != nil { return err } } newFile := path.Join(backupPath, "nginx.bak"+"-"+time.Now().Format("2006-01-02-15-04-05")) if err := fileOp.Copy(filePath, backupPath); err != nil { return err } if err := fileOp.Rename(path.Join(backupPath, "nginx.conf"), newFile); err != nil { return err } } oldContent, err := os.ReadFile(filePath) if err != nil { return err } if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), constant.DirPerm); err != nil { return err } if status, err := checkContainerStatus(nginxInstall.ContainerName); err == nil && status != "running" { if out, err := compose.DownAndUp(nginxInstall.GetComposePath()); err != nil { _ = fileOp.SaveFile(filePath, string(oldContent), constant.DirPerm) return fmt.Errorf("nginx restart failed: %v", out) } else { return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName) } } return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName) } func (n NginxService) Build(req request.NginxBuildReq) error { nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } taskName := task.GetTaskName(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp) if err = task.CheckTaskIsExecuting(taskName); err != nil { return err } if err = task.CheckScopeTaskIsExecuting(task.TaskScopeApp, nginxInstall.ID); err != nil { return err } buildPath := path.Join(nginxInstall.GetPath(), nginxModuleBuildDir) if !files.NewFileOp().Stat(buildPath) { return buserr.New("ErrBuildDirNotFound") } buildTask, err := task.NewTaskWithOps(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp, req.TaskID, nginxInstall.ID) if err != nil { return err } buildTask.AddSubTaskWithOps("", func(t *task.Task) error { return executeNginxModuleBuild(nginxInstall, req.Modules, req.Force, req.Mirror, t, true) }, nil, 0, 120*time.Minute) go func() { _ = buildTask.Execute() }() return nil } func (n NginxService) GetModules() (*response.NginxBuildConfig, error) { nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return nil, err } modules, err := loadNginxModules(nginxInstall) if err != nil { return nil, err } target, targetWarning, targetErr := resolveNginxModuleTarget(nginxInstall) if targetWarning != "" { global.LOG.Warn(targetWarning) } runtimeTarget := target runtimeTarget.BuilderDigest = "" setNginxModuleTargetKey(&runtimeTarget) runtimeTargetErr := targetErr if runtimeTarget.OpenRestyVersion != "" { runtimeTargetErr = nil } var resList []response.NginxModule for _, module := range modules { buildStatus := nginxModuleStatusPending loadStatus := nginxModuleLoadDisabled var artifacts []dto.NginxModuleArtifact if module.BuildMode == nginxModuleBuildStatic && runtimeTargetErr == nil { if build := findCurrentNginxModuleBuild(module, runtimeTarget); build != nil && build.Status == nginxModuleStatusReady { buildStatus = nginxModuleStatusReady if module.Enable { loadStatus = nginxModuleLoadEnabled } } else if findLatestNginxModuleBuild(module, runtimeTarget) != nil { if module.Enable { loadStatus = nginxModuleLoadEnabled } } } else if targetErr == nil { if build := findCurrentNginxModuleBuild(module, target); build != nil { buildStatus = build.Status artifacts = build.Artifacts if build.Status == nginxModuleStatusReady { if module.Enable { loadStatus = nginxModuleLoadEnabled } } } else if latestBuild := findLatestNginxModuleBuild(module, target); latestBuild != nil { artifacts = latestBuild.Artifacts if module.Enable { loadStatus = nginxModuleLoadEnabled } } } if module.BuildMode != nginxModuleBuildStatic && module.LastError != "" { buildStatus = nginxModuleStatusFailed } resList = append(resList, response.NginxModule{ Name: module.Name, Custom: module.Custom, Script: module.Script, Packages: strings.Join(module.Packages, ","), Params: module.Params, Enable: module.Enable, BuildMode: module.BuildMode, Provider: module.Provider, LoadOrder: module.LoadOrder, BuildStatus: buildStatus, LoadStatus: loadStatus, Artifacts: artifacts, LastError: module.LastError, }) } envs, err := gotenv.Read(nginxInstall.GetEnvPath()) if err != nil { return nil, err } return &response.NginxBuildConfig{ Mirror: envs["CONTAINER_PACKAGE_URL"], DynamicSupported: nginxModuleDynamicSupported(nginxInstall), Modules: resList, }, nil } func applyNginxModuleUpdate(modules []dto.NginxModule, req request.NginxModuleUpdate) ([]dto.NginxModule, *dto.NginxModule, error) { switch req.Operate { case nginxModuleOperateCreate: if err := validateNginxModuleBuildMode(dto.NginxModule{Name: req.Name, BuildMode: req.BuildMode}); err != nil { return nil, nil, err } for _, module := range modules { if module.Name == req.Name { return nil, nil, buserr.New("ErrNameIsExist") } } modules = append(modules, dto.NginxModule{ Name: req.Name, Custom: true, Script: req.Script, Packages: strings.Split(req.Packages, ","), Params: req.Params, Enable: req.Enable, BuildMode: req.BuildMode, Provider: req.Provider, LoadOrder: req.LoadOrder, }) case nginxModuleOperateUpdate: for i := range modules { if modules[i].Name != req.Name { continue } if modules[i].Custom { if err := validateNginxModuleBuildMode(dto.NginxModule{Name: req.Name, BuildMode: req.BuildMode}); err != nil { return nil, nil, err } } else { modules[i].Enable = req.Enable return modules, nil, nil } modules[i].Enable = req.Enable modules[i].Script = req.Script modules[i].Packages = strings.Split(req.Packages, ",") modules[i].Params = req.Params modules[i].BuildMode = req.BuildMode modules[i].Provider = req.Provider modules[i].LoadOrder = req.LoadOrder return modules, nil, nil } return nil, nil, fmt.Errorf("OpenResty module %s not found", req.Name) case nginxModuleOperateDelete: for i := range modules { if modules[i].Name != req.Name { continue } if !modules[i].Custom { return nil, nil, fmt.Errorf("built-in OpenResty module %s cannot be deleted", req.Name) } deleted := modules[i] modules = append(modules[:i], modules[i+1:]...) return modules, &deleted, nil } return nil, nil, fmt.Errorf("OpenResty module %s not found", req.Name) default: return nil, nil, fmt.Errorf("unsupported OpenResty module operation %q", req.Operate) } return modules, nil, nil } func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error { nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } if err = task.CheckScopeTaskIsExecuting(task.TaskScopeApp, nginxInstall.ID); err != nil { return err } modules, err := loadNginxModules(nginxInstall) if err != nil { return err } oldModules := cloneNginxModules(modules) modules, deletedModule, err := applyNginxModuleUpdate(modules, req) if err != nil { return err } if err = saveNginxModules(nginxInstall, modules); err != nil { return err } if err = reconcileDynamicNginxModuleConfig(nginxInstall, modules, true); err != nil { _ = saveNginxModules(nginxInstall, oldModules) _ = reconcileDynamicNginxModuleConfig(nginxInstall, oldModules, false) return err } if deletedModule != nil { _ = removeNginxModuleArtifacts(nginxInstall, *deletedModule) } return nil } func (n NginxService) OperateDefaultHTTPs(req request.NginxDefaultHTTPSUpdate) error { appInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return err } websites, _ := websiteRepo.List() hasDefaultWebsite := false for _, website := range websites { if website.DefaultServer { hasDefaultWebsite = true break } } defaultConfigPath := path.Join(appInstall.GetPath(), nginxModuleConfDir, "default", "00.default.conf") content, err := os.ReadFile(defaultConfigPath) if err != nil { return err } switch req.Operate { case "enable": if req.SSLRejectHandshake { defaultWebsite, _ := websiteRepo.GetFirst(websiteRepo.WithDefaultServer()) if defaultWebsite.ID > 0 { return buserr.New("ErrDefaultWebsite") } } if err := handleSSLConfig(&appInstall, hasDefaultWebsite, req.SSLRejectHandshake); err != nil { return err } case "disable": defaultConfig, err := parser.NewStringParser(string(content)).Parse() if err != nil { return err } defaultConfig.FilePath = defaultConfigPath defaultServer := defaultConfig.FindServers()[0] defaultServer.RemoveListen(fmt.Sprintf("%d", appInstall.HttpsPort)) defaultServer.RemoveListen(fmt.Sprintf("[::]:%d", appInstall.HttpsPort)) defaultServer.RemoveDirective("include", []string{"/usr/local/openresty/nginx/conf/ssl/root_ssl.conf"}) defaultServer.RemoveDirective("http2", []string{"on"}) defaultServer.RemoveDirective("ssl_reject_handshake", []string{"on"}) if err = nginx.WriteConfig(defaultConfig, nginx.IndentedStyle); err != nil { return err } } return nginxCheckAndReload(string(content), defaultConfigPath, appInstall.ContainerName) } func (n NginxService) GetDefaultHttpsStatus() (*response.NginxConfigRes, error) { appInstall, err := getAppInstallByKey(constant.AppOpenresty) if err != nil { return nil, err } defaultConfigPath := path.Join(appInstall.GetPath(), nginxModuleConfDir, "default", "00.default.conf") content, err := os.ReadFile(defaultConfigPath) if err != nil { return nil, err } defaultConfig, err := parser.NewStringParser(string(content)).Parse() if err != nil { return nil, err } defaultConfig.FilePath = defaultConfigPath defaultServer := defaultConfig.FindServers()[0] res := &response.NginxConfigRes{} for _, directive := range defaultServer.GetDirectives() { if directive.GetName() == "include" && directive.GetParameters()[0] == "/usr/local/openresty/nginx/conf/ssl/root_ssl.conf" { res.Https = true } if directive.GetName() == "ssl_reject_handshake" && directive.GetParameters()[0] == "on" { res.Https = true res.SSLRejectHandshake = true } } return res, nil }