# FARMQ Headscale Windows One-Click Installation Script # Usage: Run in Administrator PowerShell # iex ((New-Object System.Net.WebClient).DownloadString('https://git.0bin.in/.../farmq-install.ps1')) # Set UTF-8 encoding for Korean characters $OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 [Console]::OutputEncoding = [Text.Encoding]::UTF8 param( [switch]$Force, [string]$HeadscaleServer = "https://head.0bin.in", [string]$PreAuthKey = "8b3df41d37cb158ea39f41fc32c9af46e761de817ad06038", [string]$FarmqNetwork = "100.64.0.0/10" ) # ================================ # 색상 출력 함수 # ================================ function Write-ColorOutput { param( [string]$Message, [string]$ForegroundColor = "White" ) Write-Host $Message -ForegroundColor $ForegroundColor } function Write-Header { param([string]$Text) Write-Host "" Write-Host "============================================" -ForegroundColor Magenta Write-Host $Text -ForegroundColor White Write-Host "============================================" -ForegroundColor Magenta Write-Host "" } function Write-Status { param([string]$Message) Write-Host "" Write-Host "[*] $Message" -ForegroundColor Blue } function Write-Success { param([string]$Message) Write-Host "" Write-Host "[+] $Message" -ForegroundColor Green } function Write-Error { param([string]$Message) Write-Host "" Write-Host "[!] ERROR: $Message" -ForegroundColor Red } function Write-Warning { param([string]$Message) Write-Host "" Write-Host "[!] WARNING: $Message" -ForegroundColor Yellow } function Write-Info { param([string]$Message) Write-Host "" Write-Host "[i] $Message" -ForegroundColor Cyan } # ================================ # 시스템 요구사항 확인 # ================================ function Test-Requirements { Write-Status "Checking system requirements..." # Check administrator privileges $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($currentUser) if (-NOT $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Error "This script requires administrator privileges." Write-Info "Please restart using one of these methods:" Write-Info "1. Windows Key + X -> 'Windows PowerShell (Admin)'" Write-Info "2. Run the script command again" Write-Host "" Read-Host "Press any key to exit..." exit 1 } # Check Windows version $osVersion = [System.Environment]::OSVersion.Version if ($osVersion.Major -lt 10) { Write-Warning "Windows 10 or later recommended. Current: Windows $($osVersion.Major).$($osVersion.Minor)" } # Check internet connection try { Test-NetConnection "8.8.8.8" -Port 53 -InformationLevel Quiet | Out-Null } catch { Write-Warning "Please check your internet connection." } Write-Success "System requirements check completed" } # ================================ # Tailscale 설치 # ================================ function Install-Tailscale { Write-Status "Tailscale 클라이언트 확인 중..." # 기존 설치 확인 $tailscalePath = Get-Command "tailscale" -ErrorAction SilentlyContinue if ($tailscalePath) { $version = & tailscale version 2>$null | Select-Object -First 1 Write-Info "Tailscale이 이미 설치되어 있습니다." Write-Info "현재 버전: $version" return } Write-Info "Windows용 Tailscale 설치 중..." # 최신 Tailscale 버전 확인 try { Write-Status "최신 Tailscale 버전 확인 중..." $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/tailscale/tailscale/releases/latest" -UseBasicParsing $version = $latestRelease.tag_name.TrimStart('v') Write-Info "최신 버전: $version" } catch { Write-Warning "최신 버전 확인 실패, 기본 버전 사용" $version = "1.86.2" } # 임시 다운로드 경로 $tempPath = "$env:TEMP\tailscale-setup-$version.exe" $downloadUrl = "https://pkgs.tailscale.com/stable/tailscale-setup-$version.exe" try { Write-Status "Tailscale 다운로드 중: $downloadUrl" Invoke-WebRequest -Uri $downloadUrl -OutFile $tempPath -UseBasicParsing Write-Status "Tailscale 설치 중... (잠시 기다려주세요)" Start-Process -FilePath $tempPath -ArgumentList "/S" -Wait # PATH 환경변수 새로고침 $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") # 설치 확인 Start-Sleep -Seconds 3 $tailscaleInstalled = Get-Command "tailscale" -ErrorAction SilentlyContinue if (-not $tailscaleInstalled) { # 직접 경로 시도 $tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe" if (Test-Path $tailscaleExe) { # PATH에 Tailscale 경로 추가 $currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine") if ($currentPath -notlike "*Tailscale*") { [Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\Program Files\Tailscale", "Machine") $env:Path = "$env:Path;C:\Program Files\Tailscale" } } } Remove-Item $tempPath -Force -ErrorAction SilentlyContinue Write-Success "Tailscale 설치 완료" } catch { Write-Error "Tailscale 설치 실패: $($_.Exception.Message)" throw } } # ================================ # Tailscale 서비스 시작 # ================================ function Start-TailscaleService { Write-Status "Tailscale 서비스 시작 중..." try { # Tailscale 서비스 시작 $service = Get-Service -Name "Tailscale" -ErrorAction SilentlyContinue if ($service) { if ($service.Status -ne "Running") { Start-Service -Name "Tailscale" Start-Sleep -Seconds 3 } Write-Success "Tailscale 서비스가 실행 중입니다." } else { Write-Warning "Tailscale 서비스를 찾을 수 없습니다. 수동 시작을 시도합니다." } } catch { Write-Warning "서비스 시작에 실패했습니다: $($_.Exception.Message)" } } # ================================ # Headscale 등록 # ================================ function Register-Headscale { Write-Status "Headscale 서버에 등록 중..." # Tailscale 실행파일 경로 확인 $tailscaleCmd = Get-Command "tailscale" -ErrorAction SilentlyContinue if (-not $tailscaleCmd) { $tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe" if (Test-Path $tailscaleExe) { $tailscaleCmd = @{Source = $tailscaleExe} } else { Write-Error "Tailscale 실행파일을 찾을 수 없습니다." return $false } } $tailscalePath = $tailscaleCmd.Source # 기존 연결 확인 try { $status = & $tailscalePath status 2>$null if ($LASTEXITCODE -eq 0 -and $status) { Write-Warning "이미 Tailscale/Headscale에 연결되어 있습니다." # 현재 연결 상태 표시 Write-Info "현재 연결 상태:" $status | Select-Object -First 5 | ForEach-Object { Write-Host " $_" } # 강제 등록 옵션 확인 if ($Force) { Write-Warning "강제 재등록 옵션이 활성화되었습니다." Write-Info "기존 연결을 해제하고 재등록합니다..." } else { $response = Read-Host "기존 연결을 해제하고 팜큐 Headscale로 등록하시겠습니까? (Y/n)" if ($response -eq "" -or $response -match "^[Yy]") { Write-Info "기존 연결을 해제합니다..." } else { Write-Info "등록을 건너뜁니다." return $true } } # 기존 연결 해제 try { & $tailscalePath logout 2>$null Start-Sleep -Seconds 3 Write-Success "기존 연결이 해제되었습니다." } catch { Write-Warning "연결 해제 중 오류가 발생했지만 계속 진행합니다." } } } catch { # 연결되어 있지 않음 (정상) } Write-Info "Headscale 서버: $HeadscaleServer" Write-Info "Pre-auth Key: $($PreAuthKey.Substring(0,8))***************" # Headscale 등록 시도 Write-Status "등록 명령 실행 중..." try { $arguments = @( "up", "--login-server=$HeadscaleServer", "--authkey=$PreAuthKey", "--accept-routes", "--accept-dns=true" ) & $tailscalePath $arguments if ($LASTEXITCODE -eq 0) { Write-Success "Headscale 등록 성공!" return $true } else { Write-Error "자동 등록에 실패했습니다." Write-Info "수동 등록 명령어:" Write-Host "tailscale up --login-server=`"$HeadscaleServer`" --authkey=`"$PreAuthKey`"" return $false } } catch { Write-Error "등록 중 오류 발생: $($_.Exception.Message)" return $false } } # ================================ # 방화벽 설정 # ================================ function Configure-Firewall { Write-Status "방화벽 설정 확인 중..." try { # Windows Defender 방화벽 예외 추가 $ruleName = "Tailscale-FarmQ" $existingRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue if (-not $existingRule) { New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Protocol UDP -LocalPort 41641 -Action Allow -Profile Any | Out-Null New-NetFirewallRule -DisplayName "$ruleName-Outbound" -Direction Outbound -Protocol UDP -LocalPort 41641 -Action Allow -Profile Any | Out-Null Write-Info "Windows Defender 방화벽 예외를 추가했습니다." } Write-Success "방화벽 설정 완료" } catch { Write-Warning "방화벽 설정 중 오류 발생: $($_.Exception.Message)" Write-Info "수동으로 방화벽에서 Tailscale을 허용해주세요." } } # ================================ # 연결 상태 확인 # ================================ function Test-Connection { Write-Status "연결 상태 확인 중..." Start-Sleep -Seconds 5 # Tailscale 실행파일 경로 확인 $tailscaleCmd = Get-Command "tailscale" -ErrorAction SilentlyContinue if (-not $tailscaleCmd) { $tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe" if (Test-Path $tailscaleExe) { $tailscaleCmd = @{Source = $tailscaleExe} } else { Write-Error "Tailscale 실행파일을 찾을 수 없습니다." return } } $tailscalePath = $tailscaleCmd.Source try { $status = & $tailscalePath status 2>$null if ($LASTEXITCODE -ne 0 -or -not $status) { Write-Error "Tailscale 연결에 문제가 있습니다." return } # IP 주소 확인 $ipv4 = & $tailscalePath ip -4 2>$null $ipv6 = & $tailscalePath ip -6 2>$null Write-Success "Headscale 네트워크 연결 완료!" Write-Info "할당된 IPv4: $(if($ipv4){$ipv4}else{'N/A'})" Write-Info "할당된 IPv6: $(if($ipv6){$ipv6}else{'N/A'})" # 네트워크 테스트 Write-Status "네트워크 연결 테스트 중..." try { Test-Connection "100.64.0.1" -Count 2 -Quiet | Out-Null Write-Success "팜큐 네트워크($FarmqNetwork) 연결 정상!" } catch { Write-Warning "네트워크 테스트 실패. 방화벽을 확인해주세요." } # 연결된 노드 확인 Write-Info "네트워크 상태:" $status | Select-Object -First 10 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } } catch { Write-Error "연결 상태 확인 실패: $($_.Exception.Message)" } } # ================================ # 정리 작업 # ================================ function Complete-Installation { Write-Status "설치 완료 작업 중..." # 임시 파일 정리 Get-ChildItem "$env:TEMP\tailscale*" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue Write-Success "정리 작업 완료" } # ================================ # 최종 정보 출력 # ================================ function Show-FinalInfo { Write-Header "팜큐 Headscale Windows 설치 완료!" # 시스템 정보 $computerName = $env:COMPUTERNAME $tailscaleCmd = Get-Command "tailscale" -ErrorAction SilentlyContinue if (-not $tailscaleCmd) { $tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe" if (Test-Path $tailscaleExe) { $tailscaleCmd = @{Source = $tailscaleExe} } } if ($tailscaleCmd) { $tailscaleIP = & $tailscaleCmd.Source ip -4 2>$null } $osVersion = [System.Environment]::OSVersion.Version Write-ColorOutput "🎉 설치가 성공적으로 완료되었습니다!" "Green" Write-Host "" Write-ColorOutput "📋 시스템 정보:" "Cyan" Write-Host " 컴퓨터명: $computerName" Write-Host " Tailscale IP: $(if($tailscaleIP){$tailscaleIP}else{'N/A'})" Write-Host " OS: Windows $($osVersion.Major).$($osVersion.Minor)" Write-Host " Headscale 서버: $HeadscaleServer" Write-Host "" Write-ColorOutput "🔧 유용한 명령어:" "Yellow" Write-Host " tailscale status # 연결 상태 확인" Write-Host " tailscale ip # 할당된 IP 확인" Write-Host " tailscale ping # 다른 노드와 연결 테스트" Write-Host " tailscale logout # 네트워크에서 해제" Write-Host "" Write-ColorOutput "🌐 팜큐 관리자 페이지:" "Magenta" Write-Host " http://192.168.0.151:5002" Write-Host " http://192.168.0.151:5002/vms (VM 관리)" Write-Host "" Write-ColorOutput "문제가 있을 경우 다음을 확인하세요:" "White" Write-Host " 1. Windows 방화벽 설정" Write-Host " 2. 바이러스 백신 프로그램 예외 설정" Write-Host " 3. 회사 네트워크 정책 확인" Write-Header "설치 완료 - 팜큐 네트워크를 사용할 수 있습니다!" } # ================================ # 메인 함수 # ================================ function Main { # 에러 발생 시 중단 $ErrorActionPreference = "Stop" Write-Header "팜큐(FARMQ) Headscale Windows 원클릭 설치" try { # 설치 과정 Test-Requirements Install-Tailscale Start-TailscaleService $registerSuccess = Register-Headscale if ($registerSuccess) { Configure-Firewall Test-Connection Complete-Installation Show-FinalInfo } else { Write-Warning "등록에 실패했지만 Tailscale은 설치되었습니다." Write-Info "수동으로 등록을 완료해주세요." } } catch { Write-Error "설치 중 오류가 발생했습니다: $($_.Exception.Message)" Write-Info "문제가 지속되면 관리자에게 문의하세요." Write-Host "" Read-Host "아무 키나 누르세요..." exit 1 } } # ================================ # 스크립트 실행 # ================================ # 파라미터 처리 if ($args -contains "--help" -or $args -contains "-h") { Write-Host "팜큐 Headscale Windows 설치 스크립트" Write-Host "" Write-Host "사용법:" Write-Host " iex ((New-Object System.Net.WebClient).DownloadString('https://git.0bin.in/.../farmq-install.ps1'))" Write-Host "" Write-Host "옵션:" Write-Host " -Force 기존 연결을 강제로 해제하고 재등록" Write-Host " -HeadscaleServer 서버 주소 (기본값: https://head.0bin.in)" Write-Host "" Write-Host "예시:" Write-Host " # 강제 재등록" Write-Host " iex ((New-Object System.Net.WebClient).DownloadString('https://git.0bin.in/.../farmq-install.ps1?force=1'))" exit 0 } # Force 파라미터 URL에서 처리 if ($MyInvocation.MyCommand.Path -like "*force=1*") { $Force = $true } # 메인 함수 실행 Main