fix(windows): install Tailscale via MSI (msiexec /quiet) + wait loops
.exe /S (NSIS) 사일런트 설치가 서비스/실행파일을 스크립트 진행 전에 확실히 등록하지 못해 설치 직후 'Tailscale service not found' / 'executable not found' 오류가 났다. 공식 무인 설치 경로인 MSI(msiexec /i /quiet /norestart)로 변경하고, 설치 후 tailscale.exe 와 서비스가 나타날 때까지 폴링하는 재시도 루프를 추가. 서비스가 끝내 없으면 tailscaled install 폴백. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -111,43 +111,63 @@ function Install-Tailscale {
|
|||||||
|
|
||||||
Write-Info "Installing Tailscale for Windows..."
|
Write-Info "Installing Tailscale for Windows..."
|
||||||
|
|
||||||
# Always use the 'latest' installer from the official stable channel.
|
# Use the official MSI from the 'latest' stable channel and install it with
|
||||||
|
# msiexec /quiet. The previous approach (tailscale-setup-latest.exe /S) is the
|
||||||
|
# NSIS GUI installer; its silent switch does NOT reliably register the
|
||||||
|
# 'Tailscale' service or drop tailscale.exe before the script continues, which
|
||||||
|
# caused "service not found" / "executable not found" right after install.
|
||||||
|
# The MSI installs the service synchronously and is the supported unattended path.
|
||||||
# NOTE: GitHub's "latest release" tag and pkgs.tailscale.com/stable can be out
|
# NOTE: GitHub's "latest release" tag and pkgs.tailscale.com/stable can be out
|
||||||
# of sync (e.g. GitHub reports 1.98.3 while stable already serves 1.98.4), so
|
# of sync, so we use the version-less 'latest' alias which always exists.
|
||||||
# building a versioned URL such as tailscale-setup-<version>.exe results in a
|
$downloadUrl = "https://pkgs.tailscale.com/stable/tailscale-setup-latest-amd64.msi"
|
||||||
# 404. The 'latest' alias always exists, so we use it directly.
|
$tempPath = "$env:TEMP\tailscale-setup-latest.msi"
|
||||||
$downloadUrl = "https://pkgs.tailscale.com/stable/tailscale-setup-latest.exe"
|
$logPath = "$env:TEMP\tailscale-install.log"
|
||||||
$tempPath = "$env:TEMP\tailscale-setup-latest.exe"
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Write-Status "Downloading Tailscale from: $downloadUrl"
|
Write-Status "Downloading Tailscale from: $downloadUrl"
|
||||||
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempPath -UseBasicParsing
|
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempPath -UseBasicParsing
|
||||||
|
|
||||||
Write-Status "Installing Tailscale... (please wait)"
|
Write-Status "Installing Tailscale... (please wait)"
|
||||||
Start-Process -FilePath $tempPath -ArgumentList "/S" -Wait
|
$proc = Start-Process -FilePath "msiexec.exe" `
|
||||||
|
-ArgumentList "/i", "`"$tempPath`"", "/quiet", "/norestart", "/l*v", "`"$logPath`"" `
|
||||||
|
-Wait -PassThru
|
||||||
|
# 0 = success, 3010 = success but reboot required
|
||||||
|
if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010) {
|
||||||
|
throw "msiexec returned exit code $($proc.ExitCode). See log: $logPath"
|
||||||
|
}
|
||||||
|
|
||||||
# Refresh PATH environment variable
|
# Refresh PATH environment variable
|
||||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
||||||
|
|
||||||
# Verify installation
|
# Make sure the install dir is on PATH for this session
|
||||||
Start-Sleep -Seconds 3
|
$tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe"
|
||||||
$tailscaleInstalled = Get-Command "tailscale" -ErrorAction SilentlyContinue
|
if (Test-Path $tailscaleExe) {
|
||||||
if (-not $tailscaleInstalled) {
|
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||||
# Try direct path
|
if ($currentPath -notlike "*Tailscale*") {
|
||||||
$tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe"
|
[Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\Program Files\Tailscale", "Machine")
|
||||||
if (Test-Path $tailscaleExe) {
|
}
|
||||||
# Add Tailscale to PATH
|
if ($env:Path -notlike "*Tailscale*") {
|
||||||
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
$env:Path = "$env:Path;C:\Program Files\Tailscale"
|
||||||
if ($currentPath -notlike "*Tailscale*") {
|
|
||||||
[Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\Program Files\Tailscale", "Machine")
|
|
||||||
$env:Path = "$env:Path;C:\Program Files\Tailscale"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Wait for tailscale.exe to actually appear (install can lag a few seconds)
|
||||||
|
Write-Status "Waiting for Tailscale to be ready..."
|
||||||
|
$ready = $false
|
||||||
|
for ($i = 0; $i -lt 15; $i++) {
|
||||||
|
if ((Get-Command "tailscale" -ErrorAction SilentlyContinue) -or (Test-Path $tailscaleExe)) {
|
||||||
|
$ready = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
}
|
||||||
|
if (-not $ready) {
|
||||||
|
throw "Tailscale executable did not appear after installation. See log: $logPath"
|
||||||
|
}
|
||||||
|
|
||||||
Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
|
Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
|
||||||
Write-Success "Tailscale installation completed"
|
Write-Success "Tailscale installation completed"
|
||||||
|
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Error "Tailscale installation failed: $($_.Exception.Message)"
|
Write-Error "Tailscale installation failed: $($_.Exception.Message)"
|
||||||
@@ -162,8 +182,15 @@ function Start-TailscaleService {
|
|||||||
Write-Status "Starting Tailscale service..."
|
Write-Status "Starting Tailscale service..."
|
||||||
|
|
||||||
try {
|
try {
|
||||||
# Start Tailscale service
|
# The MSI registers a service named "Tailscale"; it may take a few seconds
|
||||||
$service = Get-Service -Name "Tailscale" -ErrorAction SilentlyContinue
|
# to appear, so poll for it before giving up.
|
||||||
|
$service = $null
|
||||||
|
for ($i = 0; $i -lt 15; $i++) {
|
||||||
|
$service = Get-Service -Name "Tailscale" -ErrorAction SilentlyContinue
|
||||||
|
if ($service) { break }
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
}
|
||||||
|
|
||||||
if ($service) {
|
if ($service) {
|
||||||
if ($service.Status -ne "Running") {
|
if ($service.Status -ne "Running") {
|
||||||
Start-Service -Name "Tailscale"
|
Start-Service -Name "Tailscale"
|
||||||
@@ -171,7 +198,19 @@ function Start-TailscaleService {
|
|||||||
}
|
}
|
||||||
Write-Success "Tailscale service is running."
|
Write-Success "Tailscale service is running."
|
||||||
} else {
|
} else {
|
||||||
|
# Fall back to launching tailscaled directly if the service is missing
|
||||||
Write-Warning "Tailscale service not found. Attempting manual start..."
|
Write-Warning "Tailscale service not found. Attempting manual start..."
|
||||||
|
$tailscaled = "C:\Program Files\Tailscale\tailscaled.exe"
|
||||||
|
if (Test-Path $tailscaled) {
|
||||||
|
Start-Process -FilePath $tailscaled -ArgumentList "install" -Wait -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
$service = Get-Service -Name "Tailscale" -ErrorAction SilentlyContinue
|
||||||
|
if ($service -and $service.Status -ne "Running") {
|
||||||
|
Start-Service -Name "Tailscale" -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
}
|
||||||
|
if ($service) { Write-Success "Tailscale service is running." }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|||||||
@@ -117,43 +117,62 @@ function Install-Tailscale {
|
|||||||
|
|
||||||
Write-Info "Windows용 Tailscale 설치 중..."
|
Write-Info "Windows용 Tailscale 설치 중..."
|
||||||
|
|
||||||
# 공식 stable 채널의 'latest' 설치 파일을 항상 사용한다.
|
# 공식 stable 채널의 'latest' MSI를 msiexec /quiet 로 설치한다.
|
||||||
# 주의: GitHub "최신 릴리스" 태그와 pkgs.tailscale.com/stable 채널 버전이
|
# 이전 방식(tailscale-setup-latest.exe /S)은 NSIS GUI 인스톨러로, 사일런트
|
||||||
# 어긋날 수 있다(예: GitHub=1.98.3 인데 stable=1.98.4). 그래서
|
# 스위치가 'Tailscale' 서비스나 tailscale.exe 를 스크립트 진행 전에 확실히
|
||||||
# tailscale-setup-<버전>.exe 식으로 URL을 조립하면 404가 난다.
|
# 설치하지 못해 설치 직후 "service not found" / "executable not found" 오류가
|
||||||
# 'latest' 별칭은 항상 존재하므로 이걸 직접 사용한다.
|
# 발생했다. MSI는 서비스를 동기적으로 설치하는 공식 무인 설치 경로다.
|
||||||
$downloadUrl = "https://pkgs.tailscale.com/stable/tailscale-setup-latest.exe"
|
# 주의: GitHub "최신 릴리스" 태그와 stable 채널 버전이 어긋날 수 있으므로
|
||||||
$tempPath = "$env:TEMP\tailscale-setup-latest.exe"
|
# 버전 없는 'latest' 별칭을 사용한다(항상 존재함).
|
||||||
|
$downloadUrl = "https://pkgs.tailscale.com/stable/tailscale-setup-latest-amd64.msi"
|
||||||
|
$tempPath = "$env:TEMP\tailscale-setup-latest.msi"
|
||||||
|
$logPath = "$env:TEMP\tailscale-install.log"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Write-Status "Tailscale 다운로드 중: $downloadUrl"
|
Write-Status "Tailscale 다운로드 중: $downloadUrl"
|
||||||
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempPath -UseBasicParsing
|
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempPath -UseBasicParsing
|
||||||
|
|
||||||
Write-Status "Tailscale 설치 중... (잠시 기다려주세요)"
|
Write-Status "Tailscale 설치 중... (잠시 기다려주세요)"
|
||||||
Start-Process -FilePath $tempPath -ArgumentList "/S" -Wait
|
$proc = Start-Process -FilePath "msiexec.exe" `
|
||||||
|
-ArgumentList "/i", "`"$tempPath`"", "/quiet", "/norestart", "/l*v", "`"$logPath`"" `
|
||||||
|
-Wait -PassThru
|
||||||
|
# 0 = 성공, 3010 = 성공(재부팅 필요)
|
||||||
|
if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010) {
|
||||||
|
throw "msiexec 종료 코드 $($proc.ExitCode). 로그: $logPath"
|
||||||
|
}
|
||||||
|
|
||||||
# PATH 환경변수 새로고침
|
# PATH 환경변수 새로고침
|
||||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
||||||
|
|
||||||
# 설치 확인
|
# 이번 세션 PATH에 설치 경로 보장
|
||||||
Start-Sleep -Seconds 3
|
$tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe"
|
||||||
$tailscaleInstalled = Get-Command "tailscale" -ErrorAction SilentlyContinue
|
if (Test-Path $tailscaleExe) {
|
||||||
if (-not $tailscaleInstalled) {
|
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||||
# 직접 경로 시도
|
if ($currentPath -notlike "*Tailscale*") {
|
||||||
$tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe"
|
[Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\Program Files\Tailscale", "Machine")
|
||||||
if (Test-Path $tailscaleExe) {
|
}
|
||||||
# PATH에 Tailscale 경로 추가
|
if ($env:Path -notlike "*Tailscale*") {
|
||||||
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
$env:Path = "$env:Path;C:\Program Files\Tailscale"
|
||||||
if ($currentPath -notlike "*Tailscale*") {
|
|
||||||
[Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\Program Files\Tailscale", "Machine")
|
|
||||||
$env:Path = "$env:Path;C:\Program Files\Tailscale"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# tailscale.exe 가 실제로 나타날 때까지 대기 (설치가 몇 초 지연될 수 있음)
|
||||||
|
Write-Status "Tailscale 준비 대기 중..."
|
||||||
|
$ready = $false
|
||||||
|
for ($i = 0; $i -lt 15; $i++) {
|
||||||
|
if ((Get-Command "tailscale" -ErrorAction SilentlyContinue) -or (Test-Path $tailscaleExe)) {
|
||||||
|
$ready = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
}
|
||||||
|
if (-not $ready) {
|
||||||
|
throw "설치 후 Tailscale 실행 파일을 찾을 수 없습니다. 로그: $logPath"
|
||||||
|
}
|
||||||
|
|
||||||
Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
|
Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
|
||||||
Write-Success "Tailscale 설치 완료"
|
Write-Success "Tailscale 설치 완료"
|
||||||
|
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Error "Tailscale 설치 실패: $($_.Exception.Message)"
|
Write-Error "Tailscale 설치 실패: $($_.Exception.Message)"
|
||||||
|
|||||||
Reference in New Issue
Block a user