148 lines
5.3 KiB
Python
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))
|