mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-21 00:24:12 +08:00
* refactor: separate openresty module catalog and state * feat: Optimize the OpenResty module compilation logic.
457 lines
14 KiB
Go
457 lines
14 KiB
Go
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
|
|
}
|