- Headscale CLI 기반 제어 방식의 핵심 아키텍처 설명 - 이중 데이터베이스 전략 및 실시간 동기화 방법론 정리 - 실제 구현 코드 예시와 표준 패턴 제시 - Phase별 기능 확장 로드맵 및 개발 가이드라인 - 성능 최적화, 보안, 디버깅 방안 포함 - 향후 모든 기능 구현의 기준 문서로 활용 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
12 KiB
12 KiB
FARMQ Admin 구현 가이드 및 설계 원칙
📋 개요
FARMQ Admin은 Headscale을 기반으로 한 100개 약국 네트워크 관리 시스템의 웹 인터페이스입니다. Headplane의 기능을 대체하면서 추가적인 약국 관리 기능을 제공하는 통합 관리 플랫폼입니다.
🏗️ 아키텍처 설계 원칙
핵심 설계 철학
FARMQ Admin (Frontend/API) → Headscale CLI (Backend Engine) → Network Management
FARMQ Admin은 프론트엔드 인터페이스이고, Headscale은 백엔드 엔진으로 작동합니다. 모든 네트워크 관리 기능은 Headscale CLI를 통해 제어되며, FARMQ Admin은 이를 웹 인터페이스로 래핑합니다.
계층 구조
┌─────────────────────────────────────┐
│ FARMQ Admin │ ← 웹 UI, 약국 관리, 대시보드
│ (Flask + Bootstrap + JS) │
├─────────────────────────────────────┤
│ API Layer │ ← REST API, CLI 인터페이스
│ (Python subprocess calls) │
├─────────────────────────────────────┤
│ Headscale CLI │ ← 네트워크 관리 엔진
│ (Docker containerized) │
├─────────────────────────────────────┤
│ Database Layer │ ← 이중 데이터베이스
│ ┌─────────────┬─────────────────┐ │
│ │ FARMQ DB │ Headscale DB │ │
│ │ (약국정보) │ (노드정보) │ │
│ └─────────────┴─────────────────┘ │
└─────────────────────────────────────┘
🔧 구현 방법론
1. CLI 기반 기능 구현 패턴
모든 Headscale 관련 기능은 다음 패턴을 따라 구현합니다:
# 표준 구현 패턴
def headscale_function():
try:
# Docker를 통해 Headscale CLI 실행
result = subprocess.run(
['docker', 'exec', 'headscale', 'headscale', 'command', 'args'],
capture_output=True,
text=True,
check=True
)
# JSON 출력 파싱 (가능한 경우)
if '-o json' in args:
data = json.loads(result.stdout)
return data
return {'success': True, 'output': result.stdout}
except subprocess.CalledProcessError as e:
return {'success': False, 'error': e.stderr}
구현 예시들
1. 실시간 온라인 상태 조회
def get_headscale_online_status() -> Dict[str, bool]:
"""Headscale CLI를 통해 실시간 온라인 상태 조회"""
result = subprocess.run([
'docker', 'exec', 'headscale',
'headscale', 'nodes', 'list', '-o', 'json'
], capture_output=True, text=True, check=True)
nodes_data = json.loads(result.stdout)
online_status = {}
for node in nodes_data:
node_name = node.get('given_name') or node.get('name', '')
is_online = node.get('online', False) == True
online_status[node_name.lower()] = is_online
return online_status
2. 노드 삭제 기능
@app.route('/api/nodes/<int:node_id>/delete', methods=['DELETE'])
def api_delete_node(node_id):
"""노드 삭제 API"""
result = subprocess.run([
'docker', 'exec', 'headscale',
'headscale', 'nodes', 'delete',
'-i', str(node_id), '--force'
], capture_output=True, text=True, check=True)
return jsonify({
'success': True,
'message': f'노드 {node_id}가 성공적으로 삭제되었습니다.'
})
2. 이중 데이터베이스 전략
FARMQ Database (자체 관리)
- 목적: 약국 정보, 관리자 데이터, 커스텀 설정
- 특징: 완전한 제어권, 외래키 제약 없음, 능동적 관리
- 테이블:
pharmacy_info,machine_profiles,monitoring_metrics,system_alerts
# FARMQ DB - 약국 정보 관리
class PharmacyInfo(FarmqBase):
__tablename__ = 'pharmacy_info'
id = Column(Integer, primary_key=True)
pharmacy_name = Column(String(100), nullable=False)
business_number = Column(String(20), unique=True)
manager_name = Column(String(50))
headscale_user_name = Column(String(50)) # Headscale과 연결점
Headscale Database (읽기 전용)
- 목적: 네트워크 노드 정보, 실시간 상태
- 특징: 읽기 전용 접근, Headscale이 관리
- 활용: 실시간 쿼리로 최신 상태 반영
# Headscale DB - 읽기 전용 조회
def get_dashboard_stats():
headscale_session = get_headscale_session()
# 실시간 노드 상태
active_nodes = headscale_session.query(Node).filter(
Node.deleted_at.is_(None)
).all()
# CLI로 온라인 상태 확인
online_status = get_headscale_online_status()
# 두 데이터 소스 결합
for node in active_nodes:
node_name = (node.given_name or '').lower()
is_online = online_status.get(node_name, False)
3. 실시간 동기화 전략
기존 문제점
- 타임아웃 기반 온라인 판단 (부정확)
- 캐시된 데이터 사용 (지연)
- Headplane과 상태 불일치
해결책: 직접 CLI 조회
// 실시간 업데이트 (프론트엔드)
function updateStats() {
fetch('/api/dashboard/stats')
.then(response => response.json())
.then(stats => {
// Headplane과 동일한 3/5 온라인 표시
document.querySelector('[data-stat="online"]').textContent = stats.online_machines;
document.querySelector('[data-stat="offline"]').textContent = stats.offline_machines;
});
}
// 10초마다 업데이트 (Headplane보다 빠름)
setInterval(updateStats, 10000);
🚀 확장 가능한 기능 구현 로드맵
Phase 1: 기본 Headplane 기능 대체 ✅
- 실시간 노드 상태 동기화
- 노드 삭제 기능
- 대시보드 통계
- 머신 목록 관리
Phase 2: 고급 네트워크 관리 기능
-
노드 이름 변경
@app.route('/api/nodes/<int:node_id>/rename', methods=['POST']) def api_rename_node(node_id): new_name = request.json.get('new_name') subprocess.run(['docker', 'exec', 'headscale', 'headscale', 'nodes', 'rename', '-i', str(node_id), new_name]) -
노드 만료/로그아웃
@app.route('/api/nodes/<int:node_id>/expire', methods=['POST']) def api_expire_node(node_id): subprocess.run(['docker', 'exec', 'headscale', 'headscale', 'nodes', 'expire', '-i', str(node_id)]) -
라우트 관리
@app.route('/api/nodes/<int:node_id>/routes', methods=['GET']) def api_node_routes(node_id): result = subprocess.run(['docker', 'exec', 'headscale', 'headscale', 'nodes', 'list-routes', '-i', str(node_id), '-o', 'json'])
Phase 3: 약국별 네트워크 관리
-
약국별 사용자 그룹 관리
@app.route('/api/pharmacy/<int:pharmacy_id>/users', methods=['GET']) def api_pharmacy_users(pharmacy_id): # 약국에 속한 Headscale 사용자 조회 pharmacy = get_pharmacy_by_id(pharmacy_id) subprocess.run(['docker', 'exec', 'headscale', 'headscale', 'users', 'list', '-o', 'json']) -
약국별 PreAuth Key 생성
@app.route('/api/pharmacy/<int:pharmacy_id>/preauth-key', methods=['POST']) def api_create_pharmacy_preauth_key(pharmacy_id): pharmacy = get_pharmacy_by_id(pharmacy_id) user_name = pharmacy.headscale_user_name subprocess.run(['docker', 'exec', 'headscale', 'headscale', 'preauthkeys', 'create', '--user', user_name, '--reusable'])
Phase 4: 고급 모니터링 및 자동화
- 실시간 네트워크 토폴로지
- 자동 장애 감지 및 알림
- 성능 메트릭 수집
- 백업 및 복구 자동화
🎯 개발 가이드라인
1. 모든 새 기능은 CLI 우선
# ❌ 잘못된 접근
def bad_implementation():
# 직접 DB 조작 시도
session.execute("UPDATE nodes SET ...")
# ✅ 올바른 접근
def good_implementation():
# Headscale CLI 사용
subprocess.run(['docker', 'exec', 'headscale', 'headscale', 'command'])
2. 에러 처리 표준화
def standard_error_handling():
try:
result = subprocess.run(headscale_command, check=True)
return {'success': True, 'data': result.stdout}
except subprocess.CalledProcessError as e:
return {'success': False, 'error': e.stderr}
except Exception as e:
return {'success': False, 'error': f'서버 오류: {str(e)}'}
3. UI 일관성 유지
// 표준 삭제 확인 패턴
function confirmDelete(itemType, itemName, deleteFunction) {
if (confirm(`정말로 ${itemType} "${itemName}"를 삭제하시겠습니까?\n\n삭제된 항목은 복구할 수 없습니다.`)) {
deleteFunction();
}
}
// 표준 피드백 패턴
function showFeedback(message, type = 'info') {
showToast(message, type);
if (type === 'success') {
setTimeout(() => location.reload(), 1500);
}
}
🔍 디버깅 및 로깅
CLI 호출 로깅
def log_cli_call(command, result):
print(f"🔧 Headscale CLI: {' '.join(command)}")
print(f"📤 Output: {result.stdout}")
if result.stderr:
print(f"⚠️ Error: {result.stderr}")
프론트엔드 상태 디버깅
// 개발 모드에서만 활성화
if (window.location.hostname === 'localhost') {
console.log('🔍 FARMQ Admin Debug Mode');
window.farmqDebug = {
showNodeStatus: () => console.table(onlineStatus),
refreshStats: updateStats,
testAPI: (endpoint) => fetch(endpoint).then(r => r.json())
};
}
📊 성능 최적화
1. CLI 호출 최적화
- JSON 출력 사용으로 파싱 효율화
- 불필요한 CLI 호출 최소화
- 결과 캐싱 (단기간)
2. 프론트엔드 최적화
- 실시간 업데이트 주기 조정 (10초)
- 필요한 데이터만 요청
- 사용자 상호작용 우선순위
🔐 보안 고려사항
1. CLI 명령 검증
def validate_node_id(node_id):
if not isinstance(node_id, int) or node_id <= 0:
raise ValueError("Invalid node ID")
return node_id
def sanitize_command_args(args):
# 특수문자 및 인젝션 방지
return [arg for arg in args if is_safe_arg(arg)]
2. 권한 관리
- API 엔드포인트별 권한 확인
- 약국별 데이터 접근 제한
- 관리자/사용자 역할 구분
📝 결론
FARMQ Admin은 Headscale CLI를 core engine으로 활용하는 웹 프론트엔드 래퍼입니다. 이 접근 방식을 통해:
- Headplane과 100% 호환성 유지
- 실시간 정확한 상태 반영
- 확장 가능한 구조 제공
- 약국 특화 기능 추가 가능
모든 새로운 기능은 이 원칙을 따라 구현하여 일관성 있고 안정적인 시스템을 구축합니다.
Generated with Claude Code - FARMQ Admin Implementation Guide v1.0