# 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-en.ps1')) param( [switch]$Force, [string]$HeadscaleServer = "https://head.0bin.in", [string]$PreAuthKey = "8b3df41d37cb158ea39f41fc32c9af46e761de817ad06038", [string]$FarmqNetwork = "100.64.0.0/10" ) # Set console to support Unicode characters $PSDefaultParameterValues['*:Encoding'] = 'utf8' if ($PSVersionTable.PSVersion.Major -ge 6) { $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' } # ================================ # Color Output Functions # ================================ 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 } # ================================ # System Requirements Check # ================================ 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. Right-click PowerShell -> 'Run as Administrator'" 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 { Microsoft.PowerShell.Management\Test-Connection "8.8.8.8" -Count 1 -Quiet | Out-Null } catch { Write-Warning "Please check your internet connection." } Write-Success "System requirements check completed" } # ================================ # Install Tailscale # ================================ function Install-Tailscale { Write-Status "Checking Tailscale installation..." # Check existing installation $tailscalePath = Get-Command "tailscale" -ErrorAction SilentlyContinue if ($tailscalePath) { $version = & tailscale version 2>$null | Select-Object -First 1 Write-Info "Tailscale is already installed." Write-Info "Current version: $version" return } Write-Info "Installing Tailscale for Windows..." # Temporary download path $tempPath = "$env:TEMP\tailscale-setup.exe" $downloadUrl = "https://pkgs.tailscale.com/stable/tailscale-setup.exe" try { Write-Status "Downloading Tailscale..." Invoke-WebRequest -Uri $downloadUrl -OutFile $tempPath -UseBasicParsing Write-Status "Installing Tailscale... (please wait)" Start-Process -FilePath $tempPath -ArgumentList "/S" -Wait # Refresh PATH environment variable $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") # Verify installation Start-Sleep -Seconds 3 $tailscaleInstalled = Get-Command "tailscale" -ErrorAction SilentlyContinue if (-not $tailscaleInstalled) { # Try direct path $tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe" if (Test-Path $tailscaleExe) { # Add Tailscale to PATH $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 installation completed" } catch { Write-Error "Tailscale installation failed: $($_.Exception.Message)" throw } } # ================================ # Start Tailscale Service # ================================ function Start-TailscaleService { Write-Status "Starting Tailscale service..." try { # Start Tailscale service $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 service is running." } else { Write-Warning "Tailscale service not found. Attempting manual start..." } } catch { Write-Warning "Failed to start service: $($_.Exception.Message)" } } # ================================ # Register with Headscale # ================================ function Register-Headscale { Write-Status "Registering with Headscale server..." # Find Tailscale executable path $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 executable not found." return $false } } $tailscalePath = $tailscaleCmd.Source # Check existing connection try { $status = & $tailscalePath status 2>$null if ($LASTEXITCODE -eq 0 -and $status) { Write-Warning "Already connected to Tailscale/Headscale network." # Show current connection status Write-Info "Current connection status:" $status | Select-Object -First 5 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } # Check force registration option if ($Force) { Write-Warning "Force registration option is enabled." Write-Info "Disconnecting existing connection and re-registering..." } else { $response = Read-Host "Disconnect existing connection and register with FARMQ Headscale? (Y/n)" if ($response -eq "" -or $response -match "^[Yy]") { Write-Info "Disconnecting existing connection..." } else { Write-Info "Skipping registration." return $true } } # Disconnect existing connection try { & $tailscalePath logout 2>$null Start-Sleep -Seconds 3 Write-Success "Existing connection disconnected." } catch { Write-Warning "Error during disconnection, but continuing..." } } } catch { # Not connected (normal) } Write-Info "Headscale Server: $HeadscaleServer" Write-Info "Pre-auth Key: $($PreAuthKey.Substring(0,8))***************" # Attempt Headscale registration Write-Status "Executing registration command..." try { $arguments = @( "up", "--login-server=$HeadscaleServer", "--authkey=$PreAuthKey", "--accept-routes", "--accept-dns=false" ) & $tailscalePath $arguments if ($LASTEXITCODE -eq 0) { Write-Success "Headscale registration successful!" return $true } else { Write-Error "Automatic registration failed." Write-Info "Manual registration command:" Write-Host "tailscale up --login-server=`"$HeadscaleServer`" --authkey=`"$PreAuthKey`"" return $false } } catch { Write-Error "Registration error: $($_.Exception.Message)" return $false } } # ================================ # Configure Firewall # ================================ function Configure-Firewall { Write-Status "Configuring firewall settings..." try { # Add Windows Defender firewall exception $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 firewall exceptions added." } Write-Success "Firewall configuration completed" } catch { Write-Warning "Firewall configuration error: $($_.Exception.Message)" Write-Info "Please manually allow Tailscale through Windows firewall." } } # ================================ # Verify Connection # ================================ function Test-NetworkConnection { Write-Status "Verifying network connection..." Start-Sleep -Seconds 5 # Find Tailscale executable path $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 executable not found." return } } $tailscalePath = $tailscaleCmd.Source try { $status = & $tailscalePath status 2>$null if ($LASTEXITCODE -ne 0 -or -not $status) { Write-Error "Tailscale connection issue detected." return } # Get IP addresses $ipv4 = & $tailscalePath ip -4 2>$null $ipv6 = & $tailscalePath ip -6 2>$null Write-Success "Headscale network connection completed!" Write-Info "Assigned IPv4: $(if($ipv4){$ipv4}else{'N/A'})" Write-Info "Assigned IPv6: $(if($ipv6){$ipv6}else{'N/A'})" # Network connectivity test Write-Status "Testing network connectivity..." try { Microsoft.PowerShell.Management\Test-Connection "100.64.0.1" -Count 2 -Quiet | Out-Null Write-Success "FARMQ network ($FarmqNetwork) connection successful!" } catch { Write-Warning "Network test failed. Please check firewall settings." } # Show connected nodes Write-Info "Network status:" $status | Select-Object -First 10 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } } catch { Write-Error "Connection verification failed: $($_.Exception.Message)" } } # ================================ # Cleanup # ================================ function Complete-Installation { Write-Status "Completing installation..." # Clean temporary files Get-ChildItem "$env:TEMP\tailscale*" -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue Write-Success "Cleanup completed" } # ================================ # Show Final Information # ================================ function Show-FinalInfo { Write-Header "FARMQ Headscale Windows Installation Complete!" # System information $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-Host "Installation completed successfully!" -ForegroundColor Green Write-Host "" Write-Host "System Information:" -ForegroundColor Cyan Write-Host " Computer Name: $computerName" Write-Host " Tailscale IP: $(if($tailscaleIP){$tailscaleIP}else{'N/A'})" Write-Host " OS: Windows $($osVersion.Major).$($osVersion.Minor)" Write-Host " Headscale Server: $HeadscaleServer" Write-Host "" Write-Host "Useful Commands:" -ForegroundColor Yellow Write-Host " tailscale status # Check connection status" Write-Host " tailscale ip # Show assigned IP" Write-Host " tailscale ping # Test connection to other nodes" Write-Host " tailscale logout # Disconnect from network" Write-Host "" Write-Host "FARMQ Management Pages:" -ForegroundColor Magenta Write-Host " http://192.168.0.151:5002" Write-Host " http://192.168.0.151:5002/vms (VM Management)" Write-Host "" Write-Host "If you encounter issues, check:" -ForegroundColor White Write-Host " 1. Windows Firewall settings" Write-Host " 2. Antivirus software exceptions" Write-Host " 3. Corporate network policies" Write-Header "Installation Complete - You can now use FARMQ network!" } # ================================ # Main Function # ================================ function Main { # Stop on errors $ErrorActionPreference = "Stop" Write-Header "FARMQ Headscale Windows One-Click Installation" try { # Installation process Test-Requirements Install-Tailscale Start-TailscaleService $registerSuccess = Register-Headscale if ($registerSuccess) { Configure-Firewall Test-NetworkConnection Complete-Installation Show-FinalInfo } else { Write-Warning "Registration failed but Tailscale is installed." Write-Info "Please complete registration manually." } } catch { Write-Error "Installation error occurred: $($_.Exception.Message)" Write-Info "If the problem persists, please contact administrator." Write-Host "" Read-Host "Press any key to exit..." exit 1 } } # ================================ # Script Execution # ================================ # Handle parameters if ($args -contains "--help" -or $args -contains "-h") { Write-Host "FARMQ Headscale Windows Installation Script" Write-Host "" Write-Host "Usage:" Write-Host " iex ((New-Object System.Net.WebClient).DownloadString('https://git.0bin.in/.../farmq-install-en.ps1'))" Write-Host "" Write-Host "Options:" Write-Host " -Force Force disconnect existing connection and re-register" Write-Host " -HeadscaleServer Server address (default: https://head.0bin.in)" Write-Host "" Write-Host "Examples:" Write-Host " # Force re-registration" Write-Host " `$Force = `$true; iex ((New-Object System.Net.WebClient).DownloadString('https://git.0bin.in/.../farmq-install-en.ps1'))" exit 0 } # Handle Force parameter from URL or variable if ($MyInvocation.MyCommand.Path -like "*force=1*" -or (Get-Variable -Name "ForceInstall" -ErrorAction SilentlyContinue)) { $Force = $true } # Execute main function Main