11package client
22
33import (
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 证书部署器
1922type CertDeployer struct {
2023 client * Client
@@ -30,7 +33,6 @@ func NewCertDeployer(client *Client) *CertDeployer {
3033// DeployCertificate 部署证书
3134func (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文件(修复:资源泄露)
90104func (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