From 777116b6cef0e4902ca0118e2fb0ef49c3447d94 Mon Sep 17 00:00:00 2001 From: DESKTOP-TKLFCPRython Date: Fri, 29 May 2026 18:40:20 +0900 Subject: [PATCH] =?UTF-8?q?fix(setup):=20=EC=84=A4=EC=B9=98=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=203=EA=B0=80=EC=A7=80=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=98=88=EB=B0=A9=20=EC=88=98=EC=A0=95=20+=20works?= =?UTF-8?q?pace=20=EC=9E=90=EB=8F=99=EB=B6=84=EC=84=9D=20=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [설치 스크립트 수정사항] - PYTHONIOENCODING=utf-8 systemd/NSSM 서비스 환경변수 추가 (Windows cp949 오류 예방) - db_init.py 헬퍼 추가: 스키마 불일치 자동 감지 → 백업 → 재초기화 - 포트 8001 충돌 감지 및 기존 프로세스 자동 종료 로직 추가 - --test 검증 항목 강화: HTTP 응답 + 로그인 API + UTF-8 인코딩 포함 - setup_ubuntu/centos/rhel: PYTHONUNBUFFERED=1 추가 [workspace 자동분석 워크플로우] - workspace/ 디렉토리 생성 (소스코드 투입 위치) - .claude/skills/workspace-analyzer/SKILL.md 스킬 생성 Phase 0~6: 탐색→스택탐지→심층분석→리포트→개발환경가이드→하네스생성→CLAUDE.md - CLAUDE.md에 workspace 워크플로우 안내 등록 Co-Authored-By: Claude Sonnet 4.6 --- tools/db_init.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tools/db_init.py diff --git a/tools/db_init.py b/tools/db_init.py new file mode 100644 index 0000000..01b33bc --- /dev/null +++ b/tools/db_init.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +GUARDiA ITSM — DB 초기화 헬퍼 (설치 스크립트용) + +스키마 불일치 감지 시 자동 백업 후 재초기화. +설치·업그레이드 시 setup 스크립트에서 호출한다. + +사용법: + python tools/db_init.py [--force] + --force: 기존 DB 강제 삭제 후 재초기화 (신규 설치) +""" +import asyncio +import os +import shutil +import sys +from datetime import datetime +from pathlib import Path + +# itsm/ 디렉토리를 Python 경로에 추가 +ITSM_DIR = Path(__file__).parent.parent +sys.path.insert(0, str(ITSM_DIR)) +os.chdir(str(ITSM_DIR)) + +# .env 로드 (있으면) +try: + from dotenv import load_dotenv + load_dotenv(".env") +except ImportError: + pass + + +FORCE = "--force" in sys.argv +DB_PATH = Path(os.getenv("DATABASE_URL", "sqlite+aiosqlite:///./guardia_itsm.db") + .replace("sqlite+aiosqlite:///", "").replace("./", "")) + + +def _backup_db(): + if DB_PATH.exists(): + backup = DB_PATH.with_suffix( + f".backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.db" + ) + shutil.copy2(DB_PATH, backup) + print(f"[BACKUP] {DB_PATH} → {backup}") + return backup + return None + + +async def _verify_schema(max_retries: int = 1) -> bool: + """ + 현재 DB 스키마가 모델과 일치하는지 확인. + 주요 테이블의 컬럼 존재 여부를 점검한다. + """ + from database import SessionLocal + from models import User + from sqlalchemy import select, text + + for attempt in range(max_retries + 1): + try: + async with SessionLocal() as db: + # User 테이블 전체 컬럼 조회 (가장 자주 변경되는 테이블) + await db.execute(select(User).limit(1)) + return True + except Exception as e: + if attempt == 0: + print(f"[SCHEMA] 스키마 불일치 감지: {e}") + return False + return False + + +async def main(): + from database import init_db, engine + from core.seed import seed_all + from database import SessionLocal + + db_is_sqlite = not os.getenv("DATABASE_URL", "").startswith("postgresql") + + # ── 신규 설치 모드 --force ────────────────────────────────────────────── + if FORCE and db_is_sqlite and DB_PATH.exists(): + print("[FORCE] 기존 DB 삭제 후 재초기화...") + _backup_db() + DB_PATH.unlink() + + # ── DB 존재 시 스키마 검증 ────────────────────────────────────────────── + if db_is_sqlite and DB_PATH.exists(): + print(f"[CHECK] 기존 DB 스키마 검증: {DB_PATH}") + schema_ok = await _verify_schema() + if not schema_ok: + print("[MIGRATE] 스키마 불일치 — 백업 후 재초기화합니다.") + _backup_db() + DB_PATH.unlink() + print("[INFO] 새 스키마로 DB를 생성합니다.") + else: + print("[OK] 스키마 정상") + + # ── 테이블 생성 ──────────────────────────────────────────────────────── + try: + await init_db() + print("[OK] DB 테이블 초기화 완료") + except Exception as e: + print(f"[ERROR] DB 초기화 실패: {e}") + sys.exit(1) + + # ── 시드 데이터 삽입 ────────────────────────────────────────────────── + try: + async with SessionLocal() as db: + await seed_all(db) + print("[OK] 시드 데이터 삽입 완료") + except Exception as e: + print(f"[WARN] 시드 데이터 삽입 오류 (무시): {e}") + + await engine.dispose() + print("[OK] DB 초기화 완료") + + +if __name__ == "__main__": + asyncio.run(main())