문제: - cleanup 스크립트가 P0003 이후 삭제 시 P001, P002도 함께 삭제됨 - 문자열 비교 'P001' >= 'P0003'이 true로 평가됨 원인: - SQLite 문자열 비교에서 'P001' < 'P0003'이지만 - 'P002' >= 'P0003'은 false인데, 기존 조건이 잘못됨 해결: - LENGTH(pharmacy_code) = 5 조건 추가 - P0003 <= pharmacy_code <= P9999 범위 명시 - P001, P002 (4자), P0001, P0002 (5자) 모두 보호 변경 파일: - cleanup-test-data.sh: 삭제 쿼리 수정 - CLEANUP_TEST_DATA.md: 문서 업데이트 보호되는 약국: - P001: default 약국 (4자) - P002: 새서울약국 (4자) - P0002: 청춘약국 (5자, 범위 밖) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
286 lines
7.3 KiB
Markdown
286 lines
7.3 KiB
Markdown
# 테스트 데이터 정리 가이드
|
|
|
|
## 개요
|
|
|
|
headscale 자동 등록 스크립트 테스트 시 생성되는 데이터를 정리하는 방법
|
|
|
|
## 정리해야 할 데이터
|
|
|
|
1. **farmq.db**: 테스트 약국 데이터 (P0003 이후)
|
|
2. **gateway.db**: 테스트 사용자 계정 (ID 5 이후)
|
|
3. **Headscale**: 테스트 VPN 노드
|
|
|
|
---
|
|
|
|
## 1. farmq.db 테스트 약국 삭제
|
|
|
|
### 수동 삭제 (Python)
|
|
|
|
```bash
|
|
cd /srv/headscale-tailscale-replacement/farmq-admin
|
|
|
|
python3 << 'EOF'
|
|
import sqlite3
|
|
|
|
conn = sqlite3.connect('farmq.db')
|
|
cursor = conn.cursor()
|
|
|
|
print('현재 약국 목록:')
|
|
cursor.execute('SELECT pharmacy_code, pharmacy_name, tailscale_ip FROM pharmacies')
|
|
for row in cursor.fetchall():
|
|
print(f' {row[0]}: {row[1]} - {row[2]}')
|
|
|
|
# P0003~P9999 약국 삭제 (P001, P002, P0001, P0002는 보호)
|
|
print('\nP0003 이후 약국 삭제 중...')
|
|
cursor.execute("DELETE FROM pharmacies WHERE pharmacy_code >= 'P0003' AND pharmacy_code <= 'P9999' AND LENGTH(pharmacy_code) = 5")
|
|
deleted_count = cursor.rowcount
|
|
|
|
conn.commit()
|
|
|
|
print(f'✓ {deleted_count}개 약국 삭제 완료')
|
|
|
|
print('\n남은 약국 목록:')
|
|
cursor.execute('SELECT pharmacy_code, pharmacy_name, tailscale_ip FROM pharmacies')
|
|
for row in cursor.fetchall():
|
|
print(f' {row[0]}: {row[1]} - {row[2]}')
|
|
|
|
conn.close()
|
|
EOF
|
|
```
|
|
|
|
### 특정 약국만 삭제
|
|
|
|
```bash
|
|
cd /srv/headscale-tailscale-replacement/farmq-admin
|
|
|
|
python3 << 'EOF'
|
|
import sqlite3
|
|
|
|
conn = sqlite3.connect('farmq.db')
|
|
cursor = conn.cursor()
|
|
|
|
# 삭제할 약국 코드 지정
|
|
pharmacy_codes = ['P0003', 'P0004', 'P0005', 'P0006']
|
|
|
|
for code in pharmacy_codes:
|
|
cursor.execute('DELETE FROM pharmacies WHERE pharmacy_code = ?', (code,))
|
|
print(f'✓ {code} 삭제 완료')
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
EOF
|
|
```
|
|
|
|
---
|
|
|
|
## 2. gateway.db 테스트 사용자 삭제
|
|
|
|
### 수동 삭제 (Python)
|
|
|
|
```bash
|
|
cd /srv/pharmq-gateway
|
|
|
|
python3 << 'EOF'
|
|
import sqlite3
|
|
|
|
conn = sqlite3.connect('gateway.db')
|
|
cursor = conn.cursor()
|
|
|
|
print('현재 사용자 목록:')
|
|
cursor.execute('SELECT id, username, name, primary_pharmacy_code FROM users')
|
|
for row in cursor.fetchall():
|
|
print(f' ID {row[0]}: {row[1]} ({row[2]}) - {row[3]}')
|
|
|
|
# ID 5 이후 사용자 삭제 (테스트 데이터)
|
|
print('\nID 5 이후 사용자 삭제 중...')
|
|
cursor.execute('DELETE FROM pharmacy_members WHERE user_id >= 5')
|
|
cursor.execute('DELETE FROM users WHERE id >= 5')
|
|
deleted_count = cursor.rowcount
|
|
|
|
conn.commit()
|
|
|
|
print(f'✓ {deleted_count}명 사용자 삭제 완료')
|
|
|
|
print('\n남은 사용자 목록:')
|
|
cursor.execute('SELECT id, username, name, primary_pharmacy_code FROM users')
|
|
for row in cursor.fetchall():
|
|
print(f' ID {row[0]}: {row[1]} ({row[2]}) - {row[3]}')
|
|
|
|
conn.close()
|
|
EOF
|
|
```
|
|
|
|
### 특정 사용자만 삭제
|
|
|
|
```bash
|
|
cd /srv/pharmq-gateway
|
|
|
|
python3 << 'EOF'
|
|
import sqlite3
|
|
|
|
conn = sqlite3.connect('gateway.db')
|
|
cursor = conn.cursor()
|
|
|
|
# 삭제할 사용자 ID 지정
|
|
user_ids = [5, 6, 7, 8]
|
|
|
|
for user_id in user_ids:
|
|
cursor.execute('DELETE FROM pharmacy_members WHERE user_id = ?', (user_id,))
|
|
cursor.execute('DELETE FROM users WHERE id = ?', (user_id,))
|
|
print(f'✓ ID {user_id} 삭제 완료')
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
EOF
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Headscale 테스트 노드 삭제
|
|
|
|
### 노드 목록 확인
|
|
|
|
```bash
|
|
docker exec headscale headscale nodes list
|
|
```
|
|
|
|
### 특정 노드 삭제 (ID 기준)
|
|
|
|
```bash
|
|
# 단일 노드 삭제
|
|
docker exec headscale headscale nodes delete --identifier <NODE_ID> --force
|
|
|
|
# 예시: ID 13 삭제
|
|
docker exec headscale headscale nodes delete --identifier 13 --force
|
|
```
|
|
|
|
### 여러 노드 한번에 삭제
|
|
|
|
```bash
|
|
# 삭제할 노드 ID들
|
|
NODE_IDS=(13 14 15 16 17 18)
|
|
|
|
for id in "${NODE_IDS[@]}"; do
|
|
docker exec headscale headscale nodes delete --identifier $id --force
|
|
echo "✓ Node ID $id 삭제 완료"
|
|
done
|
|
```
|
|
|
|
### 특정 이름 패턴 노드 삭제
|
|
|
|
```bash
|
|
# pve-로 시작하는 테스트 노드 찾기
|
|
docker exec headscale headscale nodes list | grep "pve-"
|
|
|
|
# 해당 노드들의 ID를 확인한 후 수동으로 삭제
|
|
docker exec headscale headscale nodes delete --identifier <ID> --force
|
|
```
|
|
|
|
---
|
|
|
|
## 자동화 스크립트
|
|
|
|
### 통합 정리 스크립트 실행
|
|
|
|
```bash
|
|
bash /srv/install_scripts/pve9-repo-fix/cleanup-test-data.sh
|
|
```
|
|
|
|
**또는 직접 다운로드하여 실행:**
|
|
|
|
```bash
|
|
curl -fsSL https://raw.githubusercontent.com/thug0bin/pve9-repo-fix/main/cleanup-test-data.sh | bash
|
|
```
|
|
|
|
---
|
|
|
|
## 사용 시나리오
|
|
|
|
### 시나리오 1: 전체 테스트 데이터 정리
|
|
|
|
```bash
|
|
# 1. farmq.db 정리 (P0003~P9999, P001/P002 보호)
|
|
cd /srv/headscale-tailscale-replacement/farmq-admin
|
|
python3 -c "import sqlite3; conn = sqlite3.connect('farmq.db'); cursor = conn.cursor(); cursor.execute(\"DELETE FROM pharmacies WHERE pharmacy_code >= 'P0003' AND pharmacy_code <= 'P9999' AND LENGTH(pharmacy_code) = 5\"); conn.commit(); print(f'✓ {cursor.rowcount}개 약국 삭제'); conn.close()"
|
|
|
|
# 2. gateway.db 정리 (ID 5 이후)
|
|
cd /srv/pharmq-gateway
|
|
python3 -c "import sqlite3; conn = sqlite3.connect('gateway.db'); cursor = conn.cursor(); cursor.execute('DELETE FROM pharmacy_members WHERE user_id >= 5'); cursor.execute('DELETE FROM users WHERE id >= 5'); conn.commit(); print(f'✓ {cursor.rowcount}명 사용자 삭제'); conn.close()"
|
|
|
|
# 3. Headscale 노드 정리 (수동 확인 후)
|
|
docker exec headscale headscale nodes list
|
|
# 테스트 노드 ID 확인 후 삭제
|
|
```
|
|
|
|
### 시나리오 2: 특정 약국 코드 범위만 정리
|
|
|
|
```bash
|
|
# P0005~P0010 범위만 삭제
|
|
cd /srv/headscale-tailscale-replacement/farmq-admin
|
|
python3 -c "import sqlite3; conn = sqlite3.connect('farmq.db'); cursor = conn.cursor(); cursor.execute(\"DELETE FROM pharmacies WHERE pharmacy_code >= 'P0005' AND pharmacy_code <= 'P0010'\"); conn.commit(); print(f'✓ {cursor.rowcount}개 약국 삭제'); conn.close()"
|
|
```
|
|
|
|
---
|
|
|
|
## 주의사항
|
|
|
|
⚠️ **운영 데이터 보호**
|
|
- P001, P002, P0002는 운영 약국이므로 삭제하지 마세요
|
|
- ID 1~4 사용자는 운영 계정이므로 삭제하지 마세요
|
|
- 삭제 전 반드시 데이터를 확인하세요
|
|
|
|
⚠️ **백업 권장**
|
|
```bash
|
|
# farmq.db 백업
|
|
cp /srv/headscale-tailscale-replacement/farmq-admin/farmq.db /srv/headscale-tailscale-replacement/farmq-admin/farmq.db.backup
|
|
|
|
# gateway.db 백업
|
|
cp /srv/pharmq-gateway/gateway.db /srv/pharmq-gateway/gateway.db.backup
|
|
```
|
|
|
|
⚠️ **Headscale 노드 삭제**
|
|
- offline 상태의 노드만 삭제 권장
|
|
- online 노드 삭제 시 연결이 끊어집니다
|
|
|
|
---
|
|
|
|
## 트러블슈팅
|
|
|
|
### 문제: "database is locked" 에러
|
|
|
|
**원인**: Flask 서버가 DB를 사용 중
|
|
|
|
**해결**:
|
|
```bash
|
|
# farmq-admin 재시작
|
|
cd /srv/headscale-tailscale-replacement/farmq-admin
|
|
pkill -f "flask run"
|
|
source venv/bin/activate
|
|
flask run --host=0.0.0.0 --port=5001 &
|
|
|
|
# gateway 재시작
|
|
cd /srv/pharmq-gateway
|
|
pkill -f "uvicorn"
|
|
source venv/bin/activate
|
|
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload &
|
|
```
|
|
|
|
### 문제: Python 스크립트 실행 안 됨
|
|
|
|
**해결**: 경로 확인
|
|
```bash
|
|
# 올바른 경로로 이동
|
|
cd /srv/headscale-tailscale-replacement/farmq-admin # farmq.db
|
|
cd /srv/pharmq-gateway # gateway.db
|
|
```
|
|
|
|
---
|
|
|
|
## 참고
|
|
|
|
- farmq.db 위치: `/srv/headscale-tailscale-replacement/farmq-admin/farmq.db`
|
|
- gateway.db 위치: `/srv/pharmq-gateway/gateway.db`
|
|
- Headscale: Docker 컨테이너 `headscale`
|
|
- 약국 코드 형식: P0001~P9999 (P + 4자리 숫자)
|
|
- 사용자 계정: pharmacy_code 소문자 (예: P0003 → p0003)
|