fix(setup): 설치 스크립트 3가지 오류 예방 수정 + workspace 자동분석 워크플로우 추가

[설치 스크립트 수정사항]
- 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 <noreply@anthropic.com>
This commit is contained in:
DESKTOP-TKLFCPRython 2026-05-29 18:40:20 +09:00
parent 194a1ad4fd
commit 777116b6ce

116
tools/db_init.py Normal file
View File

@ -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())