"""在用户指定的开发机或服务器运行;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)