44import hashlib
55from github import Github , Auth
66import socket
7+ import subprocess
8+ import shlex
79from typing import List
810
911
@@ -98,11 +100,54 @@ def sync_to_gitee(tag: str, body: str, files: slice, asset_links=None):
98100 repo = os .environ .get ('GITEE_REPO' , 'ServerStatus' )
99101 release_api_uri = f"https://gitee.com/api/v5/repos/{ owner } /{ repo } /releases"
100102 api_client = requests .Session ()
103+ # 第2b通道:尝试字段名 file(有些实现要求 file 而非 files)
104+ with open (file_path , 'rb' ) as fh :
105+ resp2b = api_client .post (
106+ asset_api_uri ,
107+ data = {'access_token' : access_token },
108+ files = {'file' : (file_name , fh , 'application/octet-stream' )},
109+ headers = {'Connection' : 'close' },
110+ timeout = UPLOAD_TIMEOUT ,
111+ )
112+ if resp2b .status_code == 201 :
113+ asset_info = resp2b .json ()
114+ asset_name = asset_info .get ('name' )
115+ print (f"Successfully uploaded via alt path (file field): { asset_name } !" )
116+ success = True
117+ break
118+ else :
119+ txt2b = resp2b .text
120+ if resp2b .status_code in (400 , 409 , 422 ) and ('已存在' in txt2b or 'already exists' in txt2b ):
121+ print (f"Asset { file_name } already exists (file field), treat as success." )
122+ success = True
123+ break
124+ print (f"Alt upload (file field) failed (status { resp2b .status_code } ) for { file_path } . Body: { txt2b [:256 ]} " )
125+ with open (file_path , 'rb' ) as fh :
126+ resp2c = api_client .post (
127+ asset_api_uri ,
128+ params = {'access_token' : access_token },
129+ files = {'file' : (file_name , fh , 'application/octet-stream' )},
130+ headers = {'Connection' : 'close' },
131+ timeout = UPLOAD_TIMEOUT ,
132+ )
133+ if resp2c .status_code == 201 :
134+ asset_info = resp2c .json ()
135+ asset_name = asset_info .get ('name' )
136+ print (f"Successfully uploaded via alt path (params+file): { asset_name } !" )
137+ success = True
138+ break
139+ else :
140+ txt2c = resp2c .text
141+ if resp2c .status_code in (400 , 409 , 422 ) and ('已存在' in txt2c or 'already exists' in txt2c ):
142+ print (f"Asset { file_name } already exists (params+file), treat as success." )
143+ success = True
144+ break
145+ print (f"Alt upload (params+file) failed (status { resp2c .status_code } ) for { file_path } . Body: { txt2c [:256 ]} " )
101146 api_client .headers .update ({
102147 'Accept' : 'application/json' ,
103- # 对于 form 提交不强制指定 JSON 头
148+ f"curl -sS -f --http1.1 -4 -X POST "
104149 })
105-
150+ f"-F files=@ { shlex . quote ( file_path ) } ;type=application/octet-stream "
106151 access_token = os .environ ['GITEE_TOKEN' ]
107152 release_form = {
108153 'access_token' : access_token ,
@@ -115,6 +160,21 @@ def sync_to_gitee(tag: str, body: str, files: slice, asset_links=None):
115160 # 优先尝试创建(表单提交)
116161 release_api_response = api_client .post (release_api_uri , data = release_form , timeout = REQUEST_TIMEOUT )
117162 if release_api_response .status_code == 201 :
163+ # 再试一次使用字段名 file
164+ curl_cmd2 = (
165+ f"curl -sS -f --http1.1 -4 -X POST "
166+ f"--connect-timeout 10 --max-time { UPLOAD_SOCKET_TIMEOUT } "
167+ f"-F file=@{ shlex .quote (file_path )} ;type=application/octet-stream "
168+ f"-F access_token={ shlex .quote (access_token )} "
169+ f"{ shlex .quote (asset_api_uri )} "
170+ )
171+ r2 = subprocess .run (curl_cmd2 , shell = True , capture_output = True , text = True )
172+ if r2 .returncode == 0 :
173+ print (f"Successfully uploaded via curl(file field): { file_name } !" )
174+ success = True
175+ break
176+ else :
177+ print (f"curl(file field) upload failed (exit { r2 .returncode } ) for { file_name } . stderr: { r2 .stderr [:256 ]} " )
118178 release_info = release_api_response .json ()
119179 release_id = release_info .get ('id' )
120180 else :
@@ -160,12 +220,13 @@ def sync_to_gitee(tag: str, body: str, files: slice, asset_links=None):
160220
161221 # 附件上传回退开关与出错是否继续
162222 allow_partial = os .environ .get ('SYNC_CONTINUE_ON_UPLOAD_ERROR' , 'false' ).lower () == 'true'
163- # 链接模式:跳过上传,直接把 GitHub 资产链接写入 release body
223+ # 链接模式:跳过上传(用户要求真实上传,此处默认关闭)
164224 link_only = os .environ .get ('SYNC_LINK_ONLY' , 'false' ).lower () == 'true'
165225 try :
166- link_threshold_mb = float (os .environ .get ('SYNC_LINK_THRESHOLD_MB' , '16' ))
226+ # 默认不启用按大小走链接模式(需显式设置该环境变量)
227+ link_threshold_mb = float (os .environ .get ('SYNC_LINK_THRESHOLD_MB' , '0' ))
167228 except ValueError :
168- link_threshold_mb = 16 .0
229+ link_threshold_mb = 0 .0
169230
170231 if asset_links is None :
171232 asset_links = {}
@@ -211,8 +272,9 @@ def sync_to_gitee(tag: str, body: str, files: slice, asset_links=None):
211272 resp = api_client .post (
212273 asset_api_uri ,
213274 data = {'access_token' : access_token },
214- files = {'file' : (file_name , fh , 'application/octet-stream' )},
215- headers = {'Expect' : '100-continue' },
275+ # Gitee attach_files 接口参数名为 files(可多文件)
276+ files = {'files' : (file_name , fh , 'application/octet-stream' )},
277+ headers = {'Expect' : '100-continue' , 'Connection' : 'close' },
216278 timeout = UPLOAD_TIMEOUT ,
217279 )
218280 finally :
@@ -237,7 +299,8 @@ def sync_to_gitee(tag: str, body: str, files: slice, asset_links=None):
237299 resp2 = api_client .post (
238300 asset_api_uri ,
239301 params = {'access_token' : access_token },
240- files = {'file' : (file_name , fh , 'application/octet-stream' )},
302+ files = {'files' : (file_name , fh , 'application/octet-stream' )},
303+ headers = {'Connection' : 'close' },
241304 timeout = UPLOAD_TIMEOUT ,
242305 )
243306 if resp2 .status_code == 201 :
@@ -253,10 +316,83 @@ def sync_to_gitee(tag: str, body: str, files: slice, asset_links=None):
253316 success = True
254317 break
255318 print (f"Alt upload failed (status { resp2 .status_code } ) for { file_path } . Body: { txt2 [:256 ]} " )
319+ # 第三通道:使用 curl(对某些服务器/网络更稳定)
320+ curl_cmd = (
321+ f"curl -sS -f -X POST "
322+ f"--connect-timeout 10 --max-time { UPLOAD_SOCKET_TIMEOUT } "
323+ f"-F files=@{ shlex .quote (file_path )} ;type=application/octet-stream "
324+ f"-F access_token={ shlex .quote (access_token )} "
325+ f"{ shlex .quote (asset_api_uri )} "
326+ )
327+ try :
328+ r = subprocess .run (curl_cmd , shell = True , capture_output = True , text = True )
329+ if r .returncode == 0 :
330+ print (f"Successfully uploaded via curl: { file_name } !" )
331+ success = True
332+ break
333+ else :
334+ print (f"curl upload failed (exit { r .returncode } ) for { file_name } . stderr: { r .stderr [:256 ]} " )
335+ except Exception as ce :
336+ print (f"curl upload error for { file_name } : { ce } " )
256337 except requests .RequestException as e2 :
257338 print (f"Alt upload error for { file_path } : { e2 } " )
339+ # 在请求异常时也尝试 curl
340+ curl_cmd = (
341+ f"curl -sS -f -X POST "
342+ f"--connect-timeout 10 --max-time { UPLOAD_SOCKET_TIMEOUT } "
343+ f"-F files=@{ shlex .quote (file_path )} ;type=application/octet-stream "
344+ f"-F access_token={ shlex .quote (access_token )} "
345+ f"{ shlex .quote (asset_api_uri )} "
346+ )
347+ try :
348+ r = subprocess .run (curl_cmd , shell = True , capture_output = True , text = True )
349+ if r .returncode == 0 :
350+ print (f"Successfully uploaded via curl (exception path): { file_name } !" )
351+ success = True
352+ break
353+ else :
354+ print (f"curl upload failed (exit { r .returncode } ) for { file_name } . stderr: { r .stderr [:256 ]} " )
355+ except Exception as ce :
356+ print (f"curl upload error for { file_name } : { ce } " )
258357 except requests .RequestException as e :
259358 print (f"Upload error for { file_path } : { e } (attempt { attempt } /{ MAX_UPLOAD_RETRIES } )" )
359+ # 异常路径:立刻尝试备用 requests 通道与 curl
360+ try :
361+ with open (file_path , 'rb' ) as fh :
362+ resp2 = api_client .post (
363+ asset_api_uri ,
364+ params = {'access_token' : access_token },
365+ files = {'files' : (file_name , fh , 'application/octet-stream' )},
366+ timeout = UPLOAD_TIMEOUT ,
367+ )
368+ if resp2 .status_code == 201 :
369+ asset_info = resp2 .json ()
370+ asset_name = asset_info .get ('name' )
371+ print (f"Successfully uploaded via alt path (exception): { asset_name } !" )
372+ success = True
373+ break
374+ else :
375+ print (f"Alt path (exception) failed (status { resp2 .status_code } ) for { file_path } . Body: { resp2 .text [:256 ]} " )
376+ except requests .RequestException as e3 :
377+ print (f"Alt path request error (exception) for { file_path } : { e3 } " )
378+ # 尝试 curl
379+ curl_cmd = (
380+ f"curl -sS -f -X POST "
381+ f"--connect-timeout 10 --max-time { UPLOAD_SOCKET_TIMEOUT } "
382+ f"-F files=@{ shlex .quote (file_path )} ;type=application/octet-stream "
383+ f"-F access_token={ shlex .quote (access_token )} "
384+ f"{ shlex .quote (asset_api_uri )} "
385+ )
386+ try :
387+ r = subprocess .run (curl_cmd , shell = True , capture_output = True , text = True )
388+ if r .returncode == 0 :
389+ print (f"Successfully uploaded via curl (exception outer): { file_name } !" )
390+ success = True
391+ break
392+ else :
393+ print (f"curl (exception outer) failed (exit { r .returncode } ) for { file_name } . stderr: { r .stderr [:256 ]} " )
394+ except Exception as ce :
395+ print (f"curl (exception outer) error for { file_name } : { ce } " )
260396 time .sleep (min (60 , 2 ** (attempt - 1 )))
261397 if not success :
262398 print (f"Primary upload failed for { file_name } after { MAX_UPLOAD_RETRIES } attempts, trying attachments fallback..." )
@@ -268,6 +404,7 @@ def sync_to_gitee(tag: str, body: str, files: slice, asset_links=None):
268404 attach_uri ,
269405 data = {'access_token' : access_token },
270406 files = {'file' : (file_name , fh , 'application/octet-stream' )},
407+ headers = {'Connection' : 'close' },
271408 timeout = UPLOAD_TIMEOUT ,
272409 )
273410 if ar .status_code in (200 , 201 ):
@@ -283,8 +420,44 @@ def sync_to_gitee(tag: str, body: str, files: slice, asset_links=None):
283420 print (f"Fallback: attachments API did not return a URL. Resp: { aj } " )
284421 else :
285422 print (f"Fallback: attachments upload failed { ar .status_code } { ar .text [:256 ]} " )
423+ if not success :
424+ # 尝试使用 curl 走 attachments 接口
425+ curl_cmd = (
426+ f"curl -sS -f -X POST "
427+ f"--connect-timeout 10 --max-time { UPLOAD_SOCKET_TIMEOUT } "
428+ f"-F file=@{ shlex .quote (file_path )} ;type=application/octet-stream "
429+ f"-F access_token={ shlex .quote (access_token )} "
430+ f"https://gitee.com/api/v5/repos/{ owner } /{ repo } /attachments"
431+ )
432+ try :
433+ r = subprocess .run (curl_cmd , shell = True , capture_output = True , text = True )
434+ if r .returncode == 0 :
435+ print (f"Fallback via curl: uploaded attachment for { file_name } . Will add link if API returns it on body update." )
436+ # 我们无法直接得到URL,留给后续人工查看附件列表或忽略
437+ success = True
438+ else :
439+ print (f"curl attachments failed (exit { r .returncode } ) for { file_name } . stderr: { r .stderr [:256 ]} " )
440+ except Exception as ce :
441+ print (f"curl attachments error for { file_name } : { ce } " )
286442 except requests .RequestException as ae :
287443 print (f"Fallback attachments error: { ae } " )
444+ # 尝试使用 curl 走 attachments 接口
445+ curl_cmd = (
446+ f"curl -sS -f -X POST "
447+ f"--connect-timeout 10 --max-time { UPLOAD_SOCKET_TIMEOUT } "
448+ f"-F file=@{ shlex .quote (file_path )} ;type=application/octet-stream "
449+ f"-F access_token={ shlex .quote (access_token )} "
450+ f"https://gitee.com/api/v5/repos/{ owner } /{ repo } /attachments"
451+ )
452+ try :
453+ r = subprocess .run (curl_cmd , shell = True , capture_output = True , text = True )
454+ if r .returncode == 0 :
455+ print (f"Fallback via curl: uploaded attachment for { file_name } ." )
456+ success = True
457+ else :
458+ print (f"curl attachments (exception) failed (exit { r .returncode } ) for { file_name } . stderr: { r .stderr [:256 ]} " )
459+ except Exception as ce :
460+ print (f"curl attachments (exception) error for { file_name } : { ce } " )
288461 if not success :
289462 msg = f"Failed to upload { file_path } after { MAX_UPLOAD_RETRIES } attempts"
290463 if allow_partial :
0 commit comments