"""Gitea Contents API로 zioinfo-mail 파일 직접 업로드""" import paramiko, sys, os, json, base64, time sys.stdout.reconfigure(encoding='utf-8', errors='replace') c = paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect('101.79.17.164', username='root', password='1q2w3e!Q', timeout=15) sftp = c.open_sftp() G = base64.b64encode(b'zio:Zio@Admin2026!').decode() def gitea_put(path: str, content_bytes: bytes, message: str, sha: str = None): enc = base64.b64encode(content_bytes).decode() if sha: payload = json.dumps({"message": message, "content": enc, "sha": sha, "branch": "main"}) method = "PUT" else: payload = json.dumps({"message": message, "content": enc, "branch": "main"}) method = "POST" with sftp.open('/tmp/gf.json', 'w') as f: f.write(payload) _, o, _ = c.exec_command( f'curl -sf -o /dev/null -w "%{{http_code}}" -X {method} ' f'"http://127.0.0.1:9003/api/v1/repos/zio/zioinfo-mail/contents/{path}" ' f'-H "Authorization: Basic {G}" -H "Content-Type: application/json" ' f'--data @/tmp/gf.json 2>/dev/null', timeout=10) return o.read().decode('utf-8','replace').strip() def get_sha(path: str) -> str: _, o, _ = c.exec_command( f'curl -sf "http://127.0.0.1:9003/api/v1/repos/zio/zioinfo-mail/contents/{path}" ' f'-H "Authorization: Basic {G}" 2>/dev/null | ' 'python3 -c "import sys,json; print(json.load(sys.stdin).get(\'sha\',\'\'))" 2>/dev/null', timeout=10) return o.read().decode('utf-8','replace').strip() # 먼저 Gitea repo 완전 초기화 (DELETE + RECREATE) print('[Gitea repo 재생성]') _, o, _ = c.exec_command( f'curl -sf -X DELETE "http://127.0.0.1:9003/api/v1/repos/zio/zioinfo-mail" ' f'-H "Authorization: Basic {G}" 2>/dev/null; echo $?', timeout=10) print(f' delete: {o.read().decode().strip()}') time.sleep(1) _, o, _ = c.exec_command( f'curl -sf -X POST "http://127.0.0.1:9003/api/v1/user/repos" ' f'-H "Authorization: Basic {G}" -H "Content-Type: application/json" ' f'-d \'{{"name":"zioinfo-mail","private":false,"auto_init":false}}\' 2>/dev/null | ' 'python3 -c "import sys,json; print(json.load(sys.stdin).get(\'full_name\',\'?\'))" 2>/dev/null', timeout=10) print(f' create: {o.read().decode().strip()}') time.sleep(1) # 업로드할 파일 목록 WS = 'C:/GUARDiA/workspace/zioinfo-mail' EXCLUDE = {'node_modules', '__pycache__', '.git', '.pytest_cache', 'dist', '.expo'} BINARY_EXT = {'.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot'} files_to_upload = [] for root, dirs, files in os.walk(WS): dirs[:] = [d for d in dirs if d not in EXCLUDE] for fn in files: if fn.endswith(('.pyc', '.db', '.tsbuildinfo')): continue fp = os.path.join(root, fn) rel = os.path.relpath(fp, WS).replace('\\', '/') files_to_upload.append((rel, fp)) # .gitignore 추가 files_to_upload.append(('.gitignore', None)) print(f'\n업로드 파일: {len(files_to_upload)}개') ok = fail = 0 for rel, fp in files_to_upload: try: if fp is None: # .gitignore content_bytes = b'node_modules/\ndist/\n__pycache__/\n*.pyc\n.env\n*.db\n.tsbuildinfo\n' else: with open(fp, 'rb') as f: content_bytes = f.read() # 파일이 너무 크면 스킵 (package-lock.json 등) if len(content_bytes) > 500_000: print(f' SKIP (too large): {rel} ({len(content_bytes)//1024}KB)') continue code = gitea_put(rel, content_bytes, f'feat: add {rel}') if code in ('200', '201'): ok += 1 if ok % 5 == 0: print(f' {ok}/{len(files_to_upload)} 완료...') else: fail += 1 print(f' FAIL {code}: {rel}') except Exception as e: fail += 1 print(f' ERR {rel}: {e}') print(f'\n완료: {ok}개 성공, {fail}개 실패') # Gitea 파일 확인 _, o, _ = c.exec_command( f'curl -sf "http://127.0.0.1:9003/api/v1/repos/zio/zioinfo-mail/contents/" ' f'-H "Authorization: Basic {G}" 2>/dev/null | ' 'python3 -c "import sys,json; [print(f[\'name\'],f[\'type\']) for f in json.load(sys.stdin)[:10]]" 2>/dev/null', timeout=10) print('\n[Gitea 파일 목록]\n' + o.read().decode('utf-8','replace').strip()) sftp.close(); c.close() print('\n=== 완료 ===')