## Features - 한국어 Flask 관리 인터페이스 with Bootstrap 5 - Headscale과 분리된 독립 데이터베이스 구조 - 약국 관리 시스템 (pharmacy management) - 머신 모니터링 및 상태 관리 - 실시간 대시보드 with 통계 및 알림 - Headscale 사용자명과 약국명 분리 관리 ## Database Architecture - 별도 FARMQ SQLite DB (farmq.sqlite) - Headscale DB와 외래키 충돌 방지 - 느슨한 결합 설계 (ID 참조만 사용) ## UI Components - 반응형 대시보드 with 실시간 통계 - 약국별 머신 상태 모니터링 - 한국어 지역화 및 사용자 친화적 인터페이스 - 머신 온라인/오프라인 상태 표시 (24시간 타임아웃) ## API Endpoints - `/api/sync/machines` - Headscale 머신 동기화 - `/api/sync/users` - Headscale 사용자 동기화 - `/api/pharmacy/<id>/update` - 약국 정보 업데이트 - 대시보드 통계 및 알림 API ## Problem Resolution - Fixed foreign key conflicts preventing Windows client connections - Resolved machine online status detection with proper timeout handling - Separated technical Headscale usernames from business pharmacy names 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
4.6 KiB
4.6 KiB
Headscale 데이터베이스 외래키 제약조건 문제 해결 기록
🔍 문제 상황
발생한 오류
backend error: handling register with auth key: registering node: failed register(save) node in the database: SQL logic error: foreign key mismatch - "pharmacy_info" referencing "users" (1)
증상
- Windows Tailscale 클라이언트 연결 시 Google SSO 로그인만 표시됨
tailscale up --login-server명령어 실행 시 위 오류 발생- Headscale 컨테이너 로그에서 foreign key mismatch 오류 지속 발생
원인 분석
-
Flask Admin 앱 개발 과정에서 추가된 커스텀 테이블들이 Headscale 스키마와 충돌
pharmacy_info테이블이users테이블을 참조하는 외래키 제약조건 설정machine_specs,monitoring_data테이블도 유사한 외래키 제약조건 존재
-
Headscale이 자체 사용자 관리 스키마를 가지고 있어 외부 테이블의 외래키 참조 거부
- Headscale은 내부적으로
users,machines,nodes등의 테이블을 관리 - 외부에서 추가된 테이블이 이들을 참조할 때 스키마 불일치 발생
- Headscale은 내부적으로
🛠️ 해결 과정
1단계: 문제 테이블 식별
# 문제가 되는 테이블들 확인
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%pharmacy%'")
problem_tables = cursor.fetchall()
# 결과: ['pharmacy_info', 'machine_specs', 'monitoring_data']
2단계: 데이터베이스 정리 스크립트 실행
python3 clean-database.py
스크립트 수행 작업:
- 백업 생성:
db.sqlite.clean_backup.20250909_170759 - 외래키 제약조건 비활성화:
PRAGMA foreign_keys = OFF - 문제 테이블 제거:
DROP TABLE IF EXISTS pharmacy_infoDROP TABLE IF EXISTS machine_specsDROP TABLE IF EXISTS monitoring_data
- 변경사항 커밋 및 무결성 검사
3단계: Headscale 서비스 재시작
cd /srv/headscale-setup && docker-compose restart headscale
✅ 해결 결과
Before (문제 상황):
2025-09-09T17:07:02+09:00 FTL Migration failed: SQL logic error: foreign key mismatch - "pharmacy_info" referencing "users" (1)
After (해결 후):
2025-09-09T17:08:42+09:00 INF Using policy manager version: 2
2025-09-09T17:08:42+09:00 INF Starting Headscale commit=474ea236d0c6d393dbcf7baa98da240ad20c1b66 version=0.26.1
2025-09-09T17:08:46+09:00 INF node has connected, mapSession: 0xc000172600, chan: 0xc000286d90 node=DESKTOP-EMJD1DC node.id=2
2025-09-09T17:08:48+09:00 INF node has connected, mapSession: 0xc00021b680, chan: 0xc000251180 node=0bin-Ubuntu-VM node.id=1
성공 지표:
- ✅ Headscale 정상 시작
- ✅ Windows 클라이언트 (DESKTOP-EMJD1DC) 연결 성공
- ✅ Ubuntu VM 클라이언트 (0bin-Ubuntu-VM) 연결 유지
- ✅ Health check 통과:
{"status":"pass"}
📚 교훈 및 베스트 프랙티스
1. Headscale과 커스텀 애플리케이션 분리 원칙
❌ 잘못된 접근: Headscale DB에 직접 외래키 제약조건으로 연결
✅ 올바른 접근: 별도 데이터베이스 또는 느슨한 결합 방식 사용
2. 외래키 제약조건 설계 시 고려사항
- Headscale 스키마 독립성 유지
- 별도 데이터베이스 사용 또는 ID 참조만 사용 (외래키 제약조건 없이)
- 마이그레이션 전 스키마 호환성 검증
3. 향후 개발 가이드라인
# 권장하지 않음
pharmacy_info = Table('pharmacy_info',
Column('user_id', Integer, ForeignKey('users.id')) # ❌
)
# 권장 방법
pharmacy_info = Table('pharmacy_info',
Column('headscale_user_id', Integer) # ✅ 단순 참조, 제약조건 없음
)
4. 데이터베이스 백업 중요성
- 모든 스키마 변경 전 백업 생성 필수
- 백업 파일명에 타임스탬프 포함으로 버전 관리
- 롤백 절차 사전 준비
🔧 복구 방법 (필요시)
만약 문제가 재발하거나 백업에서 복원해야 할 경우:
# 백업에서 복원
cp /srv/headscale-setup/data/db.sqlite.clean_backup.20250909_170759 /srv/headscale-setup/data/db.sqlite
# 컨테이너 재시작
cd /srv/headscale-setup && docker-compose restart headscale
📝 관련 파일들
- 해결 스크립트:
/srv/headscale-setup/clean-database.py - 백업 파일:
/srv/headscale-setup/data/db.sqlite.clean_backup.20250909_170759 - Docker Compose:
/srv/headscale-setup/docker-compose.yml - 로그 위치:
docker logs headscale
문제 해결일: 2025-09-09
해결 소요시간: ~30분
영향 범위: Windows/Ubuntu 클라이언트 연결 복구