Skip to content

Commit 365a7de

Browse files
committed
refactor(client): 优化连接通知和证书部署逻辑
重构连接通知处理逻辑,增加重连机制和状态日志控制 改进证书下载和解压流程,增加安全检查和资源清理 添加下载超时和文件大小格式化功能
1 parent 70b3141 commit 365a7de

File tree

2 files changed

+233
-191
lines changed

2 files changed

+233
-191
lines changed

internal/client/cert_deploy.go

Lines changed: 90 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package client
22

33
import (
4-
"archive/tar"
54
"archive/zip"
65
"context"
76
"fmt"
@@ -15,6 +14,10 @@ import (
1514
"github.com/orange-juzipi/cert-deploy/internal/config"
1615
)
1716

17+
const (
18+
certsDir = "certs"
19+
)
20+
1821
// CertDeployer 证书部署器
1922
type CertDeployer struct {
2023
client *Client
@@ -30,7 +33,6 @@ func NewCertDeployer(client *Client) *CertDeployer {
3033
// DeployCertificate 部署证书
3134
func (cd *CertDeployer) DeployCertificate(domain, url string) error {
3235
// 创建certs目录
33-
certsDir := "certs"
3436
if err := os.MkdirAll(certsDir, 0755); err != nil {
3537
return fmt.Errorf("创建证书目录失败: %w", err)
3638
}
@@ -46,47 +48,59 @@ func (cd *CertDeployer) DeployCertificate(domain, url string) error {
4648

4749
fmt.Printf("证书下载完成: %s\n", zipFile)
4850

49-
// 2. 检查是否配置了SSL目录
50-
sslPath := config.GetConfig().SSL.Path
51-
if sslPath != "" {
52-
// 证书文件夹名
53-
folderName := domain + "_certificates"
54-
55-
// 1. 解压zip文件
56-
extractDir := filepath.Join(certsDir, folderName)
57-
if err := cd.extractZip(zipFile, extractDir); err != nil {
58-
return fmt.Errorf("解压证书失败: %w", err)
51+
// 确保下载失败时清理
52+
defer func() {
53+
if _, err := os.Stat(zipFile); err == nil {
54+
// 部署成功后删除zip文件
55+
os.Remove(zipFile)
5956
}
57+
}()
6058

61-
// 2. 移动到配置的SSL目录
62-
if err := cd.moveCertificates(extractDir, sslPath, folderName); err != nil {
63-
return fmt.Errorf("移动证书失败: %w", err)
64-
}
59+
// 检查是否配置了SSL目录
60+
sslPath := config.GetConfig().SSL.Path
61+
if sslPath == "" {
62+
fmt.Println("未配置SSL目录,证书已下载到: ", zipFile)
63+
return nil
64+
}
6565

66-
// 3. 检查nginx是否存在,如果存在则测试配置和重新加载
67-
if cd.isNginxAvailable() {
68-
// 测试nginx配置
69-
if err := cd.testNginxConfig(); err != nil {
70-
return fmt.Errorf("nginx配置测试失败: %w", err)
71-
}
72-
73-
// 重新加载nginx
74-
if err := cd.reloadNginx(); err != nil {
75-
return fmt.Errorf("nginx重新加载失败: %w", err)
76-
}
77-
} else {
78-
fmt.Println("nginx未安装或不在PATH中,跳过nginx相关操作")
66+
// 证书文件夹名
67+
folderName := domain + "_certificates"
68+
extractDir := filepath.Join(certsDir, folderName)
69+
70+
// 1. 解压zip文件
71+
if err := cd.extractZip(zipFile, extractDir); err != nil {
72+
// 清理失败的解压文件
73+
os.RemoveAll(extractDir)
74+
return fmt.Errorf("解压证书失败: %w", err)
75+
}
76+
77+
// 2. 移动到配置的SSL目录
78+
if err := cd.moveCertificates(extractDir, sslPath, folderName); err != nil {
79+
// 清理失败的解压文件
80+
os.RemoveAll(extractDir)
81+
return fmt.Errorf("移动证书失败: %w", err)
82+
}
83+
84+
// 3. 检查nginx是否存在,如果存在则测试配置和重新加载
85+
if cd.isNginxAvailable() {
86+
// 测试nginx配置
87+
if err := cd.testNginxConfig(); err != nil {
88+
return fmt.Errorf("nginx配置测试失败: %w", err)
7989
}
80-
fmt.Printf("证书部署完成: %s\n", domain)
8190

91+
// 重新加载nginx
92+
if err := cd.reloadNginx(); err != nil {
93+
return fmt.Errorf("nginx重新加载失败: %w", err)
94+
}
8295
} else {
83-
fmt.Println("未配置SSL目录,证书已下载到: ", zipFile)
96+
fmt.Println("nginx未安装或不在PATH中,跳过nginx相关操作")
8497
}
8598

99+
fmt.Printf("自动部署流程完成: %s\n", domain)
86100
return nil
87101
}
88102

89-
// extractZip 解压zip文件
103+
// extractZip 解压zip文件(修复:资源泄露)
90104
func (cd *CertDeployer) extractZip(zipFile, extractDir string) error {
91105
// 创建解压目录
92106
if err := os.MkdirAll(extractDir, 0755); err != nil {
@@ -102,118 +116,55 @@ func (cd *CertDeployer) extractZip(zipFile, extractDir string) error {
102116

103117
// 解压所有文件
104118
for _, file := range reader.File {
105-
// 构建目标文件路径
106-
targetPath := filepath.Join(extractDir, file.Name)
107-
108-
// 检查路径安全性
109-
if !strings.HasPrefix(targetPath, filepath.Clean(extractDir)+string(os.PathSeparator)) {
110-
return fmt.Errorf("不安全的文件路径: %s", file.Name)
111-
}
112-
113-
// 创建目录
114-
if file.FileInfo().IsDir() {
115-
if err := os.MkdirAll(targetPath, file.FileInfo().Mode()); err != nil {
116-
return fmt.Errorf("创建目录失败: %w", err)
117-
}
118-
continue
119-
}
120-
121-
// 创建文件目录
122-
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
123-
return fmt.Errorf("创建文件目录失败: %w", err)
124-
}
125-
126-
// 打开zip文件中的文件
127-
rc, err := file.Open()
128-
if err != nil {
129-
return fmt.Errorf("打开zip中的文件失败: %w", err)
130-
}
131-
132-
// 创建目标文件
133-
outFile, err := os.Create(targetPath)
134-
if err != nil {
135-
rc.Close()
136-
return fmt.Errorf("创建文件失败: %w", err)
137-
}
138-
139-
// 复制文件内容
140-
if _, err := io.Copy(outFile, rc); err != nil {
141-
rc.Close()
142-
outFile.Close()
143-
return fmt.Errorf("复制文件内容失败: %w", err)
144-
}
145-
146-
rc.Close()
147-
outFile.Close()
148-
149-
// 设置文件权限
150-
if err := os.Chmod(targetPath, file.FileInfo().Mode()); err != nil {
151-
return fmt.Errorf("设置文件权限失败: %w", err)
119+
if err := cd.extractZipFile(file, extractDir); err != nil {
120+
return err
152121
}
153122
}
154123

155124
return nil
156125
}
157126

158-
// extractTar 解压tar文件
159-
func (cd *CertDeployer) extractTar(tarFile, extractDir string) error {
160-
// 创建解压目录
161-
if err := os.MkdirAll(extractDir, 0755); err != nil {
162-
return fmt.Errorf("创建解压目录失败: %w", err)
127+
// extractZipFile 解压单个zip文件条目
128+
func (cd *CertDeployer) extractZipFile(file *zip.File, extractDir string) error {
129+
// 使用 filepath.Rel 安全地检查路径
130+
targetPath := filepath.Join(extractDir, file.Name)
131+
rel, err := filepath.Rel(extractDir, targetPath)
132+
if err != nil || strings.HasPrefix(rel, "..") {
133+
return fmt.Errorf("不安全的文件路径: %s", file.Name)
163134
}
164135

165-
// 打开tar文件
166-
file, err := os.Open(tarFile)
167-
if err != nil {
168-
return fmt.Errorf("打开tar文件失败: %w", err)
136+
// 创建目录
137+
if file.FileInfo().IsDir() {
138+
return os.MkdirAll(targetPath, file.FileInfo().Mode())
169139
}
170-
defer file.Close()
171-
172-
// 创建tar reader
173-
tr := tar.NewReader(file)
174-
175-
// 解压所有文件
176-
for {
177-
header, err := tr.Next()
178-
if err == io.EOF {
179-
break
180-
}
181-
if err != nil {
182-
return fmt.Errorf("读取tar文件失败: %w", err)
183-
}
184140

185-
// 构建目标文件路径
186-
targetPath := filepath.Join(extractDir, header.Name)
187-
188-
// 创建目录
189-
if header.Typeflag == tar.TypeDir {
190-
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
191-
return fmt.Errorf("创建目录失败: %w", err)
192-
}
193-
continue
194-
}
141+
// 创建文件目录
142+
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
143+
return fmt.Errorf("创建文件目录失败: %w", err)
144+
}
195145

196-
// 创建文件
197-
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
198-
return fmt.Errorf("创建文件目录失败: %w", err)
199-
}
146+
// 打开zip文件中的文件
147+
rc, err := file.Open()
148+
if err != nil {
149+
return fmt.Errorf("打开zip中的文件失败: %w", err)
150+
}
151+
defer rc.Close()
200152

201-
outFile, err := os.Create(targetPath)
202-
if err != nil {
203-
return fmt.Errorf("创建文件失败: %w", err)
204-
}
153+
// 创建目标文件
154+
outFile, err := os.Create(targetPath)
155+
if err != nil {
156+
return fmt.Errorf("创建文件失败: %w", err)
157+
}
158+
defer outFile.Close()
205159

206-
// 复制文件内容
207-
if _, err := io.Copy(outFile, tr); err != nil {
208-
outFile.Close()
209-
return fmt.Errorf("复制文件内容失败: %w", err)
210-
}
211-
outFile.Close()
160+
// 复制文件内容
161+
if _, err := io.Copy(outFile, rc); err != nil {
162+
return fmt.Errorf("复制文件内容失败: %w", err)
163+
}
212164

213-
// 设置文件权限
214-
if err := os.Chmod(targetPath, os.FileMode(header.Mode)); err != nil {
215-
return fmt.Errorf("设置文件权限失败: %w", err)
216-
}
165+
// 设置文件权限
166+
if err := os.Chmod(targetPath, file.FileInfo().Mode()); err != nil {
167+
return fmt.Errorf("设置文件权限失败: %w", err)
217168
}
218169

219170
return nil
@@ -229,19 +180,23 @@ func (cd *CertDeployer) moveCertificates(sourceDir, sslPath, folderName string)
229180
// 构建目标路径
230181
targetDir := filepath.Join(sslPath, folderName)
231182

232-
// 如果目标目录已存在,先删除
183+
// 如果目标目录已存在,先备份
233184
if _, err := os.Stat(targetDir); err == nil {
234-
if err := os.RemoveAll(targetDir); err != nil {
235-
return fmt.Errorf("删除已存在的目录失败: %w", err)
185+
backupDir := targetDir + ".backup"
186+
// 删除旧备份
187+
os.RemoveAll(backupDir)
188+
// 移动现有目录到备份
189+
if err := os.Rename(targetDir, backupDir); err != nil {
190+
return fmt.Errorf("备份现有目录失败: %w", err)
236191
}
237192
}
238193

239-
// 直接移动整个文件夹
194+
// 移动新证书到目标位置
240195
if err := os.Rename(sourceDir, targetDir); err != nil {
241196
return fmt.Errorf("移动证书文件夹失败: %w", err)
242197
}
243198

244-
fmt.Printf("移动证书文件夹: %s -> %s\n", sourceDir, targetDir)
199+
fmt.Printf("证书文件夹已更新: %s\n", targetDir)
245200
return nil
246201
}
247202

0 commit comments

Comments
 (0)