diff --git a/internal/deployer/local.go b/internal/deployer/local.go
index 784660b6..7b6e224c 100644
--- a/internal/deployer/local.go
+++ b/internal/deployer/local.go
@@ -1,15 +1,15 @@
package deployer
import (
+ "bytes"
"context"
"encoding/json"
"fmt"
- "os"
"os/exec"
- "path/filepath"
"runtime"
"github.com/usual2970/certimate/internal/domain"
+ "github.com/usual2970/certimate/internal/pkg/utils/fs"
)
type LocalDeployer struct {
@@ -38,74 +38,84 @@ func (d *LocalDeployer) Deploy(ctx context.Context) error {
return err
}
- preCommand := getDeployString(d.option.DeployConfig, "preCommand")
-
+ // 执行前置命令
+ preCommand := d.option.DeployConfig.GetConfigAsString("preCommand")
if preCommand != "" {
- if err := execCmd(preCommand); err != nil {
- return fmt.Errorf("执行前置命令失败: %w", err)
+ stdout, stderr, err := d.execCommand(preCommand)
+ if err != nil {
+ return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
+
+ d.infos = append(d.infos, toStr("执行前置命令成功", stdout))
}
- // 复制证书文件
- if err := copyFile(getDeployString(d.option.DeployConfig, "certPath"), d.option.Certificate.Certificate); err != nil {
- return fmt.Errorf("复制证书失败: %w", err)
- }
+ // 写入证书和私钥文件
+ switch d.option.DeployConfig.GetConfigOrDefaultAsString("format", "pem") {
+ case "pfx":
+ // TODO: pfx
+ return fmt.Errorf("not implemented")
- // 复制私钥文件
- if err := copyFile(getDeployString(d.option.DeployConfig, "keyPath"), d.option.Certificate.PrivateKey); err != nil {
- return fmt.Errorf("复制私钥失败: %w", err)
+ case "pem":
+ if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("certPath"), d.option.Certificate.Certificate); err != nil {
+ return fmt.Errorf("failed to save certificate file: %w", err)
+ }
+
+ d.infos = append(d.infos, toStr("保存证书成功", nil))
+
+ if err := fs.WriteFileString(d.option.DeployConfig.GetConfigAsString("keyPath"), d.option.Certificate.PrivateKey); err != nil {
+ return fmt.Errorf("failed to save private key file: %w", err)
+ }
+
+ d.infos = append(d.infos, toStr("保存私钥成功", nil))
}
// 执行命令
- if err := execCmd(getDeployString(d.option.DeployConfig, "command")); err != nil {
- return fmt.Errorf("执行命令失败: %w", err)
+ command := d.option.DeployConfig.GetConfigAsString("command")
+ if command != "" {
+ stdout, stderr, err := d.execCommand(command)
+ if err != nil {
+ return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
+ }
+
+ d.infos = append(d.infos, toStr("执行命令成功", stdout))
}
return nil
}
-func execCmd(command string) error {
- // 执行命令
+func (d *LocalDeployer) execCommand(command string) (string, string, error) {
var cmd *exec.Cmd
- if runtime.GOOS == "windows" {
+ switch d.option.DeployConfig.GetConfigAsString("shell") {
+ case "cmd":
cmd = exec.Command("cmd", "/C", command)
- } else {
+
+ case "powershell":
+ cmd = exec.Command("powershell", "-Command", command)
+
+ case "sh":
cmd = exec.Command("sh", "-c", command)
+
+ case "":
+ if runtime.GOOS == "windows" {
+ cmd = exec.Command("cmd", "/C", command)
+ } else {
+ cmd = exec.Command("sh", "-c", command)
+ }
+
+ default:
+ return "", "", fmt.Errorf("unsupported shell")
}
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
+ var stdoutBuf bytes.Buffer
+ cmd.Stdout = &stdoutBuf
+ var stderrBuf bytes.Buffer
+ cmd.Stderr = &stderrBuf
err := cmd.Run()
if err != nil {
- return fmt.Errorf("执行命令失败: %w", err)
+ return "", "", fmt.Errorf("failed to execute script: %w", err)
}
- return nil
-}
-
-func copyFile(path string, content string) error {
- dir := filepath.Dir(path)
-
- // 如果目录不存在,创建目录
- err := os.MkdirAll(dir, os.ModePerm)
- if err != nil {
- return fmt.Errorf("创建目录失败: %w", err)
- }
-
- // 创建或打开文件
- file, err := os.Create(path)
- if err != nil {
- return fmt.Errorf("创建文件失败: %w", err)
- }
- defer file.Close()
-
- // 写入内容到文件
- _, err = file.Write([]byte(content))
- if err != nil {
- return fmt.Errorf("写入文件失败: %w", err)
- }
-
- return nil
+ return stdoutBuf.String(), stderrBuf.String(), err
}
diff --git a/internal/deployer/ssh.go b/internal/deployer/ssh.go
index 551e8634..e2f11c45 100644
--- a/internal/deployer/ssh.go
+++ b/internal/deployer/ssh.go
@@ -6,10 +6,10 @@ import (
"encoding/json"
"fmt"
"os"
- xpath "path"
+ "path/filepath"
"github.com/pkg/sftp"
- sshPkg "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh"
"github.com/usual2970/certimate/internal/domain"
)
@@ -41,49 +41,84 @@ func (d *SSHDeployer) Deploy(ctx context.Context) error {
}
// 连接
- client, err := d.createClient(access)
+ client, err := d.createSshClient(access)
if err != nil {
return err
}
defer client.Close()
- d.infos = append(d.infos, toStr("ssh连接成功", nil))
+ d.infos = append(d.infos, toStr("SSH 连接成功", nil))
// 执行前置命令
- preCommand := getDeployString(d.option.DeployConfig, "preCommand")
+ preCommand := d.option.DeployConfig.GetConfigAsString("preCommand")
if preCommand != "" {
stdout, stderr, err := d.sshExecCommand(client, preCommand)
if err != nil {
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
+
+ d.infos = append(d.infos, toStr("SSH 执行前置命令成功", stdout))
}
// 上传证书
- if err := d.upload(client, d.option.Certificate.Certificate, getDeployString(d.option.DeployConfig, "certPath")); err != nil {
- return fmt.Errorf("failed to upload certificate: %w", err)
+ if err := d.uploadFile(client, d.option.Certificate.Certificate, d.option.DeployConfig.GetConfigAsString("certPath")); err != nil {
+ return fmt.Errorf("failed to upload certificate file: %w", err)
}
- d.infos = append(d.infos, toStr("ssh上传证书成功", nil))
+ d.infos = append(d.infos, toStr("SSH 上传证书成功", nil))
// 上传私钥
- if err := d.upload(client, d.option.Certificate.PrivateKey, getDeployString(d.option.DeployConfig, "keyPath")); err != nil {
- return fmt.Errorf("failed to upload private key: %w", err)
+ if err := d.uploadFile(client, d.option.Certificate.PrivateKey, d.option.DeployConfig.GetConfigAsString("keyPath")); err != nil {
+ return fmt.Errorf("failed to upload private key file: %w", err)
}
- d.infos = append(d.infos, toStr("ssh上传私钥成功", nil))
+ d.infos = append(d.infos, toStr("SSH 上传私钥成功", nil))
// 执行命令
- stdout, stderr, err := d.sshExecCommand(client, getDeployString(d.option.DeployConfig, "command"))
- if err != nil {
- return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
- }
+ command := d.option.DeployConfig.GetConfigAsString("command")
+ if command != "" {
+ stdout, stderr, err := d.sshExecCommand(client, command)
+ if err != nil {
+ return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
+ }
- d.infos = append(d.infos, toStr("ssh执行命令成功", stdout))
+ d.infos = append(d.infos, toStr("SSH 执行命令成功", stdout))
+ }
return nil
}
-func (d *SSHDeployer) sshExecCommand(client *sshPkg.Client, command string) (string, string, error) {
+func (d *SSHDeployer) createSshClient(access *domain.SSHAccess) (*ssh.Client, error) {
+ var authMethod ssh.AuthMethod
+
+ if access.Key != "" {
+ var signer ssh.Signer
+ var err error
+
+ if access.KeyPassphrase != "" {
+ signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(access.Key), []byte(access.KeyPassphrase))
+ } else {
+ signer, err = ssh.ParsePrivateKey([]byte(access.Key))
+ }
+
+ if err != nil {
+ return nil, err
+ }
+ authMethod = ssh.PublicKeys(signer)
+ } else {
+ authMethod = ssh.Password(access.Password)
+ }
+
+ return ssh.Dial("tcp", fmt.Sprintf("%s:%s", access.Host, access.Port), &ssh.ClientConfig{
+ User: access.Username,
+ Auth: []ssh.AuthMethod{
+ authMethod,
+ },
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ })
+}
+
+func (d *SSHDeployer) sshExecCommand(client *ssh.Client, command string) (string, string, error) {
session, err := client.NewSession()
if err != nil {
return "", "", fmt.Errorf("failed to create ssh session: %w", err)
@@ -98,14 +133,14 @@ func (d *SSHDeployer) sshExecCommand(client *sshPkg.Client, command string) (str
return stdoutBuf.String(), stderrBuf.String(), err
}
-func (d *SSHDeployer) upload(client *sshPkg.Client, content, path string) error {
+func (d *SSHDeployer) uploadFile(client *ssh.Client, path string, content string) error {
sftpCli, err := sftp.NewClient(client)
if err != nil {
return fmt.Errorf("failed to create sftp client: %w", err)
}
defer sftpCli.Close()
- if err := sftpCli.MkdirAll(xpath.Dir(path)); err != nil {
+ if err := sftpCli.MkdirAll(filepath.Dir(path)); err != nil {
return fmt.Errorf("failed to create remote directory: %w", err)
}
@@ -122,33 +157,3 @@ func (d *SSHDeployer) upload(client *sshPkg.Client, content, path string) error
return nil
}
-
-func (d *SSHDeployer) createClient(access *domain.SSHAccess) (*sshPkg.Client, error) {
- var authMethod sshPkg.AuthMethod
-
- if access.Key != "" {
- var signer sshPkg.Signer
- var err error
-
- if access.KeyPassphrase != "" {
- signer, err = sshPkg.ParsePrivateKeyWithPassphrase([]byte(access.Key), []byte(access.KeyPassphrase))
- } else {
- signer, err = sshPkg.ParsePrivateKey([]byte(access.Key))
- }
-
- if err != nil {
- return nil, err
- }
- authMethod = sshPkg.PublicKeys(signer)
- } else {
- authMethod = sshPkg.Password(access.Password)
- }
-
- return sshPkg.Dial("tcp", fmt.Sprintf("%s:%s", access.Host, access.Port), &sshPkg.ClientConfig{
- User: access.Username,
- Auth: []sshPkg.AuthMethod{
- authMethod,
- },
- HostKeyCallback: sshPkg.InsecureIgnoreHostKey(),
- })
-}
diff --git a/internal/pkg/utils/fs/fs.go b/internal/pkg/utils/fs/fs.go
new file mode 100644
index 00000000..a8db4c04
--- /dev/null
+++ b/internal/pkg/utils/fs/fs.go
@@ -0,0 +1,51 @@
+package fs
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+)
+
+// 与 `WriteFile` 类似,但写入的是字符串内容。
+//
+// 入参:
+// - path: 文件路径。
+// - content: 文件内容。
+//
+// 出参:
+// - 错误。
+func WriteFileString(path string, content string) error {
+ return WriteFile(path, []byte(content))
+}
+
+// 将数据写入指定路径的文件。
+// 如果目录不存在,将会递归创建目录。
+// 如果文件不存在,将会创建该文件;如果文件已存在,将会覆盖原有内容。
+//
+// 入参:
+// - path: 文件路径。
+// - data: 文件数据字节数组。
+//
+// 出参:
+// - 错误。
+func WriteFile(path string, data []byte) error {
+ dir := filepath.Dir(path)
+
+ err := os.MkdirAll(dir, os.ModePerm)
+ if err != nil {
+ return fmt.Errorf("failed to create directory: %w", err)
+ }
+
+ file, err := os.Create(path)
+ if err != nil {
+ return fmt.Errorf("failed to create file: %w", err)
+ }
+ defer file.Close()
+
+ _, err = file.Write(data)
+ if err != nil {
+ return fmt.Errorf("failed to write file: %w", err)
+ }
+
+ return nil
+}
diff --git a/ui/src/components/certimate/DeployEditDialog.tsx b/ui/src/components/certimate/DeployEditDialog.tsx
index 7ef5f291..24d122c7 100644
--- a/ui/src/components/certimate/DeployEditDialog.tsx
+++ b/ui/src/components/certimate/DeployEditDialog.tsx
@@ -17,6 +17,7 @@ import DeployToTencentCOS from "./DeployToTencentCOS";
import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN";
import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB";
import DeployToQiniuCDN from "./DeployToQiniuCDN";
+import DeployToLocal from "./DeployToLocal";
import DeployToSSH from "./DeployToSSH";
import DeployToWebhook from "./DeployToWebhook";
import DeployToKubernetesSecret from "./DeployToKubernetesSecret";
@@ -136,8 +137,10 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro
case "qiniu-cdn":
childComponent =