zioinfo-mail/workspace/guardia-itsm/tools/db_init.py
DESKTOP-TKLFCPR\ython cfe2901a55 refactor(structure): consolidate all projects under workspace/
- itsm/    -> workspace/guardia-itsm/
- manager/ -> workspace/guardia-manager/
- app/     -> workspace/guardia-messenger/
- manual/  -> workspace/guardia-docs/

workspace/zioinfo-web/ unchanged.
git mv preserves full commit history.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:50:56 +09:00

128 lines
4.4 KiB
Python

#!/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 초기화 완료")
def _copy_offline_assets():
"""폐쇄망 정적 파일 복사 (있는 경우)."""
import shutil
offline_chart = ITSM_DIR.parent / "setup" / "offline" / "common" / "chart.umd.min.js"
target_chart = ITSM_DIR / "static" / "chart.umd.min.js"
if offline_chart.exists() and not target_chart.exists():
shutil.copy2(offline_chart, target_chart)
print(f"[OK] Chart.js 오프라인 파일 복사: {target_chart}")
if __name__ == "__main__":
_copy_offline_assets()
asyncio.run(main())