Skip to content

Commit 61a0bac

Browse files
committed
Update.
1 parent 806d402 commit 61a0bac

File tree

2 files changed

+120
-51
lines changed

2 files changed

+120
-51
lines changed

.github/sync.py

Lines changed: 118 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,23 @@
22
import time
33
import requests
44
import hashlib
5-
from github import Github
5+
from github import Github, Auth
6+
7+
8+
REQUEST_TIMEOUT = (10, 300) # (connect timeout, read timeout)
9+
# 上传可能较慢,单独设置更长的读超时
10+
UPLOAD_TIMEOUT = (10, 900)
11+
MAX_DOWNLOAD_RETRIES = 3
12+
MAX_UPLOAD_RETRIES = 5
613

714

815
def get_github_latest_release():
9-
g = Github()
16+
# 使用令牌提升速率配额
17+
gh_token = os.environ.get('GH_TOKEN') or os.environ.get('GITHUB_TOKEN')
18+
if gh_token:
19+
g = Github(auth=Auth.Token(gh_token))
20+
else:
21+
g = Github()
1022
repo = g.get_repo("xOS/ServerStatus")
1123
release = repo.get_latest_release()
1224
if release:
@@ -17,13 +29,24 @@ def get_github_latest_release():
1729
url = asset.browser_download_url
1830
name = asset.name
1931

20-
response = requests.get(url)
21-
if response.status_code == 200:
22-
with open(name, 'wb') as f:
23-
f.write(response.content)
24-
print(f"Downloaded {name}")
32+
# 下载资产(流式 + 超时 + 有限重试)
33+
for attempt in range(1, MAX_DOWNLOAD_RETRIES + 1):
34+
try:
35+
with requests.get(url, stream=True, timeout=REQUEST_TIMEOUT) as r:
36+
if r.status_code == 200:
37+
with open(name, 'wb') as f:
38+
for chunk in r.iter_content(chunk_size=1024 * 256):
39+
if chunk:
40+
f.write(chunk)
41+
print(f"Downloaded {name}")
42+
break
43+
else:
44+
print(f"Failed to download {name}, status: {r.status_code}, attempt {attempt}/{MAX_DOWNLOAD_RETRIES}")
45+
except requests.RequestException as e:
46+
print(f"Download error for {name}: {e} (attempt {attempt}/{MAX_DOWNLOAD_RETRIES})")
47+
time.sleep(2 ** (attempt - 1))
2548
else:
26-
print(f"Failed to download {name}")
49+
raise RuntimeError(f"Failed to download {name} after {MAX_DOWNLOAD_RETRIES} attempts")
2750
file_abs_path = get_abs_path(asset.name)
2851
files.append(file_abs_path)
2952
sync_to_gitee(release.tag_name, release.body, files)
@@ -32,89 +55,134 @@ def get_github_latest_release():
3255

3356

3457
def delete_gitee_releases(latest_id, client, uri, token):
35-
get_data = {
36-
'access_token': token
37-
}
58+
# 仅当 latest_id 有效时执行保留最新
59+
if not latest_id:
60+
print('Skip delete_gitee_releases: latest_id is empty')
61+
return
3862

63+
params = {'access_token': token, 'page': 1, 'per_page': 100}
3964
release_info = []
40-
release_response = client.get(uri, json=get_data)
65+
release_response = client.get(uri, params=params, timeout=REQUEST_TIMEOUT)
4166
if release_response.status_code == 200:
4267
release_info = release_response.json()
4368
else:
44-
print(
45-
f"Request failed with status code {release_response.status_code}")
69+
print(f"List releases failed: {release_response.status_code} {release_response.text}")
70+
return
4671

4772
release_ids = []
4873
for block in release_info:
4974
if 'id' in block:
5075
release_ids.append(block['id'])
5176

5277
print(f'Current release ids: {release_ids}')
53-
release_ids.remove(latest_id)
54-
55-
for id in release_ids:
56-
release_uri = f"{uri}/{id}"
57-
delete_data = {
58-
'access_token': token
59-
}
60-
delete_response = client.delete(release_uri, json=delete_data)
78+
if latest_id in release_ids:
79+
release_ids.remove(latest_id)
80+
81+
for rid in release_ids:
82+
release_uri = f"{uri}/{rid}"
83+
delete_response = client.delete(release_uri, params={'access_token': token}, timeout=REQUEST_TIMEOUT)
6184
if delete_response.status_code == 204:
62-
print(f'Successfully deleted release #{id}.')
85+
print(f'Successfully deleted release #{rid}.')
6386
else:
64-
raise ValueError(
65-
f"Request failed with status code {delete_response.status_code}")
87+
raise ValueError(f"Delete release #{rid} failed: {delete_response.status_code} {delete_response.text}")
6688

6789

6890
def sync_to_gitee(tag: str, body: str, files: slice):
6991
release_id = ""
70-
owner = "Ten"
71-
repo = "ServerStatus"
92+
owner = os.environ.get('GITEE_OWNER', 'Ten')
93+
repo = os.environ.get('GITEE_REPO', 'ServerStatus')
7294
release_api_uri = f"https://gitee.com/api/v5/repos/{owner}/{repo}/releases"
7395
api_client = requests.Session()
7496
api_client.headers.update({
7597
'Accept': 'application/json',
76-
'Content-Type': 'application/json'
98+
# 对于 form 提交不强制指定 JSON 头
7799
})
78100

