"""zioinfo-mail CI/CD 전체 구축: 1. deploy_server.py에 zioinfo-mail 배포 함수 추가 2. Jenkins job 생성 3. Gitea webhook 등록 (port 9999 + Jenkins) 4. Jenkinsfile Gitea push """ import paramiko, sys, json, base64, subprocess, os, 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() J = 'http://127.0.0.1:9080' A = 'admin:Admin@2026!' TOK = 'gitea-build-2026' def run(label, cmd, timeout=30): print(f'\n[{label}]') _, o, _ = c.exec_command(cmd, timeout=timeout) out = o.read().decode('utf-8','replace').strip() if out: print(out[:500]) return out # crumb _, o, _ = c.exec_command(f'curl -sf -u "{A}" {J}/crumbIssuer/api/json 2>/dev/null', timeout=10) try: cd = json.loads(o.read().decode('utf-8','replace').strip()) CH = f'{cd["crumbRequestField"]}: {cd["crumb"]}' except: CH = 'Jenkins-Crumb: x' # ── 1. deploy_server.py 업데이트 ───────────────────────────── print('\n━━ 1. deploy_server.py zioinfo-mail 추가 ━━') update_script = r""" import re with open('/opt/zioinfo/deploy_server.py', 'r') as f: content = f.read() if 'zioinfo-mail' in content: print('이미 있음') else: # guardia-docs 블록 이후에 zioinfo-mail 추가 # 또는 elif repo == "guardia-docs": 다음에 추가 MAIL_BLOCK = ''' elif repo == "zioinfo-mail": SRC = "/opt/mail" ok = run_steps(repo, [ ("git pull", ["bash", "-c", f"[ -d {SRC}/src/.git ] && git -C {SRC}/src fetch origin main && git -C {SRC}/src reset --hard origin/main" f" || git clone 'http://zio:Zio%40Admin2026%21@127.0.0.1:9003/zio/zioinfo-mail.git' {SRC}/src"]), ("npm build", ["bash", "-c", f"cd {SRC}/src/frontend && npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps && npm run build"]), ("copy dist", ["bash", "-c", f"cp -r {SRC}/src/dist/. /var/www/mail/"]), ("pip install", ["bash", "-c", f"{SRC}/venv/bin/pip install -r {SRC}/src/backend/requirements.txt -q"]), ("rsync", ["bash", "-c", f"rsync -a --exclude=__pycache__ --exclude=.git --exclude='*.pyc' --exclude='.env' {SRC}/src/backend/ {SRC}/backend/"]), ("restart", ["systemctl", "restart", "zioinfo-mail"]), ("health check", ["bash", "-c", "sleep 4 && curl -sf http://localhost:8026/health"]), ]) if ok: notify_itsm(True, "\\u2705 zioinfo-mail \\ubc30\\ud3ec \\uc644\\ub8cc") else: notify_itsm(False, "\\u274c zioinfo-mail \\ube4c\\ub4dc \\uc2e4\\ud328") ''' # guardia-docs 블록 직전에 삽입 content = content.replace( ' elif repo == "guardia-docs":', MAIL_BLOCK + ' elif repo == "guardia-docs":' ) with open('/opt/zioinfo/deploy_server.py', 'w') as f: f.write(content) print('추가 완료') """ with sftp.open('/tmp/upd.py', 'w') as f: f.write(update_script) run('deploy_server 업데이트', 'python3 /tmp/upd.py 2>&1; rm /tmp/upd.py') run('zioinfo-mail 추가 확인', "grep -n 'zioinfo-mail' /opt/zioinfo/deploy_server.py") run('webhook 서버 재시작', 'systemctl restart zioinfo-deploy && sleep 2 && systemctl is-active zioinfo-deploy') # ── 2. Jenkins job 생성 ────────────────────────────────────── print('\n━━ 2. Jenkins job 생성 (zioinfo-mail) ━━') job_config = f""" GUARDiA zioinfo-mail Webmail CI/CD false {TOK} 2 http://127.0.0.1:9003/zio/zioinfo-mail.git gitea-zio */main false Jenkinsfile true """ with sftp.open('/tmp/job_mail.xml', 'w') as f: f.write(job_config) run('Jenkins job 생성', f'curl -sf -X POST "{J}/createItem?name=zioinfo-mail" ' f'-u "{A}" -H "{CH}" ' f'-H "Content-Type: text/xml" ' f'--data-binary @/tmp/job_mail.xml 2>/dev/null; echo $?') # authToken 설정 run('authToken config.xml 설정', f'curl -sf -u "{A}" {J}/job/zioinfo-mail/config.xml 2>/dev/null | ' f'grep -c "authToken" || echo "없음"') # ── 3. Gitea webhook 등록 ──────────────────────────────────── print('\n━━ 3. Gitea webhook 등록 ━━') # 3-1. port 9999 webhook payload9999 = json.dumps({ "type": "gitea", "config": {"url": "http://127.0.0.1:9999", "content_type": "json", "secret": "zioinfo-deploy-2026"}, "events": ["push"], "active": True }) with sftp.open('/tmp/h1.json', 'w') as f: f.write(payload9999) run('webhook 9999 등록', f'curl -sf -X POST "http://127.0.0.1:9003/api/v1/repos/zio/zioinfo-mail/hooks" ' f'-H "Authorization: Basic {G}" -H "Content-Type: application/json" ' f'--data @/tmp/h1.json 2>/dev/null | ' 'python3 -c "import sys,json; d=json.load(sys.stdin); print(\'id:\',d.get(\'id\'),d.get(\'config\',{}).get(\'url\',\'\'))" 2>/dev/null') # 3-2. Jenkins webhook payload_jenkins = json.dumps({ "type": "gitea", "config": {"url": f"http://127.0.0.1:9080/job/zioinfo-mail/build?token={TOK}", "content_type": "json"}, "events": ["push"], "active": True }) with sftp.open('/tmp/h2.json', 'w') as f: f.write(payload_jenkins) run('webhook Jenkins 등록', f'curl -sf -X POST "http://127.0.0.1:9003/api/v1/repos/zio/zioinfo-mail/hooks" ' f'-H "Authorization: Basic {G}" -H "Content-Type: application/json" ' f'--data @/tmp/h2.json 2>/dev/null | ' 'python3 -c "import sys,json; d=json.load(sys.stdin); print(\'id:\',d.get(\'id\'),d.get(\'config\',{}).get(\'url\',\'\'))" 2>/dev/null') # ── 4. Jenkinsfile Gitea push ───────────────────────────────── print('\n━━ 4. Jenkinsfile → Gitea push ━━') jf_content = open('C:/GUARDiA/workspace/zioinfo-mail/Jenkinsfile', encoding='utf-8').read() jf_b64 = base64.b64encode(jf_content.encode('utf-8')).decode() # 기존 파일 SHA 확인 _, o, _ = c.exec_command( f'curl -sf "http://127.0.0.1:9003/api/v1/repos/zio/zioinfo-mail/contents/Jenkinsfile" ' 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) sha = o.read().decode('utf-8','replace').strip() if sha: payload_jf = json.dumps({"message":"ci: add Jenkinsfile","content":jf_b64,"sha":sha,"branch":"main"}) method = "PUT" else: payload_jf = json.dumps({"message":"ci: add Jenkinsfile","content":jf_b64,"branch":"main"}) method = "POST" with sftp.open('/tmp/jf.json', 'w') as f: f.write(payload_jf) run('Jenkinsfile Gitea 업로드', f'curl -sf -X {method} "http://127.0.0.1:9003/api/v1/repos/zio/zioinfo-mail/contents/Jenkinsfile" ' f'-H "Authorization: Basic {G}" -H "Content-Type: application/json" ' f'--data @/tmp/jf.json 2>/dev/null | ' 'python3 -c "import sys,json; d=json.load(sys.stdin); print(\'OK:\', d.get(\'content\',{}).get(\'name\',\'?\'))" 2>/dev/null') # ── 5. 검증 ───────────────────────────────────────────────── print('\n━━ 5. 최종 검증 ━━') time.sleep(3) run('Jenkins jobs 전체', f'curl -sf -u "{A}" {J}/api/json 2>/dev/null | ' 'python3 -c "import sys,json; [print(j[\'name\'].ljust(22),j[\'color\']) for j in json.load(sys.stdin)[\'jobs\']]" 2>/dev/null') run('zioinfo-mail hooks 확인', f'curl -sf "http://127.0.0.1:9003/api/v1/repos/zio/zioinfo-mail/hooks" ' f'-H "Authorization: Basic {G}" 2>/dev/null | ' 'python3 -c "import sys,json; [print(\' hook\',h[\'id\'],h[\'config\'].get(\'url\',\'\')[:60],\'active:\',h[\'active\']) for h in json.load(sys.stdin)]" 2>/dev/null') # 첫 빌드 트리거 run('첫 빌드 트리거', f'curl -sf -X POST -u "{A}" -H "{CH}" {J}/job/zioinfo-mail/build 2>/dev/null && echo "트리거됨"') time.sleep(10) run('빌드 상태', f'curl -sf -u "{A}" {J}/job/zioinfo-mail/api/json 2>/dev/null | ' 'python3 -c "import sys,json; d=json.load(sys.stdin); ' 'lb=d.get(\'lastBuild\',{}); print(\'build #\'+str(lb.get(\'number\',\'?\')))" 2>/dev/null') sftp.close(); c.close() print('\n=== zioinfo-mail CI/CD 구축 완료 ===')