#!/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 # 다른 노드와 연결 테스트" 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 "설치 완료 - 팜큐 네트워크를 사용할 수 있습니다!" } # ================================ # 메인 함수 # ================================ main() { print_header "팜큐(FARMQ) Headscale 원클릭 설치" # 사전 체크 detect_os check_requirements # 설치 과정 install_tailscale start_tailscale register_headscale # 사후 설정 configure_firewall verify_connection # 정리 및 완료 cleanup show_final_info } # ================================ # 에러 핸들링 # ================================ trap 'echo -e "\n❌ 설치 중 오류가 발생했습니다. 로그를 확인해주세요."; exit 1' ERR # 스크립트 실행 main "$@"