package installers import ( "bytes" "errors" "fmt" "os" "path/filepath" "regexp" "strings" "github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" ) type HTTPDNSNodeInstaller struct { BaseInstaller } func (i *HTTPDNSNodeInstaller) Install(dir string, params interface{}, installStatus *models.NodeInstallStatus) error { if params == nil { return errors.New("'params' required for node installation") } nodeParams, ok := params.(*NodeParams) if !ok { return errors.New("'params' should be *NodeParams") } err := nodeParams.Validate() if err != nil { return fmt.Errorf("params validation: %w", err) } installRootDir, appDir := resolveHTTPDNSInstallPaths(dir) _, err = i.client.Stat(installRootDir) if err != nil { err = i.client.MkdirAll(installRootDir) if err != nil { installStatus.ErrorCode = "CREATE_ROOT_DIRECTORY_FAILED" return fmt.Errorf("create directory '%s' failed: %w", installRootDir, err) } } env, err := i.InstallHelper(installRootDir, nodeconfigs.NodeRoleHTTPDNS) if err != nil { installStatus.ErrorCode = "INSTALL_HELPER_FAILED" return err } filePrefix := "edge-httpdns-" + env.OS + "-" + env.Arch zipFile, err := i.LookupLatestInstallerForTarget(filePrefix, env) if err != nil { return err } if len(zipFile) == 0 { return errors.New("can not find installer file for " + env.OS + "/" + env.Arch + ", expected '" + filePrefix + "-v*.zip' or distro-specific '" + filePrefix + "-{ubuntu22.04|amzn2023}-v*.zip'") } targetZip, err := i.copyZipToRemote(installRootDir, zipFile) if err != nil { return err } if !nodeParams.IsUpgrading { _, stderr, testErr := i.client.Exec(env.HelperPath + " -cmd=test") if testErr != nil { return fmt.Errorf("test failed: %w", testErr) } if len(stderr) > 0 { return errors.New("test failed: " + stderr) } } exePath := appDir + "/bin/edge-httpdns" if nodeParams.IsUpgrading { _, err = i.client.Stat(exePath) if err == nil { _, _, _ = i.client.Exec(exePath + " stop") removeErr := i.client.Remove(exePath) if removeErr != nil && removeErr != os.ErrNotExist { return fmt.Errorf("remove old file failed: %w", removeErr) } } } _, stderr, err := i.client.Exec(env.HelperPath + " -cmd=unzip -zip=\"" + targetZip + "\" -target=\"" + installRootDir + "\"") if err != nil { return err } if len(stderr) > 0 { return errors.New("unzip installer failed: " + stderr) } certFile := appDir + "/configs/tls/server.crt" keyFile := appDir + "/configs/tls/server.key" err = i.writeTLSCertificate(certFile, keyFile, nodeParams.TLSCertData, nodeParams.TLSKeyData) if err != nil { installStatus.ErrorCode = "WRITE_TLS_CERT_FAILED" return err } configFile := appDir + "/configs/api_httpdns.yaml" if i.client.sudo { _, _, _ = i.client.Exec("chown " + i.client.User() + " " + filepath.Dir(configFile)) } listenAddr := strings.TrimSpace(nodeParams.HTTPDNSListenAddr) if len(listenAddr) == 0 { listenAddr = ":443" } configData := []byte(`rpc.endpoints: [ ${endpoints} ] nodeId: "${nodeId}" secret: "${nodeSecret}" https.listenAddr: "${listenAddr}" https.cert: "${certFile}" https.key: "${keyFile}"`) certFileClean := strings.ReplaceAll(certFile, "\\", "/") keyFileClean := strings.ReplaceAll(keyFile, "\\", "/") configData = bytes.ReplaceAll(configData, []byte("${endpoints}"), []byte(nodeParams.QuoteEndpoints())) configData = bytes.ReplaceAll(configData, []byte("${nodeId}"), []byte(nodeParams.NodeId)) configData = bytes.ReplaceAll(configData, []byte("${nodeSecret}"), []byte(nodeParams.Secret)) configData = bytes.ReplaceAll(configData, []byte("${listenAddr}"), []byte(listenAddr)) configData = bytes.ReplaceAll(configData, []byte("${certFile}"), []byte(certFileClean)) configData = bytes.ReplaceAll(configData, []byte("${keyFile}"), []byte(keyFileClean)) _, err = i.client.WriteFile(configFile, configData) if err != nil { return fmt.Errorf("write '%s': %w", configFile, err) } err = i.SetupFluentBit(nodeconfigs.NodeRoleHTTPDNS) if err != nil { installStatus.ErrorCode = "SETUP_FLUENT_BIT_FAILED" return fmt.Errorf("setup fluent-bit failed: %w", err) } startCmdPrefix := "cd " + shQuote(appDir+"/configs") + " && ../bin/edge-httpdns " stdout, stderr, err := i.client.Exec(startCmdPrefix + "test") if err != nil { installStatus.ErrorCode = "TEST_FAILED" return fmt.Errorf("test edge-httpdns failed: %w, stdout: %s, stderr: %s", err, stdout, stderr) } if len(stderr) > 0 { if regexp.MustCompile(`(?i)rpc`).MatchString(stderr) || regexp.MustCompile(`(?i)rpc`).MatchString(stdout) { installStatus.ErrorCode = "RPC_TEST_FAILED" } return errors.New("test edge-httpdns failed, stdout: " + stdout + ", stderr: " + stderr) } stdout, stderr, err = i.client.Exec(startCmdPrefix + "start") if err != nil { return fmt.Errorf("start edge-httpdns failed: %w, stdout: %s, stderr: %s", err, stdout, stderr) } if len(stderr) > 0 { return errors.New("start edge-httpdns failed, stdout: " + stdout + ", stderr: " + stderr) } return nil } func resolveHTTPDNSInstallPaths(rawDir string) (installRootDir string, appDir string) { dir := strings.TrimSpace(rawDir) dir = strings.TrimRight(dir, "/") if len(dir) == 0 { return rawDir, rawDir + "/edge-httpdns" } if strings.HasSuffix(dir, "/edge-httpdns") { root := strings.TrimSuffix(dir, "/edge-httpdns") if len(root) == 0 { root = "/" } return root, dir } return dir, dir + "/edge-httpdns" } func (i *HTTPDNSNodeInstaller) copyZipToRemote(dir string, zipFile string) (string, error) { targetZip := "" var firstCopyErr error zipName := filepath.Base(zipFile) for _, candidate := range []string{ dir + "/" + zipName, i.client.UserHome() + "/" + zipName, "/tmp/" + zipName, } { err := i.client.Copy(zipFile, candidate, 0777) if err != nil { if firstCopyErr == nil { firstCopyErr = err } continue } targetZip = candidate firstCopyErr = nil break } if firstCopyErr != nil { return "", fmt.Errorf("upload httpdns file failed: %w", firstCopyErr) } return targetZip, nil } func (i *HTTPDNSNodeInstaller) writeTLSCertificate(certFile string, keyFile string, certData []byte, keyData []byte) error { if len(certData) == 0 || len(keyData) == 0 { return errors.New("cluster tls certificate is empty") } certDir := filepath.Dir(certFile) _, stderr, err := i.client.Exec("mkdir -p " + shQuote(certDir)) if err != nil { return fmt.Errorf("create tls directory failed: %w, stderr: %s", err, stderr) } if i.client.sudo { _, _, _ = i.client.Exec("chown " + i.client.User() + " " + shQuote(certDir)) } _, err = i.client.WriteFile(certFile, certData) if err != nil { return fmt.Errorf("write cert file failed: %w", err) } _, err = i.client.WriteFile(keyFile, keyData) if err != nil { return fmt.Errorf("write key file failed: %w", err) } _, stderr, err = i.client.Exec("chmod 0644 " + shQuote(certFile) + " && chmod 0600 " + shQuote(keyFile)) if err != nil { return fmt.Errorf("chmod tls files failed: %w, stderr: %s", err, stderr) } return nil }