"""전체 배포 상태 종합 검증""" import paramiko, sys, 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() J = 'http://127.0.0.1:9080' JA = 'admin:Admin@2026!' def run(cmd, timeout=15): _, o, _ = c.exec_command(cmd, timeout=timeout) return o.read().decode('utf-8','replace').strip() def ok(v): return '✅' if v else '❌' results = {} print('=' * 60) print('GUARDiA 전체 배포 상태 검증') print('=' * 60) # ── 1. 서비스 상태 ───────────────────────────────────────── print('\n■ 1. 서비스 상태') services = { 'GUARDiA ITSM (9001)': 'guardia', '홈페이지 (8082)': 'zioinfo', 'Manager (8090)': 'guardia-manager', 'Webhook (9999)': 'zioinfo-deploy', 'Jenkins (9080)': 'jenkins', 'zioinfo-mail (8026)': 'zioinfo-mail', 'Gitea (9003)': 'gitea', } for name, svc in services.items(): status = run(f'systemctl is-active {svc} 2>/dev/null') print(f' {ok(status=="active")} {name}: {status}') # ── 2. ITSM API 엔드포인트 ────────────────────────────────── print('\n■ 2. GUARDiA ITSM API') api_info = run( 'curl -sf http://localhost:9001/openapi.json 2>/dev/null | ' 'python3 -c "import sys,json; d=json.load(sys.stdin); paths=d[\'paths\']; ' 'adv=[p for p in paths if any(k in p for k in [\'/api/ocr\',\'/api/docflow\',\'/api/autodiscovery\',\'/api/nlquery\',\'/api/drift\',\'/api/kpi\',\'/api/multicloud\'])]; ' 'print(f\'전체: {len(paths)}개 | OCR+확장: {len(adv)}개\')" 2>/dev/null || echo "API 확인 불가"' ) print(f' ✅ {api_info}') # 주요 신규 기능 엔드포인트 확인 key_paths = ['/api/ocr/parse', '/api/docflow/brand-contract', '/api/nlquery/ask', '/api/drift/scan/{server_id}', '/api/kpi/dashboard', '/api/autodiscovery/scan', '/api/multicloud/providers', '/api/narasajang/bids'] existing = run( 'curl -sf http://localhost:9001/openapi.json 2>/dev/null | ' 'python3 -c "import sys,json; d=json.load(sys.stdin)[\'paths\']; print(\'|\'.join(d.keys()))" 2>/dev/null' ) for path in key_paths: exists = path in existing print(f' {ok(exists)} {path}') # ── 3. DB 테이블 ──────────────────────────────────────────── print('\n■ 3. 신규 DB 테이블') db_check = """ import asyncio, sys sys.path.insert(0, '/opt/guardia/app') from database import engine from sqlalchemy import text TABLES = [ 'tb_upstage_ocr_config', 'tb_ocr_history', 'tb_doc_workflow_job', 'tb_doc_template', 'tb_discovery_scan_job', 'tb_cmdb_autodiscovery', 'tb_service_dependency', 'tb_query_history', 'tb_assistant_session', 'tb_golden_config', 'tb_drift_result', 'tb_multicloud_config', 'tb_kpi_definition', 'tb_kpi_value', 'tb_jira_config', 'tb_slack_config', 'tb_tenant_branding', 'tb_narasajang_config', 'tb_network_zone', 'tb_procurement_record', 'tb_learning_run', 'tb_container_alert_rule', 'tb_aws_config', ] async def check(): ok_cnt = fail_cnt = 0 async with engine.connect() as conn: for t in TABLES: try: await conn.execute(text(f"SELECT 1 FROM {t} LIMIT 0")) ok_cnt += 1 except: fail_cnt += 1 print(f" MISS: {t}") print(f" DB 테이블: {ok_cnt}/{len(TABLES)}개 정상") asyncio.run(check()) """ with sftp.open('/tmp/dbchk.py', 'w') as f: f.write(db_check) print(run('cd /opt/guardia/app && /opt/guardia/venv/bin/python3 /tmp/dbchk.py 2>&1; rm /tmp/dbchk.py', timeout=20)) # ── 4. 홈페이지 ───────────────────────────────────────────── print('\n■ 4. 홈페이지 (zioinfo.co.kr)') www_files = run('ls /var/www/zioinfo/assets/*.js 2>/dev/null | wc -l') www_date = run('stat /var/www/zioinfo/index.html 2>/dev/null | grep Modify | cut -d" " -f2,3') guardia_js = run('ls /var/www/zioinfo/assets/GuardiaDetail*.js 2>/dev/null | sort | tail -1 | xargs ls -lh 2>/dev/null') print(f' ✅ 정적 파일: {www_files}개') print(f' ✅ index.html: {www_date}') print(f' ✅ GuardiaDetail: {guardia_js}') homepage_ok = run('curl -sf -o /dev/null -w "%{http_code}" https://zioinfo.co.kr/ -L 2>/dev/null || echo "0"') print(f' {ok(homepage_ok in ("200","301","302"))} HTTP: {homepage_ok}') # ── 5. Manager ────────────────────────────────────────────── print('\n■ 5. GUARDiA Manager (8090)') mgr_date = run('stat /var/www/manager/index.html 2>/dev/null | grep Modify | cut -d" " -f2,3') mgr_files = run('ls /var/www/manager/assets/*.js 2>/dev/null | wc -l') print(f' ✅ 정적 파일: {mgr_files}개 | index.html: {mgr_date}') # ── 6. zioinfo-mail ───────────────────────────────────────── print('\n■ 6. 웹메일 (mail:8025, API:8026)') mail_health = run('curl -sf http://localhost:8026/health 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get(\'status\'))" 2>/dev/null || echo "응답없음"') mail_www = run('ls /var/www/mail/assets/*.js 2>/dev/null | wc -l') print(f' {ok(mail_health=="ok")} API health: {mail_health}') print(f' ✅ 프론트엔드: {mail_www}개 JS 파일') # ── 7. Jenkins CI/CD ──────────────────────────────────────── print('\n■ 7. Jenkins CI/CD (9080)') jenkins_jobs = run( f'curl -sf -u "{JA}" {J}/api/json 2>/dev/null | ' 'python3 -c "import sys,json; d=json.load(sys.stdin); jobs=d[\'jobs\']; ' 'blue=sum(1 for j in jobs if j[\'color\']==\'blue\'); ' 'print(f\'{blue}/{len(jobs)}개 blue\')" 2>/dev/null || echo "확인불가"' ) print(f' ✅ Jenkins jobs: {jenkins_jobs}') # ── 8. Gitea repos ────────────────────────────────────────── print('\n■ 8. Gitea 저장소 현황') for repo in ['zioinfo-web', 'guardia-itsm', 'guardia-manager', 'guardia-messenger', 'guardia-docs', 'zioinfo-mail']: commit = run( f'curl -sf "http://127.0.0.1:9003/api/v1/repos/zio/{repo}/commits?limit=1" ' f'-H "Authorization: Basic {G}" 2>/dev/null | ' 'python3 -c "import sys,json; c=json.load(sys.stdin)[0]; print(c[\'sha\'][:8], c[\'commit\'][\'message\'][:45])" 2>/dev/null || echo "확인불가"' ) print(f' ✅ {repo}: {commit}') # ── 9. Webhook 배포 서버 ──────────────────────────────────── print('\n■ 9. Webhook 배포 서버') webhook_repos = run("grep -c 'elif repo ==' /opt/zioinfo/deploy_server.py 2>/dev/null || echo 0") webhook_port = run('ss -tlnp | grep 9999 2>/dev/null | head -1') print(f' ✅ 지원 repo: {webhook_repos}개') print(f' {ok("9999" in webhook_port)} 포트 9999: {"LISTEN" if "9999" in webhook_port else "없음"}') # ── 요약 ──────────────────────────────────────────────────── print('\n' + '=' * 60) print('■ 종합 요약') print('=' * 60) print(f' ITSM: {api_info}') print(f' 서비스: 7개 중 {"7" if all(run(f"systemctl is-active {s} 2>/dev/null")=="active" for s in services.values()) else "일부"} active') print(f' 홈페이지: www {www_files}개 파일') print(f' 웹메일: API {mail_health}') print(f' CI/CD: Jenkins {jenkins_jobs}') sftp.close(); c.close()