#!/usr/bin/env python3 """ Headscale 데이터베이스 외래키 제약조건 수정 스크립트 """ import sqlite3 import os from datetime import datetime def fix_database_constraints(): """외래키 제약조건 문제 해결""" db_path = '/srv/headscale-setup/data/db.sqlite' backup_path = f'/srv/headscale-setup/data/db.sqlite.backup.{datetime.now().strftime("%Y%m%d_%H%M%S")}' print("🔧 Headscale 데이터베이스 외래키 제약조건 수정") print("=" * 60) # 1. 백업 생성 print(f"📦 데이터베이스 백업 중: {backup_path}") import shutil shutil.copy2(db_path, backup_path) print("✅ 백업 완료") # 2. 데이터베이스 연결 conn = sqlite3.connect(db_path) cursor = conn.cursor() try: # 3. 외래키 제약조건 비활성화 print("🔓 외래키 제약조건 비활성화 중...") cursor.execute("PRAGMA foreign_keys = OFF") # 4. 기존 테이블들 확인 cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%pharmacy%'") pharmacy_tables = cursor.fetchall() print(f"📋 발견된 약국 관련 테이블: {[table[0] for table in pharmacy_tables]}") # 5. pharmacy_info 테이블 재생성 (외래키 없이) if any('pharmacy_info' in table[0] for table in pharmacy_tables): print("🔄 pharmacy_info 테이블 재생성 중...") # 기존 데이터 백업 cursor.execute("SELECT * FROM pharmacy_info") existing_data = cursor.fetchall() # 기존 테이블 구조 확인 cursor.execute("PRAGMA table_info(pharmacy_info)") columns = cursor.fetchall() print(f"📊 기존 컬럼: {[col[1] for col in columns]}") # 새 테이블 생성 (외래키 제약조건 없음) cursor.execute("DROP TABLE IF EXISTS pharmacy_info_new") cursor.execute(""" CREATE TABLE pharmacy_info_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, pharmacy_name VARCHAR(255) NOT NULL, business_number VARCHAR(20), manager_name VARCHAR(100), phone VARCHAR(20), address TEXT, proxmox_host VARCHAR(255), user_id VARCHAR(255), -- 외래키 제약조건 제거 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) # 기존 데이터 복사 if existing_data: print(f"📁 기존 데이터 복사 중... ({len(existing_data)}개 레코드)") cursor.executemany(""" INSERT INTO pharmacy_info_new (id, pharmacy_name, business_number, manager_name, phone, address, proxmox_host, user_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, existing_data) # 기존 테이블 삭제 및 새 테이블로 교체 cursor.execute("DROP TABLE pharmacy_info") cursor.execute("ALTER TABLE pharmacy_info_new RENAME TO pharmacy_info") print("✅ pharmacy_info 테이블 재생성 완료") # 6. machine_specs 테이블도 수정 (필요시) if any('machine_specs' in table[0] for table in pharmacy_tables): print("🔄 machine_specs 테이블 확인 중...") cursor.execute("PRAGMA table_info(machine_specs)") columns = cursor.fetchall() # 외래키 제약조건이 있는지 확인 cursor.execute("SELECT sql FROM sqlite_master WHERE name='machine_specs'") table_sql = cursor.fetchone() if table_sql and 'REFERENCES' in table_sql[0]: print("🔄 machine_specs 테이블 재생성 중...") # 기존 데이터 백업 cursor.execute("SELECT * FROM machine_specs") existing_specs = cursor.fetchall() # 새 테이블 생성 (외래키 제약조건 없음) cursor.execute("DROP TABLE IF EXISTS machine_specs_new") cursor.execute(""" CREATE TABLE machine_specs_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, machine_id INTEGER, -- 외래키 제약조건 제거 pharmacy_id INTEGER, -- 외래키 제약조건 제거 cpu_model VARCHAR(255), cpu_cores INTEGER, ram_gb INTEGER, storage_gb INTEGER, network_speed INTEGER, os_info VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) # 기존 데이터 복사 if existing_specs: print(f"📁 기존 스펙 데이터 복사 중... ({len(existing_specs)}개 레코드)") cursor.executemany(""" INSERT INTO machine_specs_new VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, existing_specs) # 기존 테이블 삭제 및 새 테이블로 교체 cursor.execute("DROP TABLE machine_specs") cursor.execute("ALTER TABLE machine_specs_new RENAME TO machine_specs") print("✅ machine_specs 테이블 재생성 완료") # 7. 변경사항 커밋 conn.commit() print("💾 변경사항 저장 완료") # 8. 외래키 제약조건 재활성화 (필요시) cursor.execute("PRAGMA foreign_keys = ON") # 9. 무결성 검사 print("🔍 데이터베이스 무결성 검사 중...") cursor.execute("PRAGMA integrity_check") integrity_result = cursor.fetchone() print(f"✅ 무결성 검사 결과: {integrity_result[0]}") print("\n🎉 데이터베이스 수정 완료!") print(f"📦 백업 위치: {backup_path}") print("🚀 이제 Tailscale 클라이언트 등록을 다시 시도해보세요!") except Exception as e: print(f"❌ 오류 발생: {e}") conn.rollback() print(f"🔙 백업에서 복원하려면: cp {backup_path} {db_path}") raise finally: conn.close() if __name__ == '__main__': fix_database_constraints()