"""system-sync-orchestrator: 검증 결과 기반 수정 실행""" import paramiko, sys, time, subprocess, shutil, os, json, base64 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 run(label, cmd, timeout=120): print(f'\n [{label}]') _, o, e = c.exec_command(cmd, timeout=timeout) out = o.read().decode('utf-8','replace').strip() if out: print(' ' + out[:400]) return out results = {} # ── 1. guardia-itsm: APP_SRC_DRIFT 수정 ──────────────────── print('\n━━ FIX 1: guardia-itsm APP_SRC_DRIFT ━━') # rpa_rules.json, embed_codebase.py는 app에서만 생성되는 런타임 파일 → src에서도 생성 경로 동기화 run('rpa 폴더 확인', 'ls /opt/guardia/src/routers/rpa.py 2>/dev/null && echo exists || echo missing') run('rsync src→app (런타임 파일 제외)', 'rsync -a --delete ' '--exclude=__pycache__ --exclude=.git --exclude="*.db" ' '--exclude="uploads" --exclude=".env" --exclude=".pytest_cache" ' '--exclude="rpa_rules.json" --exclude="rpa/" ' '/opt/guardia/src/ /opt/guardia/app/ 2>&1 | tail -3') run('guardia 재기동', 'systemctl restart guardia && sleep 4 && systemctl is-active guardia') # 재검증 diff = run('app vs src 재검증', 'diff -rq /opt/guardia/src /opt/guardia/app ' '--exclude="*.pyc" --exclude="__pycache__" --exclude=".git" ' '--exclude="*.db" --exclude="uploads" --exclude=".env" ' '--exclude=".pytest_cache" --exclude="rpa_rules.json" --exclude="rpa" ' '2>/dev/null | head -5 || echo "동일"') results['guardia-itsm'] = '✅ 동기화 완료' if '동일' in diff else f'⚠️ {diff[:80]}' print(f' 결과: {results["guardia-itsm"]}') # ── 2. guardia-manager: STALE_WWW 수정 ───────────────────── print('\n━━ FIX 2: guardia-manager STALE_WWW 재빌드 ━━') # workspace/guardia-manager의 최신 frontend를 서버에 업로드 # repos/guardia-manager에서 bundle → server → git pull → npm build run('Manager src 확인', 'ls /opt/manager/backend/main.py 2>/dev/null && echo "backend OK" || echo "backend MISSING"') run('Manager frontend 확인', 'ls /opt/manager/frontend/package.json 2>/dev/null && echo "frontend OK" || echo "frontend MISSING"') # repos/guardia-manager에서 최신 코드 가져와 서버에 업로드 # 이미 Gitea에 올라가 있으므로 서버에서 직접 클론 GITEA_URL = 'http://zio:Zio%40Admin2026%21@127.0.0.1:9003/zio/guardia-manager.git' run('Manager 소스 clone/pull', f'''if [ -d /opt/manager/frontend/.git ]; then git -C /opt/manager/frontend fetch origin main && git -C /opt/manager/frontend reset --hard origin/main elif [ -d /tmp/mgr_src ]; then rm -rf /tmp/mgr_src fi [ -d /opt/manager/frontend/src ] && echo "frontend src OK" || echo "frontend src MISSING" ''') # workspace의 guardia-manager frontend를 bundle로 서버에 전송 print('\n workspace/guardia-manager bundle 서버 전송 중...') bundle_path = 'C:/GUARDiA/repos/guardia-manager.bundle' result = subprocess.run( ['git', '-C', 'C:/GUARDiA/repos/guardia-manager', 'bundle', 'create', bundle_path, '--all'], capture_output=True) if result.returncode == 0: size = os.path.getsize(bundle_path) // 1024 print(f' bundle {size}KB → 서버 전송...') sftp.put(bundle_path, '/tmp/mgr.bundle') os.remove(bundle_path) run('서버에서 Manager 클론', 'rm -rf /tmp/mgr_work && ' 'git clone /tmp/mgr.bundle /tmp/mgr_work 2>/dev/null && ' 'echo "clone OK" && ls /tmp/mgr_work/ | head -5') # frontend 빌드 run('Manager frontend 빌드', 'cd /tmp/mgr_work/frontend && ' 'npm ci --legacy-peer-deps 2>/dev/null || npm install --legacy-peer-deps && ' 'npm run build 2>&1 | tail -5', timeout=300) # /var/www/manager 복사 run('www/manager 복사', 'mkdir -p /var/www/manager && ' 'cp -r /tmp/mgr_work/frontend/dist/. /var/www/manager/ && ' f'stat /var/www/manager/index.html | grep Modify') # backend도 업데이트 run('Manager backend 업데이트', 'cp -r /tmp/mgr_work/backend/. /opt/manager/backend/ 2>/dev/null || true && ' 'rm -rf /tmp/mgr_work /tmp/mgr.bundle') run('Manager 서비스 재기동', 'systemctl restart guardia-manager && sleep 4 && systemctl is-active guardia-manager') www_date = run('www/manager 날짜 확인', 'stat /var/www/manager/index.html 2>/dev/null | grep Modify') results['guardia-manager'] = f'✅ www 갱신: {www_date[:30]}' if 'Jun 1' in www_date or 'Jun 1' in www_date else f'⚠️ {www_date}' else: results['guardia-manager'] = '❌ bundle 생성 실패' print(f' ❌ bundle 오류: {result.stderr.decode()[:100]}') print(f' 결과: {results["guardia-manager"]}') # ── 3. zioinfo-web: stash + uncommitted 정리 ─────────────── print('\n━━ FIX 3: zioinfo-web stash 정리 + 스크린샷 반영 ━━') # uncommitted 파일 목록 uncommit = run('uncommitted 파일 목록', "git -C /opt/zioinfo/src status --short 2>/dev/null | " "grep -v 'static/assets/' | grep -v '.pyc'") # logo_bottom.png, 스크린샷 → workspace로 다운로드 후 커밋 new_files = [l.strip().split()[-1] for l in uncommit.splitlines() if '??' in l or 'M ' in l] print(f' 새 파일: {new_files}') downloaded = [] for f in new_files: if not f: continue remote = f'/opt/zioinfo/src/{f}' local_ws = f'C:/GUARDiA/workspace/zioinfo-web/{f}' local_repo = f'C:/GUARDiA/repos/zioinfo-web/{f}' try: os.makedirs(os.path.dirname(local_ws), exist_ok=True) os.makedirs(os.path.dirname(local_repo), exist_ok=True) sftp.get(remote, local_ws) shutil.copy2(local_ws, local_repo) downloaded.append(f) print(f' 다운로드: {f}') except Exception as e: print(f' SKIP {f}: {e}') # stash 처리: 빌드 산출물만 있으면 drop stash_stat = run('stash 내용', 'git -C /opt/zioinfo/src stash show --stat 2>/dev/null | head -10') only_assets = all('static/assets' in l or 'Jenkinsfile' in l for l in stash_stat.splitlines() if '|' in l) if only_assets: run('stash drop (빌드 산출물만)', 'git -C /opt/zioinfo/src stash drop 2>/dev/null && echo "dropped"') results['zioinfo-web-stash'] = '✅ stash drop (빌드산출물)' else: # 중요 파일이 있으면 보존 results['zioinfo-web-stash'] = '⚠️ stash 보존 (중요 파일 포함) - 수동 확인 필요' print(f' stash 보존: {stash_stat[:200]}') # 서버 uncommitted 파일 add & commit if new_files: run('uncommitted 파일 commit', 'cd /opt/zioinfo/src && ' 'git add backend/src/main/resources/static/logo_bottom.png ' 'backend/src/main/resources/static/screenshots/ 2>/dev/null; ' 'git config user.email "ci@zioinfo.co.kr"; ' 'git config user.name "CI Bot"; ' 'git diff --cached --stat 2>/dev/null; ' 'git commit -m "chore: add screenshots and logo assets" 2>/dev/null || echo "nothing to commit"') results['zioinfo-web'] = f'✅ {len(downloaded)}개 파일 반영' print(f' 결과: {results["zioinfo-web"]}') # ── 최종 요약 ──────────────────────────────────────────────── print('\n' + '='*55) print('수정 결과 요약') print('='*55) for sys_name, result in results.items(): print(f' {result} [{sys_name}]') # fix_report 저장 os.makedirs('C:/GUARDiA/.claude/agents/_workspace', exist_ok=True) with open('C:/GUARDiA/.claude/agents/_workspace/fix_report.json', 'w', encoding='utf-8') as f: json.dump({"timestamp": __import__('datetime').datetime.now().isoformat(), "results": results}, f, ensure_ascii=False, indent=2) sftp.close() c.close() print('\n=== 수정 완료 ===')