cpuai's picture
Deploy BlockBridge Minecraft file converter 2.1.0 with independent world conversion
7056f70 verified
Raw History Blame Contribute Delete
4.27 kB
"""在用户指定的开发机或服务器运行;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)