79101
access_token = os.environ['GITEE_TOKEN']
80-
release_data = {
102+
release_form = {
81103
'access_token': access_token,
82104
'tag_name': tag,
83105
'name': tag,
84106
'body': body,
85-
'prerelease': False,
107+
'prerelease': 'false',
86108
'target_commitish': 'master'
87109
}
88-
release_api_response = api_client.post(release_api_uri, json=release_data)
110+
# 优先尝试创建(表单提交)
111+
release_api_response = api_client.post(release_api_uri, data=release_form, timeout=REQUEST_TIMEOUT)
89112
if release_api_response.status_code == 201:
90113
release_info = release_api_response.json()
91114
release_id = release_info.get('id')
92115
else:
93-
print(
94-
f"Request failed with status code {release_api_response.status_code}")
116+
print(f"Create release failed: {release_api_response.status_code} {release_api_response.text}")
117+
# 如果已存在同名 tag,则尝试查找已存在的发布并复用其 id
118+
list_resp = api_client.get(release_api_uri, params={'access_token': access_token, 'page': 1, 'per_page': 100}, timeout=REQUEST_TIMEOUT)
119+
if list_resp.status_code == 200:
120+
for rel in list_resp.json():
121+
if rel.get('tag_name') == tag:
122+
release_id = rel.get('id')
123+
print(f"Found existing Gitee release id: {release_id} for tag {tag}")
124+
break
125+
else:
126+
print(f"List releases failed: {list_resp.status_code} {list_resp.text}")
95127

96128
print(f"Gitee release id: {release_id}")
129+
if not release_id:
130+
raise RuntimeError(f"No Gitee release id available for tag {tag}. Please ensure repo {owner}/{repo} exists and token has permission.")
97131
asset_api_uri = f"{release_api_uri}/{release_id}/attach_files"
132+
# 列出现有资产,避免重复上传导致 400/405
133+
existing_assets = {}
134+
try:
135+
# 获取单个 release 详情,其中包含 assets 列表
136+
release_detail_resp = api_client.get(f"{release_api_uri}/{release_id}", params={'access_token': access_token}, timeout=REQUEST_TIMEOUT)
137+
if release_detail_resp.status_code == 200:
138+
data = release_detail_resp.json()
139+
assets = data.get('assets') or []
140+
for a in assets:
141+
name = a.get('name')
142+
if name:
143+
existing_assets[name] = a.get('id')
144+
print(f"Existing assets: {list(existing_assets.keys())}")
145+
else:
146+
print(f"Get release detail failed: {release_detail_resp.status_code} {release_detail_resp.text}")
147+
except requests.RequestException as e:
148+
print(f"List assets error: {e}")
98149

99150
for file_path in files:
151+
file_name = os.path.basename(file_path)
100152
success = False
101-
102-
while not success:
103-
files = {
104-
'file': open(file_path, 'rb')
105-
}
106-
107-
asset_api_response = requests.post(
108-
asset_api_uri, params={'access_token': access_token}, files=files)
109-
110-
if asset_api_response.status_code == 201:
111-
asset_info = asset_api_response.json()
112-
asset_name = asset_info.get('name')
113-
print(f"Successfully uploaded {asset_name}!")
114-
success = True
115-
else:
116-
print(
117-
f"Request failed with status code {asset_api_response.status_code}")
153+
for attempt in range(1, MAX_UPLOAD_RETRIES + 1):
154+
try:
155+
# 如果已存在同名资产,则跳过上传
156+
if file_name in existing_assets:
157+
print(f"Skip upload {file_name}: already exists in release (asset id {existing_assets[file_name]})")
158+
success = True
159+
break
160+
with open(file_path, 'rb') as fh:
161+
resp = api_client.post(
162+
asset_api_uri,
163+
params={'access_token': access_token},
164+
files={'file': (file_name, fh, 'application/octet-stream')},
165+
timeout=UPLOAD_TIMEOUT,
166+
)
167+
if resp.status_code == 201:
168+
asset_info = resp.json()
169+
asset_name = asset_info.get('name')
170+
print(f"Successfully uploaded {asset_name}!")
171+
success = True
172+
break
173+
else:
174+
# 某些情况下返回400且文案提示已存在,视为成功
175+
txt = resp.text
176+
if resp.status_code in (400, 409, 422) and ('已存在' in txt or 'already exists' in txt):
177+
print(f"Asset {file_name} already exists, treat as success.")
178+
success = True
179+
break
180+
print(f"Upload failed (status {resp.status_code}) for {file_path}, attempt {attempt}/{MAX_UPLOAD_RETRIES}. Body: {txt[:256]}")
181+
except requests.RequestException as e:
182+
print(f"Upload error for {file_path}: {e} (attempt {attempt}/{MAX_UPLOAD_RETRIES})")
183+
time.sleep(min(60, 2 ** (attempt - 1)))
184+
if not success:
185+
raise RuntimeError(f"Failed to upload {file_path} after {MAX_UPLOAD_RETRIES} attempts")
118186

119187
# 仅保留最新 Release 以防超出 Gitee 仓库配额
120188
try:

.github/workflows/sync-release.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ on:
66
jobs:
77
sync-release-to-gitee:
88
runs-on: ubuntu-latest
9-
timeout-minutes: 30
9+
timeout-minutes: 60
1010
env:
1111
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
12+
GH_TOKEN: ${{ github.token }}
1213
steps:
1314
- uses: actions/checkout@v4
1415
- name: Sync to Gitee

0 commit comments

Comments
 (0)