Fix PowerShell encoding issues: Add English-only version
- Add farmq-install-en.ps1 with UTF-8 encoding support - Remove Korean characters to prevent PowerShell encoding issues - Use ASCII-only status indicators ([*], [+], [!]) - Maintain all functionality while ensuring compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
09cdb088af
commit
b7c621f294
481
farmq-install-en.ps1
Normal file
481
farmq-install-en.ps1
Normal file
@ -0,0 +1,481 @@
|
|||||||
|
# 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 {
|
||||||
|
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-Connection {
|
||||||
|
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 {
|
||||||
|
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 <node> # 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-Connection
|
||||||
|
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
|
||||||
Loading…
Reference in New Issue
Block a user