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:
PharmQ Admin
2026-05-31 01:07:31 +00:00
parent 7f3a6b9302
commit b581a2b3a8
2 changed files with 113 additions and 55 deletions

View File

@@ -111,38 +111,58 @@ function Install-Tailscale {
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
# of sync (e.g. GitHub reports 1.98.3 while stable already serves 1.98.4), so
# building a versioned URL such as tailscale-setup-<version>.exe results in a
# 404. The 'latest' alias always exists, so we use it directly.
$downloadUrl = "https://pkgs.tailscale.com/stable/tailscale-setup-latest.exe"
$tempPath = "$env:TEMP\tailscale-setup-latest.exe"
# of sync, so we use the version-less 'latest' alias which always exists.
$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 {
Write-Status "Downloading Tailscale from: $downloadUrl"
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempPath -UseBasicParsing
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
$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"
}
# Make sure the install dir is on PATH for this session
$tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe"
if (Test-Path $tailscaleExe) {
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
if ($currentPath -notlike "*Tailscale*") {
[Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\Program Files\Tailscale", "Machine")
}
if ($env:Path -notlike "*Tailscale*") {
$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
@@ -162,8 +182,15 @@ function Start-TailscaleService {
Write-Status "Starting Tailscale service..."
try {
# Start Tailscale service
$service = Get-Service -Name "Tailscale" -ErrorAction SilentlyContinue
# The MSI registers a service named "Tailscale"; it may take a few seconds
# 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.Status -ne "Running") {
Start-Service -Name "Tailscale"
@@ -171,7 +198,19 @@ function Start-TailscaleService {
}
Write-Success "Tailscale service is running."
} else {
# Fall back to launching tailscaled directly if the service is missing
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 {

View File

@@ -117,38 +117,57 @@ function Install-Tailscale {
Write-Info "Windows용 Tailscale 설치 중..."
# 공식 stable 채널의 'latest' 설치 파일을 항상 사용한다.
# 주의: GitHub "최신 릴리스" 태그와 pkgs.tailscale.com/stable 채널 버전이
# 어긋날 수 있다(예: GitHub=1.98.3 인데 stable=1.98.4). 그래서
# tailscale-setup-<버전>.exe 식으로 URL을 조립하면 404가 난다.
# 'latest' 별칭은 항상 존재하므로 이걸 직접 사용한다.
$downloadUrl = "https://pkgs.tailscale.com/stable/tailscale-setup-latest.exe"
$tempPath = "$env:TEMP\tailscale-setup-latest.exe"
# 공식 stable 채널의 'latest' MSI를 msiexec /quiet 로 설치한다.
# 이전 방식(tailscale-setup-latest.exe /S)은 NSIS GUI 인스톨러로, 사일런트
# 스위치가 'Tailscale' 서비스나 tailscale.exe 를 스크립트 진행 전에 확실히
# 설치하지 못해 설치 직후 "service not found" / "executable not found" 오류가
# 발생했다. MSI는 서비스를 동기적으로 설치하는 공식 무인 설치 경로다.
# 주의: GitHub "최신 릴리스" 태그와 stable 채널 버전이 어긋날 수 있으므로
# 버전 없는 '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 {
Write-Status "Tailscale 다운로드 중: $downloadUrl"
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempPath -UseBasicParsing
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 환경변수 새로고침
$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"
}
# 이번 세션 PATH에 설치 경로 보장
$tailscaleExe = "C:\Program Files\Tailscale\tailscale.exe"
if (Test-Path $tailscaleExe) {
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
if ($currentPath -notlike "*Tailscale*") {
[Environment]::SetEnvironmentVariable("Path", "$currentPath;C:\Program Files\Tailscale", "Machine")
}
if ($env:Path -notlike "*Tailscale*") {
$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