@@ -20,30 +20,25 @@ jobs:
2020 with :
2121 python-version : ' 3.11'
2222
23- - name : Force Sync Multi-Arch Providers
23+ - name : Force Sync & Gen Index
24+ shell : python
2425 run : |
25- # 创建镜像根目录
26- mkdir -p public/tf-mirror
27-
28- # 执行 Python 脚本
29- python3 -c "
3026 import json
3127 import urllib.request
3228 import urllib.error
3329 import shutil
3430 import os
3531 import sys
32+ import datetime
3633 from pathlib import Path
3734 from concurrent.futures import ThreadPoolExecutor, as_completed
3835
3936 # ================= 配置区域 =================
40- # 常用官方小工具
4137 PROVIDERS = [
4238 'random', 'null', 'time', 'local',
4339 'tls', 'http', 'archive', 'external'
4440 ]
4541
46- # 强制同步的平台列表 (Windows, Linux, macOS)
4742 TARGET_PLATFORMS = [
4843 ('linux', 'amd64'), ('linux', 'arm64'),
4944 ('windows', 'amd64'),
5247
5348 MIRROR_ROOT = Path('public/tf-mirror')
5449 REGISTRY_API = 'https://registry.terraform.io/v1/providers/hashicorp'
50+ # 修改为您实际的 GitHub Pages 地址
51+ MIRROR_URL = 'https://redc.wgpsec.org/tf-mirror/'
5552 MAX_WORKERS = 4
5653 # ===========================================
5754
55+ # 确保目录存在
56+ MIRROR_ROOT.mkdir(parents=True, exist_ok=True)
57+
5858 class MirrorSync:
5959 def __init__(self, base_dir: Path):
6060 self.base_dir = base_dir
6767 def download_file(self, url, dest_path: Path):
6868 if dest_path.exists() and dest_path.stat().st_size > 0:
6969 return f'⏩ Skip: {dest_path.name}'
70-
7170 try:
7271 dest_path.parent.mkdir(parents=True, exist_ok=True)
7372 req = urllib.request.Request(url, headers={'User-Agent': 'TF-Mirror-Bot'})
@@ -81,87 +80,145 @@ jobs:
8180 def process_provider(self, name):
8281 print(f'🔍 Analyzing hashicorp/{name}...')
8382 try:
84- # 1. 获取最新版本
8583 meta = self.fetch_json(f'{REGISTRY_API}/{name}')
8684 version = meta['version']
8785
88- # 准备目录结构
89- # 格式: registry.terraform.io/hashicorp/{name}/{version}/{os}_{arch}/
9086 provider_dir = self.base_dir / 'registry.terraform.io' / 'hashicorp' / name
9187
92- # 2. 准备下载任务
9388 tasks = {}
9489 platform_meta = []
9590
9691 with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
97- # A. 下载各平台 ZIP 包
9892 for os_name, arch in TARGET_PLATFORMS:
9993 try:
10094 dl_api = f'{REGISTRY_API}/{name}/{version}/download/{os_name}/{arch}'
10195 dl_info = self.fetch_json(dl_api)
102-
10396 filename = dl_info['filename']
10497 file_url = dl_info['download_url']
10598
106- # 本地存放路径
10799 save_dir = provider_dir / version / f'{os_name}_{arch}'
108100 dest = save_dir / filename
109101
110102 tasks[executor.submit(self.download_file, file_url, dest)] = f'{os_name}/{arch}'
111-
112- # 记录到索引元数据中
113103 platform_meta.append({'os': os_name, 'arch': arch})
114-
115- except urllib.error.HTTPError as e:
116- print(f' ⚠️ {name} {version} not found for {os_name}/{arch}')
104+ except urllib.error.HTTPError:
105+ # 某些版本可能不支持特定架构,忽略即可
106+ pass
117107
118- # B. 下载 SHA256SUMS 和 签名 (重要! Terraform 校验需要)
119- # 通常在任意一个下载链接的同级目录
108+ # 下载签名文件
120109 if 'file_url' in locals():
121110 base_url_sums = file_url.rsplit('/', 1)[0]
122111 sums_name = f'terraform-provider-{name}_{version}_SHA256SUMS'
123-
124- # 存放到 version 根目录
125112 version_root = provider_dir / version
126113 tasks[executor.submit(self.download_file, f'{base_url_sums}/{sums_name}', version_root / sums_name)] = 'SHA256SUMS'
127114 tasks[executor.submit(self.download_file, f'{base_url_sums}/{sums_name}.sig', version_root / f'{sums_name}.sig')] = 'Signature'
128115
129- # 等待所有下载完成
130116 for future in as_completed(tasks):
131117 print(f' {future.result()}')
132118
133- # 3. 生成 index.json (关键! Network Mirror 协议)
134- # 路径: registry.terraform.io/hashicorp/{name}/index.json
135- index_data = {
136- 'versions': {
137- version: {
138- 'protocols': ['5.0'],
139- 'platforms': platform_meta
140- }
141- }
142- }
119+ # 生成 index.json
120+ index_data = {'versions': {version: {'protocols': ['5.0'], 'platforms': platform_meta}}}
143121 with open(provider_dir / 'index.json', 'w') as f:
144122 json.dump(index_data, f, indent=2)
145123
146- return f'✅ Finished: { name} v{ version}'
124+ return {'name': name, ' version': version, 'status': 'success'}
147125
148126 except Exception as e:
149- return f'🚫 Error: {name} - {e}'
127+ print(f'🚫 Error: {name} - {e}')
128+ return {'name': name, 'version': 'Error', 'status': 'failed'}
150129
151- # --- 主程序 ---
130+ # --- Main Execution ---
152131 syncer = MirrorSync(MIRROR_ROOT)
132+ results = []
153133
154- # 并发处理多个 Provider
155134 with ThreadPoolExecutor(max_workers=3) as main_executor:
156135 futures = {main_executor.submit(syncer.process_provider, p): p for p in PROVIDERS}
157136 for f in as_completed(futures):
158- print(f.result())
159- "
137+ res = f.result()
138+ results.append(res)
139+ print(f"✅ Finished processing {res['name']}")
160140
161- - name : Verify Windows Files
162- run : |
163- echo "📂 Checking for Windows binaries:"
164- find public/tf-mirror -name "*windows_amd64*" | head -n 5 || echo "No windows files found"
141+ # --- Generate index.html (Dynamic Config) ---
142+ print('📝 Generating index.html...')
143+ timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')
144+
145+ sorted_providers = sorted(PROVIDERS)
146+ config_list = [f'"registry.terraform.io/hashicorp/{p}"' for p in sorted_providers]
147+ config_str = ",\n ".join(config_list)
148+
149+ html_content = f"""
150+ <!DOCTYPE html>
151+ <html lang="en">
152+ <head>
153+ <meta charset="UTF-8">
154+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
155+ <title>Terraform Provider Mirror</title>
156+ <style>
157+ body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 2rem; color: #333; line-height: 1.6; }}
158+ h1 {{ border-bottom: 2px solid #eee; padding-bottom: 0.5rem; }}
159+ .status {{ background: #f6f8fa; padding: 1rem; border-radius: 6px; margin-bottom: 2rem; border: 1px solid #e1e4e8; }}
160+ code {{ background: #eee; padding: 0.2rem 0.4rem; border-radius: 3px; font-family: monospace; font-size: 0.9em; }}
161+ pre {{ background: #2d333b; color: #adbac7; padding: 1.5rem; border-radius: 6px; overflow-x: auto; font-family: monospace; }}
162+ .grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; margin-top: 1rem; }}
163+ .card {{ border: 1px solid #e1e4e8; border-radius: 6px; padding: 1rem; transition: transform 0.2s; }}
164+ .card:hover {{ transform: translateY(-2px); box-shadow: 0 4px 6px rgba(0,0,0,0.1); }}
165+ .name {{ font-weight: bold; color: #0366d6; margin-bottom: 0.5rem; }}
166+ .ver {{ font-size: 0.85em; color: #586069; background: #f1f8ff; display: inline-block; padding: 2px 8px; border-radius: 12px; }}
167+ footer {{ margin-top: 3rem; text-align: center; color: #666; font-size: 0.85rem; border-top: 1px solid #eee; padding-top: 1rem; }}
168+ a {{ color: #0366d6; text-decoration: none; }}
169+ a:hover {{ text-decoration: underline; }}
170+ </style>
171+ </head>
172+ <body>
173+ <h1>Terraform Provider Mirror</h1>
174+ <div class="status">
175+ <p><strong>Last Updated:</strong> {timestamp}</p>
176+ <p><strong>Status:</strong> ✅ Ready</p>
177+ </div>
178+
179+ <h2>Usage Configuration</h2>
180+ <p>Add the following to your <code>~/.terraformrc</code> or <code>terraform.rc</code>:</p>
181+ <pre>
182+ provider_installation {{
183+ network_mirror {{
184+ url = "{MIRROR_URL}"
185+ # Automatically generated based on synced providers
186+ include = [
187+ {config_str}
188+ ]
189+ }}
190+ direct {{
191+ # Exclude mirrors to avoid conflicts
192+ exclude = [
193+ {config_str}
194+ ]
195+ }}
196+ }}</pre>
197+
198+ <h2>Synced Providers ({len(results)})</h2>
199+ <div class="grid">
200+ " " "
201+
202+ for r in sorted(results, key=lambda x: x['name']):
203+ if r['status'] == 'success':
204+ html_content += f" " "
205+ <div class="card">
206+ <div class="name">hashicorp/{r['name']}</div>
207+ <div class="ver">v{r['version']}</div>
208+ </div>"""
209+
210+ html_content += """
211+ </div>
212+ <footer>
213+ wgpsec terraform mirror | <a href="https://github.com/wgpsec/redc-template">Source Repository</a>
214+ </footer>
215+ </body>
216+ </html>
217+ " " "
218+
219+ with open(MIRROR_ROOT / 'index.html', 'w', encoding='utf-8') as f:
220+ f.write(html_content)
221+ print('✅ index.html created successfully.')
165222
166223 - name: Deploy to gh-pages
167224 uses: peaceiris/actions-gh-pages@v3
@@ -170,4 +227,4 @@ jobs:
170227 publish_dir: ./public
171228 publish_branch: gh-pages
172229 keep_files: true
173- commit_message : " Mirror: Force sync Windows/Linux/Mac tools "
230+ commit_message: " Mirror: Sync providers & update dynamic index.html "
0 commit comments