diff --git a/CLIENT_SETUP_GUIDE.md b/CLIENT_SETUP_GUIDE.md new file mode 100644 index 0000000..e028d0e --- /dev/null +++ b/CLIENT_SETUP_GUIDE.md @@ -0,0 +1,193 @@ +# 팜큐(FARMQ) Headscale 클라이언트 설치 가이드 + +## 🏥 개요 + +팜큐 네트워크에 PC를 연결하기 위한 간편한 설치 가이드입니다. + +## 📋 Pre-auth Key 정보 + +### ✅ Pre-auth Key 특징: +- **1회 등록**: 한 번 사용하면 해당 머신이 영구적으로 네트워크에 등록됩니다 +- **자동 재연결**: 재부팅 후에도 자동으로 연결됩니다 +- **재사용 가능**: 동일한 key로 여러 머신을 등록할 수 있습니다 (설정에 따라) + +### 🔑 현재 유효한 Key: +- **myuser**: `fc4f2dc55ee00c5352823d156129b9ce2df4db02f1d76a21` (재사용 가능) +- **pharmacy1**: `5c15b41ea8b135dbed42455ad1a9a0cf0352b100defd241c` (7일 유효, 재사용 가능) + +## 🚀 자동 설치 (권장) + +### Linux/Ubuntu 시스템 + +1. **스크립트 다운로드**: + ```bash + wget https://head.0bin.in/register-client.sh + chmod +x register-client.sh + ``` + +2. **스크립트 실행**: + ```bash + sudo ./register-client.sh + ``` + +### 수동으로 Pre-auth Key 업데이트 + +스크립트의 Pre-auth Key를 업데이트하려면: + +```bash +# 스크립트 편집 +nano register-client.sh + +# PREAUTH_KEY 값을 새로운 key로 변경 +PREAUTH_KEY="새로운키값" +``` + +## 🔧 수동 설치 + +### 1. Tailscale 설치 + +#### Ubuntu/Debian: +```bash +curl -fsSL https://tailscale.com/install.sh | sh +``` + +#### CentOS/RHEL: +```bash +curl -fsSL https://tailscale.com/install.sh | sh +``` + +#### macOS: +```bash +# Homebrew 사용 +brew install --cask tailscale + +# 또는 직접 다운로드 +# https://tailscale.com/download/mac +``` + +#### Windows: +1. https://tailscale.com/download/windows 에서 다운로드 +2. 설치 후 아래 명령어를 관리자 권한 PowerShell에서 실행 + +### 2. Headscale 서버에 등록 + +#### Linux/macOS: +```bash +sudo tailscale up \ + --login-server="https://head.0bin.in" \ + --authkey="fc4f2dc55ee00c5352823d156129b9ce2df4db02f1d76a21" \ + --accept-routes \ + --accept-dns=false +``` + +#### Windows (PowerShell 관리자 권한): +```powershell +tailscale up ` + --login-server="https://head.0bin.in" ` + --authkey="fc4f2dc55ee00c5352823d156129b9ce2df4db02f1d76a21" ` + --accept-routes ` + --accept-dns=false +``` + +## 📊 연결 확인 + +### 연결 상태 확인: +```bash +tailscale status +``` + +### IP 주소 확인: +```bash +tailscale ip -4 +``` + +### 네트워크 테스트: +```bash +# 다른 팜큐 머신으로 핑 테스트 +ping 100.64.0.1 +``` + +## 🔑 관리자용 - Pre-auth Key 생성 + +### 새로운 약국용 Key 생성: + +1. **스크립트 사용** (권장): + ```bash + ./create-preauth-key.sh pharmacy2 30d + ``` + +2. **수동 생성**: + ```bash + # 사용자 생성 (필요시) + docker exec headscale headscale users create pharmacy2 + + # Pre-auth key 생성 + docker exec headscale headscale preauthkeys create \ + -u 2 --expiration 30d --reusable + ``` + +### Key 관리 명령어: + +```bash +# 사용자 목록 확인 +docker exec headscale headscale users list + +# Pre-auth key 목록 확인 (사용자 ID 필요) +docker exec headscale headscale preauthkeys list -u 1 + +# 만료된 key 삭제 +docker exec headscale headscale preauthkeys expire -k +``` + +## 🛠️ 문제해결 + +### 연결 안됨: +1. **방화벽 확인**: 8080, 443 포트가 열려있는지 확인 +2. **DNS 확인**: `https://head.0bin.in` 접근 가능한지 확인 +3. **Key 유효성**: Pre-auth key가 만료되지 않았는지 확인 + +### 기존 연결 해제: +```bash +sudo tailscale logout +``` + +### 완전 재설정: +```bash +sudo tailscale logout +sudo tailscale up --login-server="https://head.0bin.in" --authkey="새로운키" +``` + +## 📱 모바일 설정 + +### Android/iOS: +1. Tailscale 앱 설치 +2. "Use a different server" 선택 +3. 서버 URL: `https://head.0bin.in` +4. Pre-auth key 입력 (위 key 중 하나 사용) + +## 🔐 보안 참고사항 + +- Pre-auth key는 민감한 정보입니다. 공유 시 주의하세요 +- Key가 만료되기 전에 새로운 key를 생성하세요 +- 불필요한 key는 정기적으로 만료시키세요 +- 각 약국별로 별도의 사용자와 key를 사용하는 것을 권장합니다 + +## 📞 지원 + +문제가 발생하면 다음 정보를 포함하여 문의하세요: +- 운영체제 정보 +- `tailscale status` 출력 +- 에러 메시지 +- 사용한 Pre-auth key (마지막 8자리만) + +--- + +## 📋 요약 + +1. **간편 설치**: `register-client.sh` 스크립트 실행 +2. **수동 설치**: Tailscale 설치 → `tailscale up` 명령어 실행 +3. **연결 확인**: `tailscale status` 및 `tailscale ip` 확인 +4. **문제 시**: 재부팅 또는 logout 후 재연결 + +**서버**: https://head.0bin.in +**기본 Key**: `fc4f2dc55ee00c5352823d156129b9ce2df4db02f1d76a21` diff --git a/create-preauth-key.sh b/create-preauth-key.sh new file mode 100755 index 0000000..d5aebe0 --- /dev/null +++ b/create-preauth-key.sh @@ -0,0 +1,167 @@ +#!/bin/bash + +# 팜큐(FARMQ) Pre-auth Key 생성 스크립트 +# 사용법: ./create-preauth-key.sh [사용자명] [유효기간(시간)] + +set -e + +# 기본 설정 +DEFAULT_USER="myuser" +DEFAULT_EXPIRY="24h" # 24시간 + +# 사용법 출력 +usage() { + echo "사용법: $0 [사용자명] [유효기간]" + echo "" + echo "예시:" + echo " $0 # myuser 사용자, 24시간 유효" + echo " $0 pharmacy1 # pharmacy1 사용자, 24시간 유효" + echo " $0 pharmacy1 7d # pharmacy1 사용자, 7일 유효" + echo " $0 pharmacy1 1h # pharmacy1 사용자, 1시간 유효" + echo "" + echo "유효기간 형식: 1h, 24h, 7d, 30d 등" +} + +# 색상 출력 함수 +print_status() { + echo -e "\n🔧 $1" +} + +print_success() { + echo -e "\n✅ $1" +} + +print_error() { + echo -e "\n❌ $1" +} + +print_info() { + echo -e "\n📋 $1" +} + +# 사용자 존재 확인 +check_user_exists() { + local username=$1 + + print_status "사용자 '$username' 확인 중..." + + if docker exec headscale headscale users list | grep -q "$username"; then + print_info "사용자 '$username'이 존재합니다." + return 0 + else + print_error "사용자 '$username'이 존재하지 않습니다." + print_info "사용자 생성 중..." + + if docker exec headscale headscale users create "$username"; then + print_success "사용자 '$username'이 생성되었습니다." + else + print_error "사용자 생성에 실패했습니다." + exit 1 + fi + fi +} + +# 사용자 ID 가져오기 +get_user_id() { + local username=$1 + + local user_id=$(docker exec headscale headscale users list | grep "$username" | awk '{print $1}') + + if [[ -n "$user_id" ]]; then + echo $user_id + else + print_error "사용자 ID를 찾을 수 없습니다." + exit 1 + fi +} + +# Pre-auth key 생성 +create_preauth_key() { + local username=$1 + local expiry=$2 + + print_status "Pre-auth key 생성 중..." + print_info "사용자: $username" + print_info "유효기간: $expiry" + + local user_id=$(get_user_id "$username") + print_info "사용자 ID: $user_id" + + # Pre-auth key 생성 (재사용 가능, 임시 아님) + local preauth_output=$(docker exec headscale headscale preauthkeys create \ + -u "$user_id" \ + --expiration "$expiry" \ + --reusable) + + if [[ $? -eq 0 ]]; then + # Key 값 추출 + local preauth_key=$(echo "$preauth_output" | grep -o '[a-f0-9]\{48\}') + + if [[ -n "$preauth_key" ]]; then + print_success "Pre-auth key가 생성되었습니다!" + print_info "Key: $preauth_key" + + # 클라이언트 등록 스크립트에 추가할 명령어 출력 + echo "" + echo "==========================================" + echo "📋 클라이언트에서 사용할 명령어:" + echo "==========================================" + echo "" + echo "Linux/macOS:" + echo "sudo tailscale up \\" + echo " --login-server=\"https://head.0bin.in\" \\" + echo " --authkey=\"$preauth_key\" \\" + echo " --accept-routes \\" + echo " --accept-dns=false" + echo "" + echo "==========================================" + echo "📋 등록 스크립트 업데이트:" + echo "==========================================" + echo "" + echo "register-client.sh 파일의 PREAUTH_KEY 값을 다음으로 업데이트하세요:" + echo "PREAUTH_KEY=\"$preauth_key\"" + echo "" + + return 0 + fi + fi + + print_error "Pre-auth key 생성에 실패했습니다." + exit 1 +} + +# 기존 Pre-auth key 목록 표시 +list_existing_keys() { + local username=$1 + local user_id=$(get_user_id "$username") + + print_info "기존 Pre-auth key 목록 (사용자: $username):" + docker exec headscale headscale preauthkeys list -u "$user_id" +} + +# 메인 함수 +main() { + local username="${1:-$DEFAULT_USER}" + local expiry="${2:-$DEFAULT_EXPIRY}" + + echo "==========================================" + echo " 🔑 팜큐(FARMQ) Pre-auth Key 생성" + echo "==========================================" + + # 도움말 요청 확인 + if [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then + usage + exit 0 + fi + + check_user_exists "$username" + list_existing_keys "$username" + create_preauth_key "$username" "$expiry" + + echo "" + print_success "완료!" + print_info "이 key는 $expiry 동안 유효하며, 여러 번 사용할 수 있습니다." +} + +# 스크립트 실행 +main "$@" diff --git a/farmq-admin/utils/database_new.py b/farmq-admin/utils/database_new.py index a816e40..8918dc4 100644 --- a/farmq-admin/utils/database_new.py +++ b/farmq-admin/utils/database_new.py @@ -118,8 +118,9 @@ def get_dashboard_stats() -> Dict[str, Any]: # ========================================== def get_all_pharmacies_with_stats() -> List[Dict[str, Any]]: - """모든 약국과 통계 정보 조회""" + """모든 약국과 통계 정보 조회 - Headscale Node 데이터 사용""" farmq_session = get_farmq_session() + headscale_session = get_headscale_session() try: pharmacies = farmq_session.query(PharmacyInfo).filter( @@ -128,23 +129,31 @@ def get_all_pharmacies_with_stats() -> List[Dict[str, Any]]: result = [] for pharmacy in pharmacies: - # 해당 약국의 머신 수 조회 - machine_count = farmq_session.query(MachineProfile).filter( - MachineProfile.pharmacy_id == pharmacy.id, - MachineProfile.status == 'active' - ).count() + # Headscale에서 해당 사용자의 머신 수 조회 + user_machines = headscale_session.query(Node).join(User).filter( + User.name == pharmacy.headscale_user_name, + Node.deleted_at.is_(None) + ).all() - online_count = farmq_session.query(MachineProfile).filter( - MachineProfile.pharmacy_id == pharmacy.id, - MachineProfile.status == 'active', - MachineProfile.tailscale_status == 'online' - ).count() + machine_count = len(user_machines) - # 활성 알림 수 - alert_count = farmq_session.query(SystemAlert).filter( - SystemAlert.pharmacy_id == pharmacy.id, - SystemAlert.status == 'active' - ).count() + # 온라인 머신 수 계산 (24시간 timeout) + online_count = 0 + for machine in user_machines: + if machine.last_seen: + try: + from datetime import timezone + if machine.last_seen.tzinfo is not None: + cutoff_time = datetime.now(timezone.utc) - timedelta(hours=24) + else: + cutoff_time = datetime.now() - timedelta(hours=24) + if machine.last_seen > cutoff_time: + online_count += 1 + except Exception: + online_count += 1 # 타임존 에러 시 온라인으로 간주 + + # 활성 알림 수 (현재는 0으로 설정, 나중에 구현) + alert_count = 0 pharmacy_data = pharmacy.to_dict() pharmacy_data.update({ @@ -160,6 +169,7 @@ def get_all_pharmacies_with_stats() -> List[Dict[str, Any]]: finally: close_session(farmq_session) + close_session(headscale_session) def get_pharmacy_detail(pharmacy_id: int) -> Optional[Dict[str, Any]]: """약국 상세 정보 조회""" diff --git a/register-client.sh b/register-client.sh new file mode 100755 index 0000000..93dabe9 --- /dev/null +++ b/register-client.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +# 팜큐(FARMQ) Headscale 클라이언트 등록 스크립트 +# 사용법: ./register-client.sh + +set -e + +# 설정 +HEADSCALE_SERVER="https://head.0bin.in" +PREAUTH_KEY="fc4f2dc55ee00c5352823d156129b9ce2df4db02f1d76a21" + +# 색상 출력 함수 +print_status() { + echo -e "\n🔧 $1" +} + +print_success() { + echo -e "\n✅ $1" +} + +print_error() { + echo -e "\n❌ $1" +} + +print_info() { + echo -e "\n📋 $1" +} + +# 운영체제 감지 +detect_os() { + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + if command -v apt &> /dev/null; then + OS="ubuntu" + elif command -v yum &> /dev/null; then + OS="centos" + else + OS="linux" + fi + elif [[ "$OSTYPE" == "darwin"* ]]; then + OS="macos" + elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then + OS="windows" + else + OS="unknown" + fi + echo $OS +} + +# Tailscale 설치 확인 및 설치 +install_tailscale() { + OS=$(detect_os) + + if command -v tailscale &> /dev/null; then + print_info "Tailscale이 이미 설치되어 있습니다." + return 0 + fi + + print_status "Tailscale 설치 중..." + + case $OS in + "ubuntu") + curl -fsSL https://tailscale.com/install.sh | sh + ;; + "centos") + curl -fsSL https://tailscale.com/install.sh | sh + ;; + "macos") + echo "macOS용 Tailscale을 다운로드합니다." + echo "다음 URL에서 수동으로 설치하세요: https://tailscale.com/download/mac" + exit 1 + ;; + "windows") + echo "Windows용 Tailscale을 다운로드합니다." + echo "다음 URL에서 수동으로 설치하세요: https://tailscale.com/download/windows" + exit 1 + ;; + *) + print_error "지원되지 않는 운영체제입니다: $OSTYPE" + exit 1 + ;; + esac +} + +# 기존 Tailscale 연결 해제 +disconnect_existing() { + if tailscale status --json &> /dev/null; then + local current_status=$(tailscale status --json 2>/dev/null || echo "{}") + if echo "$current_status" | grep -q '"BackendState":"Running"'; then + print_status "기존 Tailscale 연결을 해제합니다..." + sudo tailscale logout || true + fi + fi +} + +# Headscale에 등록 +register_to_headscale() { + print_status "팜큐 Headscale 서버에 등록 중..." + print_info "서버: $HEADSCALE_SERVER" + + # Tailscale을 Headscale 서버로 설정하고 등록 + sudo tailscale up \ + --login-server="$HEADSCALE_SERVER" \ + --authkey="$PREAUTH_KEY" \ + --accept-routes \ + --accept-dns=false +} + +# 연결 상태 확인 +check_connection() { + print_status "연결 상태 확인 중..." + + # 잠시 대기 + sleep 3 + + # 상태 확인 + if tailscale status &> /dev/null; then + local tailscale_ip=$(tailscale ip -4 2>/dev/null || echo "") + if [[ -n "$tailscale_ip" ]]; then + print_success "성공적으로 연결되었습니다!" + print_info "할당된 IP: $tailscale_ip" + + print_info "네트워크 상태:" + tailscale status + + return 0 + fi + fi + + print_error "연결에 실패했습니다." + print_info "수동으로 상태를 확인해보세요: tailscale status" + return 1 +} + +# 메인 함수 +main() { + echo "==========================================" + echo " 🏥 팜큐(FARMQ) Headscale 클라이언트 등록" + echo "==========================================" + + # 루트 권한 확인 + if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then + print_error "이 스크립트는 sudo 권한이 필요합니다." + exit 1 + fi + + # 단계별 실행 + install_tailscale + disconnect_existing + register_to_headscale + + if check_connection; then + print_success "🎉 등록 완료!" + print_info "이제 팜큐 네트워크에 연결되었습니다." + print_info "문제가 있으면 관리자에게 문의하세요." + else + print_error "등록 과정에서 문제가 발생했습니다." + exit 1 + fi +} + +# 스크립트 실행 +main "$@" \ No newline at end of file