package deploy import ( "ALLinSSL/backend/internal/access" "ALLinSSL/backend/public" "bytes" "encoding/json" "fmt" "golang.org/x/crypto/ssh" "os" "path/filepath" "strconv" ) type SSHConfig struct { User string Password string // 可选 PrivateKey string `json:"key"` // 可选 Host string Port any } type RemoteFile struct { Path string Content string } func buildAuthMethods(password, privateKey string) ([]ssh.AuthMethod, error) { var methods []ssh.AuthMethod if privateKey != "" { signer, err := ssh.ParsePrivateKey([]byte(privateKey)) if err != nil { return nil, fmt.Errorf("unable to parse private key: %v", err) } methods = append(methods, ssh.PublicKeys(signer)) } if password != "" { methods = append(methods, ssh.Password(password)) } if len(methods) == 0 { return nil, fmt.Errorf("no authentication methods provided") } return methods, nil } func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, postCmd string) error { var port string switch v := config.Port.(type) { case float64: port = strconv.Itoa(int(v)) case string: port = v case int: port = strconv.Itoa(v) default: port = "22" } addr := fmt.Sprintf("%s:%s", config.Host, port) authMethods, err := buildAuthMethods(config.Password, config.PrivateKey) if err != nil { return err } sshConfig := &ssh.ClientConfig{ User: config.User, Auth: authMethods, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } client, err := ssh.Dial("tcp", addr, sshConfig) if err != nil { return fmt.Errorf("failed to dial: %v", err) } defer client.Close() session, err := client.NewSession() if err != nil { return fmt.Errorf("会话创建失败: %v", err) } defer session.Close() var script bytes.Buffer if preCmd != "" { script.WriteString(preCmd + " && ") } for i, file := range files { if i > 0 { script.WriteString(" && ") } dirCmd := fmt.Sprintf("mkdir -p $(dirname %q)", file.Path) writeCmd := fmt.Sprintf("printf %%s '%s' > %s", file.Content, file.Path) script.WriteString(dirCmd + " && " + writeCmd) } if postCmd != "" { script.WriteString(" && " + postCmd) } cmd := script.String() if err := session.Run(cmd); err != nil { return fmt.Errorf("运行出错: %v", err) } return nil } func DeploySSH(cfg map[string]any) error { cert, ok := cfg["certificate"].(map[string]any) if !ok { return fmt.Errorf("证书不存在") } // 设置证书 keyPem, ok := cert["key"].(string) if !ok { return fmt.Errorf("证书错误:key") } certPem, ok := cert["cert"].(string) if !ok { return fmt.Errorf("证书错误:cert") } var providerID string switch v := cfg["provider_id"].(type) { case float64: providerID = strconv.Itoa(int(v)) case string: providerID = v default: return fmt.Errorf("参数错误:provider_id") } keyPath, ok := cfg["keyPath"].(string) if !ok { return fmt.Errorf("参数错误:keyPath") } certPath, ok := cfg["certPath"].(string) if !ok { return fmt.Errorf("参数错误:certPath") } beforeCmd, ok := cfg["beforeCmd"].(string) if !ok { beforeCmd = "" } afterCmd, ok := cfg["afterCmd"].(string) if !ok { afterCmd = "" } providerData, err := access.GetAccess(providerID) if err != nil { return err } providerConfigStr, ok := providerData["config"].(string) if !ok { return fmt.Errorf("api配置错误") } // 解析 JSON 配置 var providerConfig SSHConfig err = json.Unmarshal([]byte(providerConfigStr), &providerConfig) if err != nil { return err } // 自动创建多级目录 files := []RemoteFile{ {Path: certPath, Content: certPem}, {Path: keyPath, Content: keyPem}, } err = writeMultipleFilesViaSSH(providerConfig, files, beforeCmd, afterCmd) if err != nil { return fmt.Errorf("SSH 部署失败: %v", err) } return nil } func DeployLocalhost(cfg map[string]any) error { cert, ok := cfg["certificate"].(map[string]any) if !ok { return fmt.Errorf("证书不存在") } // 设置证书 keyPem, ok := cert["key"].(string) if !ok { return fmt.Errorf("证书错误:key") } certPem, ok := cert["cert"].(string) if !ok { return fmt.Errorf("证书错误:cert") } keyPath, ok := cfg["keyPath"].(string) if !ok { return fmt.Errorf("参数错误:keyPath") } certPath, ok := cfg["certPath"].(string) if !ok { return fmt.Errorf("参数错误:certPath") } beforeCmd, ok := cfg["beforeCmd"].(string) if ok { _, errout, err := public.ExecCommand(beforeCmd) if err != nil { return fmt.Errorf("前置命令执行失败: %v, %s", err, errout) } } dir := filepath.Dir(certPath) if err := os.MkdirAll(dir, os.ModePerm); err != nil { panic("创建证书保存目录失败: " + err.Error()) } dir = filepath.Dir(keyPath) if err := os.MkdirAll(dir, os.ModePerm); err != nil { panic("创建私钥保存目录失败: " + err.Error()) } err := os.WriteFile(certPath, []byte(certPem), 0644) if err != nil { return fmt.Errorf("写入证书失败: %v", err) } err = os.WriteFile(keyPath, []byte(keyPem), 0644) if err != nil { return fmt.Errorf("写入私钥失败: %v", err) } afterCmd, ok := cfg["afterCmd"].(string) if ok { _, errout, err := public.ExecCommand(afterCmd) if err != nil { return fmt.Errorf("后置命令执行失败: %v, %s", err, errout) } } return nil }