zioinfo-mail/.claude/skills/system-sync-orchestrator/references/verify-script.py
DESKTOP-TKLFCPR\ython 19dd2c0c09 feat(harness): system-sync-orchestrator + deploy agents + zioinfo assets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 21:14:34 +09:00

148 lines
5.3 KiB
Python

"""
deploy-verifier 에이전트가 사용하는 5개 시스템 검증 스크립트.
verify_all_systems.py 기반으로 JSON 보고서를 출력한다.
"""
import paramiko, sys, json, base64, os
from datetime import datetime
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)
G = base64.b64encode(b'zio:Zio@Admin2026!').decode()
def ssh(cmd, timeout=15):
_, o, _ = c.exec_command(cmd, timeout=timeout)
return o.read().decode('utf-8', 'replace').strip()
def gitea_commit(repo):
out = ssh(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; d=json.load(sys.stdin); "
"c=d[0]; print(c['sha'][:8]+'|'+c['commit']['message'][:50])\" 2>/dev/null")
return out.split('|') if '|' in out else ['unknown', 'unknown']
report = {
"timestamp": datetime.now().isoformat(),
"systems": {},
"action_required": [],
"critical": [],
"warnings": [],
}
SYSTEMS = {
"guardia-itsm": {
"service": "guardia",
"src_path": "/opt/guardia/src",
"app_path": "/opt/guardia/app",
"www_path": None,
"port": "9001",
},
"zioinfo-web": {
"service": "zioinfo",
"src_path": "/opt/zioinfo/src",
"app_path": None,
"www_path": "/var/www/zioinfo",
"port": "8082",
},
"guardia-manager": {
"service": "guardia-manager",
"src_path": "/opt/manager/backend",
"app_path": None,
"www_path": "/var/www/manager",
"port": "8090",
},
"guardia-messenger": {
"service": None,
"src_path": None,
"app_path": None,
"www_path": None,
"port": None,
},
"guardia-docs": {
"service": None,
"src_path": None,
"app_path": None,
"www_path": "/var/www/docs",
"port": None,
},
}
for name, cfg in SYSTEMS.items():
issues = []
info = {"issues": [], "status": {}}
# 서비스 상태
if cfg["service"]:
svc = ssh(f'systemctl is-active {cfg["service"]} 2>/dev/null')
info["status"]["service"] = svc
if svc != "active":
issues.append(f"SERVICE_DOWN:{cfg['service']}")
report["critical"].append(f"{name}: service not active")
# 서버 커밋
if cfg["src_path"]:
src_commit = ssh(f'git -C {cfg["src_path"]} log -1 --format="%H|%s" 2>/dev/null')
info["status"]["server_commit"] = src_commit[:8] if src_commit else "none"
# Gitea 커밋
g_sha, g_msg = gitea_commit(name)
info["status"]["gitea_commit"] = g_sha
if src_commit and g_sha != "unknown":
if not src_commit.startswith(g_sha):
issues.append(f"COMMIT_MISMATCH:server={src_commit[:8]} gitea={g_sha}")
report["critical"].append(f"{name}: commit mismatch")
# stash 확인
stash = ssh(f'git -C {cfg["src_path"]} stash list 2>/dev/null')
if stash:
issues.append(f"STASH_EXISTS:{stash[:80]}")
report["warnings"].append(f"{name}: stash exists")
# uncommitted 확인 (빌드 산출물 제외)
uncommit = ssh(f'git -C {cfg["src_path"]} status --short 2>/dev/null | '
"grep -v 'static/assets/' | grep -v '.pyc' | grep -v '__pycache__'")
if uncommit:
issues.append(f"UNCOMMITTED:{uncommit[:120]}")
report["warnings"].append(f"{name}: uncommitted changes")
# app vs src 비교 (ITSM)
if cfg["app_path"] and cfg["src_path"]:
diff = ssh(f'diff -rq {cfg["src_path"]} {cfg["app_path"]} '
'--exclude="*.pyc" --exclude="__pycache__" --exclude=".git" '
'--exclude="*.db" --exclude="uploads" --exclude=".env" '
'--exclude=".pytest_cache" 2>/dev/null | head -5')
if diff:
issues.append(f"APP_SRC_DRIFT:{diff[:150]}")
report["critical"].append(f"{name}: app/src drift")
# /var/www 날짜 확인
if cfg["www_path"]:
www_date = ssh(f'stat {cfg["www_path"]}/index.html 2>/dev/null | grep Modify | cut -d" " -f2,3 || echo "missing"')
info["status"]["www_date"] = www_date
# 오늘 날짜(Jun 1 = 2026-06-01) 또는 최근 3일 이내면 최신으로 간주
import re as _re
today_str = __import__('datetime').datetime.now().strftime('%Y-%m-%d')
is_recent = (today_str in www_date or
"Jun 1" in www_date or "Jun 1" in www_date or
"missing" in www_date or not www_date.strip())
if not is_recent and www_date.strip():
issues.append(f"STALE_WWW:{www_date}")
report["critical"].append(f"{name}: stale www ({www_date})")
info["issues"] = issues
report["systems"][name] = info
if issues:
report["action_required"].append(name)
c.close()
# 저장
os.makedirs("C:/GUARDiA/.claude/agents/_workspace", exist_ok=True)
with open("C:/GUARDiA/.claude/agents/_workspace/verify_report.json", "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
print(json.dumps(report, ensure_ascii=False, indent=2))