File size: 4,270 Bytes
dead840
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7056f70
dead840
 
 
 
 
 
 
 
 
 
 
05232e4
 
dead840
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
"""在用户指定的开发机或服务器运行;HF_TOKEN 仅从环境变量读取。"""
from pathlib import Path
import argparse,json,os,sys,time

def main():
    parser=argparse.ArgumentParser(description='发布我的世界文件转换器到 Hugging Face Docker Space')
    parser.add_argument('--repo',help='可选的 owner/space-name,默认当前账号/minecraft-file-converter')
    parser.add_argument('--private',action='store_true',help='创建私有 Space')
    parser.add_argument('--verify-only',action='store_true',help='仅检查已存在空间')
    args=parser.parse_args()
    token=os.environ.get('HF_TOKEN')
    if not token: parser.error('请先在当前进程设置 HF_TOKEN 环境变量,不要把密钥写进脚本')
    try:
        from huggingface_hub import HfApi
        import httpx
    except ImportError: parser.error('请先运行 python -m pip install huggingface_hub httpx')
    api=HfApi(token=token); owner=api.whoami()['name']; repo=args.repo or owner+'/minecraft-file-converter'
    if not args.verify_only:
        api.create_repo(repo,repo_type='space',space_sdk='docker',private=args.private,exist_ok=True)
        api.upload_folder(repo_id=repo,repo_type='space',folder_path=str(Path(__file__).resolve().parents[1]),
                          ignore_patterns=['.venv/**','.git/**','**/__pycache__/**','.pytest_cache/**','**/*.log','.env*','test-results/**','artifacts/**'],
                          commit_message='Deploy BlockBridge Minecraft file converter 2.1.0 with independent world conversion')
        print('源码已上传: https://huggingface.co/spaces/'+repo,flush=True)
    # 空间构建是异步过程。只在真正可访问且完成在线转换时报告部署成功。
    headers={'Authorization':'Bearer '+token}
    client=httpx.Client(timeout=60,headers=headers,follow_redirects=True)
    space_url=None
    for attempt in range(120):
        info=api.space_info(repo); runtime=api.get_space_runtime(repo)
        stage=str(runtime.stage); print('构建状态:'+stage,flush=True)
        if any(x in stage for x in ['BUILD_ERROR','RUNTIME_ERROR','CONFIG_ERROR']): raise RuntimeError('Space 构建/运行失败,请查看空间日志')
        host=getattr(info,'host',None)
        if host: space_url=host if host.startswith('https://') else 'https://'+host
        # 重发时旧容器可能仍返回 200,必须等待本次构建完成。
        if space_url and stage == 'RUNNING':
            try:
                r=client.get(space_url+'/healthz')
                if r.status_code==200 and r.json().get('status')=='ok': break
            except (httpx.HTTPError,ValueError): pass
        time.sleep(10)
    else: raise RuntimeError('等待空间启动超时;不能标记部署成功')
    sample=client.get(space_url+'/api/samples/bedrock'); sample.raise_for_status()
    upload=client.post(space_url+'/api/upload',files={'file':('online-test.mcstructure',sample.content)}); upload.raise_for_status(); key=upload.json()['job_id']
    def wait_for(expected):
        for _ in range(120):
            r=client.get(space_url+'/api/jobs/'+key); r.raise_for_status(); state=r.json()
            if state['state']==expected: return state
            if state['state'] in ('error','cancelled'): raise RuntimeError('在线验收失败:'+state.get('message',''))
            time.sleep(2)
        raise RuntimeError('在线验收超时')
    wait_for('ready')
    client.post(space_url+'/api/jobs/'+key+'/convert',json={'target':'mcworld','strict':True}).raise_for_status()
    result=wait_for('done')
    artifact=client.get(space_url+'/api/jobs/'+key+'/files/converted.mcworld'); artifact.raise_for_status()
    if not artifact.content.startswith(b'PK'): raise RuntimeError('在线下载不是有效世界 ZIP')
    print(json.dumps({'deployed':True,'repo':repo,'url':space_url,'online_conversion_verified':True,'bytes':len(artifact.content),'validation':result['result']['report']['validation']},ensure_ascii=False,indent=2))

if __name__=='__main__':
    try: main()
    except Exception as e:
        # 不打印带认证头的 HTTP 调试信息。
        print('部署未完成:'+str(e).replace(os.environ.get('HF_TOKEN','\0'),'[REDACTED]'),file=sys.stderr)
        raise SystemExit(1)