feat: 자동 등록 스크립트를 별도 파일로 분리

변경사항:
- headscale-quick-install.sh: 기존 스크립트 유지 (단순 VPN 등록만)
- headscale-auto-register.sh: 새로운 자동 등록 스크립트 (NEW!)

headscale-auto-register.sh 기능:
- Headscale VPN 자동 등록
- VPN IP 자동 확인 (10초 재시도)
- 약국 정보 수집 (약국명 필수)
- farmq.db에 약국 자동 생성 (demo.pharmq.kr)
- gateway.db에 admin 계정 자동 생성 (gateway.pharmq.kr)
- 로그인 정보 출력 (아이디: p{code}, 비밀번호: 1234)

사용법:
# 기존 방식 (VPN만 등록)
curl -fsSL https://git.0bin.in/.../headscale-quick-install.sh | bash

# 새로운 방식 (VPN + 약국 + 계정 자동 생성)
curl -fsSL https://git.0bin.in/.../headscale-auto-register.sh | bash

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude 2025-11-14 09:46:51 +00:00
parent 9d4142ddb6
commit 7020339867
2 changed files with 682 additions and 163 deletions

676
headscale-auto-register.sh Executable file
View File

@ -0,0 +1,676 @@
#!/bin/bash
# 팜큐(FARMQ) Headscale 원클릭 설치 및 등록 스크립트
# 사용법: curl -fsSL https://git.0bin.in/.../quick-install.sh | sudo bash
# 또는: wget -qO- https://git.0bin.in/.../quick-install.sh | sudo bash
# root 계정: curl -fsSL https://git.0bin.in/.../quick-install.sh | bash
# 강제 재등록: curl -fsSL https://git.0bin.in/.../quick-install.sh | bash -s -- --force
set -e
# ================================
# 설정 (필요시 수정)
# ================================
HEADSCALE_SERVER="http://head.pharmq.kr" # Headscale 서버 주소
PREAUTH_KEY="b46923995afeaec90e588168f2e1bf99801775e8657ce003" # 7일간 재사용 가능한 키
FARMQ_NETWORK="100.64.0.0/10" # 팜큐 네트워크 대역
# 명령행 옵션 처리
FORCE_REGISTER=false
for arg in "$@"; do
case $arg in
--force|-f)
FORCE_REGISTER=true
shift
;;
--help|-h)
echo "사용법: $0 [옵션]"
echo "옵션:"
echo " --force, -f 기존 연결을 강제로 해제하고 재등록"
echo " --help, -h 도움말 표시"
exit 0
;;
*)
# 알 수 없는 옵션 무시
;;
esac
done
# ================================
# 색상 출력 함수
# ================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m' # No Color
print_header() {
echo -e "\n${PURPLE}============================================${NC}"
echo -e "${WHITE}$1${NC}"
echo -e "${PURPLE}============================================${NC}\n"
}
print_status() {
echo -e "\n${BLUE}🔧 $1${NC}"
}
print_success() {
echo -e "\n${GREEN}$1${NC}"
}
print_error() {
echo -e "\n${RED}$1${NC}"
}
print_info() {
echo -e "\n${CYAN}📋 $1${NC}"
}
print_warning() {
echo -e "\n${YELLOW}⚠️ $1${NC}"
}
# ================================
# 운영체제 감지
# ================================
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
VERSION=$VERSION_ID
CODENAME=$VERSION_CODENAME
else
print_error "지원하지 않는 운영체제입니다."
exit 1
fi
print_info "감지된 OS: $OS $VERSION ($CODENAME)"
}
# ================================
# 시스템 요구사항 확인
# ================================
check_requirements() {
print_status "시스템 요구사항 확인 중..."
# Root 권한 확인
if [ "$EUID" -ne 0 ]; then
print_error "이 스크립트는 root 권한으로 실행해야 합니다."
print_info "다음 중 하나의 방법으로 다시 실행해주세요:"
print_info "1. sudo가 있는 경우: curl ... | sudo bash"
print_info "2. root 계정인 경우: curl ... | bash"
exit 1
fi
# curl 또는 wget 확인
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
print_error "curl 또는 wget이 필요합니다."
exit 1
fi
# 네트워크 연결 확인
if ! ping -c 1 8.8.8.8 >/dev/null 2>&1; then
print_warning "인터넷 연결을 확인해주세요."
fi
print_success "시스템 요구사항 확인 완료"
}
# ================================
# Tailscale 설치
# ================================
install_tailscale() {
print_status "Tailscale 클라이언트 설치 중..."
# 이미 설치되어 있는지 확인
if command -v tailscale >/dev/null 2>&1; then
print_info "Tailscale이 이미 설치되어 있습니다."
TAILSCALE_VERSION=$(tailscale version | head -n1)
print_info "현재 버전: $TAILSCALE_VERSION"
return
fi
case $OS in
ubuntu|debian)
print_info "Ubuntu/Debian용 Tailscale 설치 중..."
# GPG 키 추가
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/jammy.noarmor.gpg | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/jammy.tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list
# 패키지 설치
apt-get update -qq
apt-get install -y tailscale
;;
centos|rhel|rocky|almalinux)
print_info "CentOS/RHEL/Rocky용 Tailscale 설치 중..."
# 리포지토리 추가
curl -fsSL https://pkgs.tailscale.com/stable/rhel/tailscale.repo | tee /etc/yum.repos.d/tailscale.repo
# 패키지 설치
if command -v dnf >/dev/null 2>&1; then
dnf install -y tailscale
else
yum install -y tailscale
fi
;;
fedora)
print_info "Fedora용 Tailscale 설치 중..."
dnf install -y tailscale
;;
arch)
print_info "Arch Linux용 Tailscale 설치 중..."
pacman -S --noconfirm tailscale
;;
*)
print_warning "지원하지 않는 배포판입니다. 수동 설치를 시도합니다."
# Universal binary 다운로드
ARCH=$(uname -m)
case $ARCH in
x86_64) TAILSCALE_ARCH="amd64" ;;
aarch64) TAILSCALE_ARCH="arm64" ;;
armv7l) TAILSCALE_ARCH="arm" ;;
*)
print_error "지원하지 않는 아키텍처: $ARCH"
exit 1
;;
esac
# 최신 버전 다운로드
TAILSCALE_VERSION=$(curl -s https://api.github.com/repos/tailscale/tailscale/releases/latest | grep '"tag_name"' | cut -d'"' -f4)
DOWNLOAD_URL="https://pkgs.tailscale.com/stable/tailscale_${TAILSCALE_VERSION#v}_linux_${TAILSCALE_ARCH}.tgz"
cd /tmp
curl -LO "$DOWNLOAD_URL"
tar xzf "tailscale_${TAILSCALE_VERSION#v}_linux_${TAILSCALE_ARCH}.tgz"
# 바이너리 복사
cp "tailscale_${TAILSCALE_VERSION#v}_linux_${TAILSCALE_ARCH}/tailscale" /usr/bin/
cp "tailscale_${TAILSCALE_VERSION#v}_linux_${TAILSCALE_ARCH}/tailscaled" /usr/sbin/
# 시스템 서비스 파일 생성
cat > /etc/systemd/system/tailscaled.service << 'EOF'
[Unit]
Description=Tailscale node agent
Documentation=https://tailscale.com/kb/
Wants=network-pre.target
After=network-pre.target NetworkManager.service systemd-resolved.service
[Service]
EnvironmentFile=/etc/default/tailscaled
ExecStart=/usr/sbin/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/run/tailscale/tailscaled.sock --port=$PORT $FLAGS
ExecStopPost=/usr/bin/tailscale logout
Restart=on-failure
RestartSec=5
Type=notify
RuntimeDirectory=tailscale
RuntimeDirectoryMode=0755
StateDirectory=tailscale
StateDirectoryMode=0700
CacheDirectory=tailscale
CacheDirectoryMode=0750
[Install]
WantedBy=multi-user.target
EOF
# 환경 설정 파일
mkdir -p /etc/default
echo 'FLAGS=""' > /etc/default/tailscaled
echo 'PORT="41641"' >> /etc/default/tailscaled
systemctl daemon-reload
;;
esac
print_success "Tailscale 설치 완료"
# 버전 확인
TAILSCALE_VERSION=$(tailscale version | head -n1)
print_info "설치된 버전: $TAILSCALE_VERSION"
}
# ================================
# Tailscale 서비스 시작
# ================================
start_tailscale() {
print_status "Tailscale 서비스 시작 중..."
# systemd 서비스 활성화 및 시작
systemctl enable tailscaled >/dev/null 2>&1 || true
systemctl start tailscaled >/dev/null 2>&1 || true
# 서비스 상태 확인
sleep 3
if systemctl is-active --quiet tailscaled; then
print_success "Tailscaled 서비스가 실행 중입니다."
else
print_error "Tailscaled 서비스 시작에 실패했습니다."
print_info "수동으로 시작을 시도합니다..."
/usr/sbin/tailscaled --state=/var/lib/tailscale/tailscaled.state &
sleep 5
fi
}
# ================================
# Headscale 등록
# ================================
register_headscale() {
print_status "Headscale 서버에 등록 중..."
# 기존 연결 확인
if tailscale status >/dev/null 2>&1; then
print_warning "이미 Tailscale/Headscale에 연결되어 있습니다."
# 현재 연결 상태 표시
CURRENT_STATUS=$(tailscale status 2>/dev/null | head -5)
print_info "현재 연결 상태:"
echo "$CURRENT_STATUS"
# 현재 서버 확인
CURRENT_SERVER=$(tailscale status --json 2>/dev/null | grep -o '"CurrentTailnet":[^,]*' | cut -d'"' -f4 2>/dev/null || echo "알 수 없음")
TARGET_SERVER=$(echo "$HEADSCALE_SERVER" | sed 's|https\?://||' | sed 's|:[0-9]*||')
print_info "현재 서버: $CURRENT_SERVER"
print_info "대상 서버: $TARGET_SERVER"
# 강제 등록 옵션 확인
if [ "$FORCE_REGISTER" = true ]; then
print_warning "강제 재등록 옵션이 활성화되었습니다."
print_info "기존 연결을 해제하고 재등록합니다..."
tailscale logout >/dev/null 2>&1 || true
sleep 3
# 같은 서버인지 확인
elif [[ "$CURRENT_SERVER" == *"$TARGET_SERVER"* ]] || [[ "$TARGET_SERVER" == *"$CURRENT_SERVER"* ]]; then
print_success "이미 올바른 Headscale 서버에 연결되어 있습니다!"
print_info "등록을 건너뜁니다."
return 0
# 대화형 실행인지 확인 (터미널에서 직접 실행)
elif [ -t 0 ] && [ -t 1 ]; then
print_warning "다른 서버에 연결되어 있습니다."
echo -n "기존 연결을 해제하고 팜큐 Headscale로 등록하시겠습니까? (Y/n): "
read -r REPLY
# 기본값을 Y로 변경 (엔터만 누르면 Y)
if [[ -z "$REPLY" ]] || [[ $REPLY =~ ^[Yy]$ ]]; then
print_info "기존 연결을 해제합니다..."
tailscale logout >/dev/null 2>&1 || true
sleep 3
else
print_info "등록을 건너뜁니다."
return 0
fi
else
# 파이프 실행 시 자동으로 재등록 (기본값: Y)
print_warning "다른 서버에 연결되어 있어 자동으로 팜큐 Headscale로 재등록합니다."
print_info "기존 연결을 해제합니다..."
tailscale logout >/dev/null 2>&1 || true
sleep 3
fi
# 추가 확인: 완전히 로그아웃되었는지 검증
print_status "연결 해제 확인 중..."
for i in {1..10}; do
if ! tailscale status >/dev/null 2>&1; then
print_success "기존 연결이 완전히 해제되었습니다."
break
fi
print_info "로그아웃 대기 중... ($i/10)"
sleep 2
if [ $i -eq 10 ]; then
print_warning "로그아웃이 완료되지 않았지만 계속 진행합니다."
fi
done
fi
print_info "Headscale 서버: $HEADSCALE_SERVER"
print_info "Pre-auth Key: ${PREAUTH_KEY:0:8}***************"
# Headscale 등록 시도
print_status "등록 명령 실행 중..."
if tailscale up \
--login-server="$HEADSCALE_SERVER" \
--authkey="$PREAUTH_KEY" \
--accept-routes \
--accept-dns=true >/dev/null 2>&1; then
print_success "Headscale 등록 성공!"
else
print_error "자동 등록에 실패했습니다. 수동 등록을 진행합니다."
# 수동 등록 모드
print_info "다음 명령을 실행하여 수동 등록하세요:"
echo ""
echo "tailscale up --login-server=\"$HEADSCALE_SERVER\" --authkey=\"$PREAUTH_KEY\" --accept-routes --accept-dns=true"
echo ""
# 등록 URL 시도
REGISTER_URL=$(tailscale up --login-server="$HEADSCALE_SERVER" 2>&1 | grep -o 'https://[^[:space:]]*' | head -1)
if [ -n "$REGISTER_URL" ]; then
print_info "또는 다음 URL을 방문하여 등록하세요:"
echo "$REGISTER_URL"
fi
return 1
fi
}
# ================================
# 연결 상태 확인
# ================================
verify_connection() {
print_status "연결 상태 확인 중..."
# 잠시 대기 (연결 안정화)
sleep 5
# Tailscale 상태 확인
if ! tailscale status >/dev/null 2>&1; then
print_error "Tailscale 연결에 문제가 있습니다."
return 1
fi
# IP 주소 확인
TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || echo "N/A")
TAILSCALE_IP6=$(tailscale ip -6 2>/dev/null || echo "N/A")
print_success "Headscale 네트워크 연결 완료!"
print_info "할당된 IPv4: $TAILSCALE_IP"
print_info "할당된 IPv6: $TAILSCALE_IP6"
# 네트워크 테스트
print_status "네트워크 연결 테스트 중..."
if ping -c 3 -W 5 100.64.0.1 >/dev/null 2>&1; then
print_success "팜큐 네트워크($FARMQ_NETWORK) 연결 정상!"
else
print_warning "네트워크 테스트 실패. 방화벽을 확인해주세요."
fi
# 연결된 노드 확인
print_info "네트워크 상태:"
tailscale status | head -10
}
# ================================
# 방화벽 설정 (선택사항)
# ================================
configure_firewall() {
print_status "방화벽 설정 확인 중..."
# UFW (Ubuntu/Debian)
if command -v ufw >/dev/null 2>&1; then
print_info "UFW 방화벽 감지됨"
if ufw status | grep -q "Status: active"; then
print_info "Tailscale 트래픽 허용 중..."
ufw allow in on tailscale0 >/dev/null 2>&1 || true
ufw allow 41641/udp comment "Tailscale" >/dev/null 2>&1 || true
fi
fi
# firewalld (CentOS/RHEL/Fedora)
if command -v firewall-cmd >/dev/null 2>&1; then
print_info "firewalld 방화벽 감지됨"
if firewall-cmd --state >/dev/null 2>&1; then
print_info "Tailscale 트래픽 허용 중..."
firewall-cmd --permanent --add-service=tailscale >/dev/null 2>&1 || true
firewall-cmd --permanent --add-port=41641/udp >/dev/null 2>&1 || true
firewall-cmd --reload >/dev/null 2>&1 || true
fi
fi
print_success "방화벽 설정 완료"
}
# ================================
# 정리 작업
# ================================
cleanup() {
print_status "정리 작업 수행 중..."
# 임시 파일 정리
rm -rf /tmp/tailscale_* >/dev/null 2>&1 || true
# 시스템 정보 업데이트
if command -v updatedb >/dev/null 2>&1; then
updatedb >/dev/null 2>&1 &
fi
print_success "정리 작업 완료"
}
# ================================
# 최종 정보 출력
# ================================
show_final_info() {
print_header "팜큐 Headscale 설치 완료!"
# 시스템 정보
HOSTNAME=$(hostname)
TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || echo "N/A")
echo -e "${GREEN}🎉 설치가 성공적으로 완료되었습니다!${NC}\n"
echo -e "${CYAN}📋 시스템 정보:${NC}"
echo -e " 호스트명: $HOSTNAME"
echo -e " Tailscale IP: $TAILSCALE_IP"
echo -e " OS: $OS $VERSION"
echo -e " Headscale 서버: $HEADSCALE_SERVER"
echo -e "\n${YELLOW}🔧 유용한 명령어:${NC}"
echo -e " tailscale status # 연결 상태 확인"
echo -e " tailscale ip # 할당된 IP 확인"
echo -e " tailscale ping <node> # 다른 노드와 연결 테스트"
echo -e " tailscale logout # 네트워크에서 해제"
echo -e "\n${PURPLE}🌐 팜큐 관리자 페이지:${NC}"
echo -e " http://192.168.0.151:5002"
echo -e " http://192.168.0.151:5002/vms (VM 관리)"
echo -e "\n${WHITE}문제가 있을 경우 로그를 확인하세요:${NC}"
echo -e " journalctl -u tailscaled -f"
print_header "설치 완료 - 팜큐 네트워크를 사용할 수 있습니다!"
}
# ================================
# 약국 자동 등록 함수들
# ================================
# 약국 정보 수집
collect_pharmacy_info() {
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${WHITE}약국 정보 입력${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# 약국명 입력 (필수)
while [ -z "$PHARMACY_NAME" ]; do
read -p "약국명을 입력하세요: " PHARMACY_NAME
done
# 요양기관부호 입력 (선택)
read -p "요양기관부호 (선택, Enter로 건너뛰기): " HIRA_CODE
# 약국 주소 입력 (선택)
read -p "약국 주소 (선택): " PHARMACY_ADDRESS
# 약국장 이름 입력 (선택)
read -p "약국장 이름 (선택): " OWNER_NAME
# 연락처 입력 (선택)
read -p "약국 연락처 (선택): " PHARMACY_PHONE
echo -e "${GREEN}✓ 약국 정보 입력 완료${NC}"
}
# VPN IP 확인
get_assigned_vpn_ip() {
echo -e "${BLUE}VPN IP 확인 중...${NC}"
# tailscale status로 IP 추출 (최대 10초 대기)
for i in {1..10}; do
VPN_IP=$(tailscale status --json 2>/dev/null | grep -oP '"TailscaleIPs":\["\K[^"]+' | head -1)
if [ -n "$VPN_IP" ]; then
echo -e "${GREEN}✓ VPN IP: $VPN_IP${NC}"
return 0
fi
echo -e "${YELLOW}⏳ VPN IP 할당 대기 중... ($i/10)${NC}"
sleep 1
done
echo -e "${RED}✗ VPN IP를 가져올 수 없습니다${NC}"
return 1
}
# farmq-admin API 호출하여 약국 생성
create_pharmacy_via_api() {
echo -e "${BLUE}약국 등록 중 (farmq.db)...${NC}"
# JSON 데이터 구성
JSON_DATA=$(cat <<EOF
{
"pharmacy_name": "$PHARMACY_NAME",
"vpn_ip": "$VPN_IP",
"hira_code": "$HIRA_CODE",
"address": "$PHARMACY_ADDRESS",
"owner_name": "$OWNER_NAME",
"phone": "$PHARMACY_PHONE",
"api_port": 8082
}
EOF
)
# API 호출 (외부 도메인)
RESPONSE=$(curl -s -X POST https://demo.pharmq.kr/api/pharmacy \
-H "Content-Type: application/json" \
-d "$JSON_DATA")
# pharmacy_code 추출
PHARMACY_CODE=$(echo "$RESPONSE" | grep -oP '"pharmacy_code":"[^"]*"' | cut -d'"' -f4)
if [ -z "$PHARMACY_CODE" ]; then
echo -e "${RED}✗ 약국 생성 실패${NC}"
echo "$RESPONSE"
return 1
fi
echo -e "${GREEN}✓ 약국 생성 완료: $PHARMACY_CODE${NC}"
return 0
}
# gateway API 호출하여 관리자 계정 생성
create_gateway_user_via_api() {
echo -e "${BLUE}관리자 계정 생성 중 (gateway.db)...${NC}"
# username: pharmacy_code 소문자 (P0005 → p0005)
USERNAME=$(echo "$PHARMACY_CODE" | tr '[:upper:]' '[:lower:]')
PASSWORD="1234" # 기본 비밀번호
EMAIL="${USERNAME}@pharmq.internal"
# JSON 데이터 구성
JSON_DATA=$(cat <<EOF
{
"username": "$USERNAME",
"email": "$EMAIL",
"password": "$PASSWORD",
"name": "${PHARMACY_NAME} 관리자",
"phone": "$PHARMACY_PHONE",
"primary_pharmacy_code": "$PHARMACY_CODE",
"role": "admin"
}
EOF
)
# API 호출 (외부 도메인)
RESPONSE=$(curl -s -X POST https://gateway.pharmq.kr/api/auth/register \
-H "Content-Type: application/json" \
-d "$JSON_DATA")
# 성공 여부 확인
if echo "$RESPONSE" | grep -q '"success":true'; then
echo -e "${GREEN}✓ 관리자 계정 생성 완료${NC}"
return 0
else
echo -e "${RED}✗ 관리자 계정 생성 실패${NC}"
echo "$RESPONSE"
return 1
fi
}
# 로그인 정보 출력
display_login_credentials() {
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${WHITE}🎉 설치 및 등록 완료!${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "\n${GREEN}약국 정보:${NC}"
echo -e " 약국 코드: ${WHITE}$PHARMACY_CODE${NC}"
echo -e " 약국명: ${WHITE}$PHARMACY_NAME${NC}"
echo -e " VPN IP: ${WHITE}$VPN_IP${NC}"
echo -e "\n${GREEN}프론트엔드 로그인 정보:${NC}"
echo -e " URL: ${WHITE}https://pharmq.kr${NC}"
echo -e " 아이디: ${WHITE}$(echo "$PHARMACY_CODE" | tr '[:upper:]' '[:lower:]')${NC}"
echo -e " 비밀번호: ${WHITE}1234${NC}"
echo -e " ${YELLOW}⚠ 최초 로그인 후 비밀번호를 변경하세요!${NC}"
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# ================================
# 메인 함수
# ================================
main() {
print_header "팜큐(FARMQ) Headscale 원클릭 설치 및 약국 등록"
# 사전 체크
detect_os
check_requirements
# 설치 과정
install_tailscale
start_tailscale
register_headscale
# VPN IP 확인 (Headscale 등록 직후)
sleep 3 # Headscale에서 IP 할당 대기
get_assigned_vpn_ip || exit 1
# 약국 정보 수집
collect_pharmacy_info
# 약국 및 계정 생성
create_pharmacy_via_api || exit 1
create_gateway_user_via_api || exit 1
# 사후 설정
configure_firewall
verify_connection
# 정리 및 완료
cleanup
display_login_credentials
}
# ================================
# 에러 핸들링
# ================================
trap 'echo -e "\n❌ 설치 중 오류가 발생했습니다. 로그를 확인해주세요."; exit 1' ERR
# 스크립트 실행
main "$@"

View File

@ -486,185 +486,28 @@ show_final_info() {
print_header "설치 완료 - 팜큐 네트워크를 사용할 수 있습니다!"
}
# ================================
# 약국 자동 등록 함수들
# ================================
# 약국 정보 수집
collect_pharmacy_info() {
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${WHITE}약국 정보 입력${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# 약국명 입력 (필수)
while [ -z "$PHARMACY_NAME" ]; do
read -p "약국명을 입력하세요: " PHARMACY_NAME
done
# 요양기관부호 입력 (선택)
read -p "요양기관부호 (선택, Enter로 건너뛰기): " HIRA_CODE
# 약국 주소 입력 (선택)
read -p "약국 주소 (선택): " PHARMACY_ADDRESS
# 약국장 이름 입력 (선택)
read -p "약국장 이름 (선택): " OWNER_NAME
# 연락처 입력 (선택)
read -p "약국 연락처 (선택): " PHARMACY_PHONE
echo -e "${GREEN}✓ 약국 정보 입력 완료${NC}"
}
# VPN IP 확인
get_assigned_vpn_ip() {
echo -e "${BLUE}VPN IP 확인 중...${NC}"
# tailscale status로 IP 추출 (최대 10초 대기)
for i in {1..10}; do
VPN_IP=$(tailscale status --json 2>/dev/null | grep -oP '"TailscaleIPs":\["\K[^"]+' | head -1)
if [ -n "$VPN_IP" ]; then
echo -e "${GREEN}✓ VPN IP: $VPN_IP${NC}"
return 0
fi
echo -e "${YELLOW}⏳ VPN IP 할당 대기 중... ($i/10)${NC}"
sleep 1
done
echo -e "${RED}✗ VPN IP를 가져올 수 없습니다${NC}"
return 1
}
# farmq-admin API 호출하여 약국 생성
create_pharmacy_via_api() {
echo -e "${BLUE}약국 등록 중 (farmq.db)...${NC}"
# JSON 데이터 구성
JSON_DATA=$(cat <<EOF
{
"pharmacy_name": "$PHARMACY_NAME",
"vpn_ip": "$VPN_IP",
"hira_code": "$HIRA_CODE",
"address": "$PHARMACY_ADDRESS",
"owner_name": "$OWNER_NAME",
"phone": "$PHARMACY_PHONE",
"api_port": 8082
}
EOF
)
# API 호출 (외부 도메인)
RESPONSE=$(curl -s -X POST https://demo.pharmq.kr/api/pharmacy \
-H "Content-Type: application/json" \
-d "$JSON_DATA")
# pharmacy_code 추출
PHARMACY_CODE=$(echo "$RESPONSE" | grep -oP '"pharmacy_code":"[^"]*"' | cut -d'"' -f4)
if [ -z "$PHARMACY_CODE" ]; then
echo -e "${RED}✗ 약국 생성 실패${NC}"
echo "$RESPONSE"
return 1
fi
echo -e "${GREEN}✓ 약국 생성 완료: $PHARMACY_CODE${NC}"
return 0
}
# gateway API 호출하여 관리자 계정 생성
create_gateway_user_via_api() {
echo -e "${BLUE}관리자 계정 생성 중 (gateway.db)...${NC}"
# username: pharmacy_code 소문자 (P0005 → p0005)
USERNAME=$(echo "$PHARMACY_CODE" | tr '[:upper:]' '[:lower:]')
PASSWORD="1234" # 기본 비밀번호
EMAIL="${USERNAME}@pharmq.internal"
# JSON 데이터 구성
JSON_DATA=$(cat <<EOF
{
"username": "$USERNAME",
"email": "$EMAIL",
"password": "$PASSWORD",
"name": "${PHARMACY_NAME} 관리자",
"phone": "$PHARMACY_PHONE",
"primary_pharmacy_code": "$PHARMACY_CODE",
"role": "admin"
}
EOF
)
# API 호출 (외부 도메인)
RESPONSE=$(curl -s -X POST https://gateway.pharmq.kr/api/auth/register \
-H "Content-Type: application/json" \
-d "$JSON_DATA")
# 성공 여부 확인
if echo "$RESPONSE" | grep -q '"success":true'; then
echo -e "${GREEN}✓ 관리자 계정 생성 완료${NC}"
return 0
else
echo -e "${RED}✗ 관리자 계정 생성 실패${NC}"
echo "$RESPONSE"
return 1
fi
}
# 로그인 정보 출력
display_login_credentials() {
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${WHITE}🎉 설치 및 등록 완료!${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "\n${GREEN}약국 정보:${NC}"
echo -e " 약국 코드: ${WHITE}$PHARMACY_CODE${NC}"
echo -e " 약국명: ${WHITE}$PHARMACY_NAME${NC}"
echo -e " VPN IP: ${WHITE}$VPN_IP${NC}"
echo -e "\n${GREEN}프론트엔드 로그인 정보:${NC}"
echo -e " URL: ${WHITE}https://pharmq.kr${NC}"
echo -e " 아이디: ${WHITE}$(echo "$PHARMACY_CODE" | tr '[:upper:]' '[:lower:]')${NC}"
echo -e " 비밀번호: ${WHITE}1234${NC}"
echo -e " ${YELLOW}⚠ 최초 로그인 후 비밀번호를 변경하세요!${NC}"
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# ================================
# 메인 함수
# ================================
main() {
print_header "팜큐(FARMQ) Headscale 원클릭 설치 및 약국 등록"
print_header "팜큐(FARMQ) Headscale 원클릭 설치"
# 사전 체크
detect_os
check_requirements
# 설치 과정
install_tailscale
start_tailscale
register_headscale
# VPN IP 확인 (Headscale 등록 직후)
sleep 3 # Headscale에서 IP 할당 대기
get_assigned_vpn_ip || exit 1
# 약국 정보 수집
collect_pharmacy_info
# 약국 및 계정 생성
create_pharmacy_via_api || exit 1
create_gateway_user_via_api || exit 1
# 사후 설정
configure_firewall
verify_connection
# 정리 및 완료
cleanup
display_login_credentials
show_final_info
}
# ================================