# Set up a self-hosted TAK server for ZYRNTOPO Team Sync, on Windows. # # irm https://zyrntopo.com/tak-server.ps1 | iex # # The Windows counterpart to tak-server.sh, and deliberately the same shape: # download the kit, check it against the published SHA-256, unpack it, and run # the installer that ships inside it. Everything after that - Docker Desktop, the # WSL 2 backend, the containers, the certificate authority - is setup-tak-server.ps1's # job, exactly as setup-tak-server.sh does it on Linux and macOS. # # Options (there are no arguments when a script is piped into iex, so they are # read from the environment): # $env:ZYRN_TAK_DIR='C:\tak' where the kit is unpacked # $env:ZYRN_TAK_KIT='https://...' pin a specific kit URL # $env:ZYRN_TAK_NORUN='1' unpack only, do not run the installer # $env:ZYRN_TAK_ARGS='-ExposeAdmin' arguments for setup-tak-server.ps1 # # Windows PowerShell 5.1 on purpose: that is what is in the Start menu on Windows # 10 and 11, and it is what this gets pasted into. No ternaries, no null # coalescing, no ?. - see CLAUDE.md's PowerShell notes. $ErrorActionPreference = 'Stop' $Base = $env:ZYRN_TAK_BASE if (-not $Base) { $Base = 'https://pois.zyrntopo.com/downloads' } # Version-free by default, for the same reason tak-server.sh is: the kit is cut # on its own line, independent of the app, so a number baked into a URL people # copy off a web page rots the next time it moves. $Dir = $env:ZYRN_TAK_DIR if (-not $Dir) { $Dir = Join-Path $HOME 'zyrntopo-tak-server' } function Say { param($m) Write-Host $m } # throw, not exit: this script is piped into iex, so it runs inside the user's # own session and `exit` would close their terminal window - taking the error # message with it. install.ps1 stops the same way, for the same reason. function Die { param($m) Write-Host "error: $m" -ForegroundColor Red; throw $m } # ── TLS ────────────────────────────────────────────────────────────────────── # Windows PowerShell 5.1 negotiates whatever ServicePointManager was left set to, # and on an unpatched Windows 10 that is still TLS 1.0/1.1, which Cloudflare # refuses. Without this the download below dies with the famously unhelpful # "Could not create SSL/TLS secure channel" on an otherwise healthy machine. try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 } catch { } # Invoke-WebRequest in 5.1 repaints its progress bar on every chunk, and that # redraw dominates the transfer. Restored at the end. $PrevProgress = $ProgressPreference $ProgressPreference = 'SilentlyContinue' try { # ── refuse to run where it cannot work, before downloading anything ────── # $IsWindows does not exist in 5.1, where the answer is always yes; it does # exist in PowerShell 7, where it may not be. if ((Test-Path variable:IsWindows) -and -not $IsWindows) { Die 'This script is for Windows. On Linux and macOS use: curl -fsSL https://zyrntopo.com/tak-server.sh | sh' } $tarExe = Join-Path $env:SystemRoot 'System32\tar.exe' $haveTar = Test-Path $tarExe # The kit ships as both a .tar.gz and a .zip. Prefer the zip on Windows and # fall back to the tarball through the bsdtar that ships in System32 - kits # cut before the zip was published have only the tarball, and a one-liner # that works today matters more than a tidier format. $kitUrl = $env:ZYRN_TAK_KIT if (-not $kitUrl) { $kitUrl = "$Base/zyrntopo-tak-server-latest.zip" try { Invoke-WebRequest -Uri $kitUrl -Method Head -UseBasicParsing | Out-Null } catch { if (-not $haveTar) { Die "No .zip kit is published and this Windows has no tar.exe (it arrived in Windows 10 1803).`nDownload the kit by hand from https://zyrntopo.com/tak-server" } $kitUrl = "$Base/zyrntopo-tak-server-latest.tar.gz" } } $kitName = [System.IO.Path]::GetFileName(($kitUrl -split '\?')[0]) $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("zyrn-tak-" + [System.Guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $tmp -Force | Out-Null try { # ── download ──────────────────────────────────────────────────────── Say '-- downloading the kit' $kitFile = Join-Path $tmp $kitName try { Invoke-WebRequest -Uri $kitUrl -OutFile $kitFile -UseBasicParsing } catch { Die "Could not download $kitUrl : $($_.Exception.Message)" } # ── verify ────────────────────────────────────────────────────────── # Checked when a manifest is published for this kit, not required. The # `latest` copy is republished on every cut and its digest changes with # it, so a missing entry must not stop an install that is otherwise fine # - but say which of the two happened rather than printing a reassuring # line either way. $want = '' try { $sums = (Invoke-WebRequest -Uri "$Base/SHA256SUMS-tak-server-latest.txt" -UseBasicParsing).Content foreach ($line in ($sums -split "`n")) { # sha256sum's own format: the digest, a space, and a name that # carries a leading * when the file was read in binary mode. $m = [regex]::Match($line.Trim(), '^([0-9a-fA-F]{64})\s+\*?(.+)$') if ($m.Success -and $m.Groups[2].Value -eq $kitName) { $want = $m.Groups[1].Value.ToLower() } } } catch { } if ($want) { $got = (Get-FileHash -Path $kitFile -Algorithm SHA256).Hash.ToLower() if ($got -ne $want) { Die "checksum mismatch - refusing to run.`n expected $want`n got $got" } Say ' checksum verified' } else { Say ' checksum skipped (no entry published for this kit)' } # ── unpack ────────────────────────────────────────────────────────── Say "-- unpacking to $Dir" $stage = Join-Path $tmp 'stage' New-Item -ItemType Directory -Path $stage -Force | Out-Null if ($kitName.ToLower().EndsWith('.zip')) { Expand-Archive -LiteralPath $kitFile -DestinationPath $stage -Force # The archive carries a versioned top directory and the install path # should not, or every cut leaves another one behind. tar strips it # with --strip-components=1; Expand-Archive cannot, so step into it. $inner = Get-ChildItem -LiteralPath $stage -Directory | Select-Object -First 1 if (-not $inner) { Die 'The kit unpacked to nothing - the download may be truncated.' } $src = $inner.FullName } else { if (-not $haveTar) { Die 'This kit is a .tar.gz and this Windows has no tar.exe (it arrived in Windows 10 1803).' } & $tarExe -xzf $kitFile -C $stage --strip-components=1 if ($LASTEXITCODE -ne 0) { Die "tar exited with code $LASTEXITCODE." } $src = $stage } New-Item -ItemType Directory -Path $Dir -Force | Out-Null Copy-Item -Path (Join-Path $src '*') -Destination $Dir -Recurse -Force # Everything here came off the internet, so Windows marks it, and # PowerShell then refuses to run a marked script under any policy short # of Bypass. The download was checksummed above; unblock what we checked. Get-ChildItem -LiteralPath $Dir -Recurse -Filter *.ps1 | ForEach-Object { Unblock-File -LiteralPath $_.FullName -ErrorAction SilentlyContinue } } finally { Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue } $setup = Join-Path $Dir 'setup-tak-server.ps1' if (-not (Test-Path $setup)) { Die "The kit unpacked but $setup is not in it." } if ($env:ZYRN_TAK_NORUN -eq '1') { Say '-- unpacked. ZYRN_TAK_NORUN=1, so not running the installer.' Say " cd '$Dir'; .\setup-tak-server.ps1" return } Say '-- running the installer' $setupArgs = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $setup) if ($env:ZYRN_TAK_ARGS) { $setupArgs += ($env:ZYRN_TAK_ARGS -split '\s+') } # A child powershell.exe rather than dot-sourcing it here: this script was # itself piped into iex, so there is no execution policy exemption to # inherit, and -ExecutionPolicy Bypass on a child process is the documented # way to run a script file that a policy would otherwise refuse. & powershell.exe @setupArgs if ($LASTEXITCODE -ne 0) { Die "setup-tak-server.ps1 exited with code $LASTEXITCODE." } } finally { $ProgressPreference = $PrevProgress }