param( [string]$Token, [int]$Port = 3000, [ValidateSet("http","tcp")][string]$Mode = "http" ) $ErrorActionPreference = "Stop" function Show-Usage { Write-Host "Interactive install: iwr https://tunneltime.dev/install.ps1 -OutFile install.ps1; .\install.ps1" Write-Host "Compatibility mode: .\install.ps1 -Token '' -Port 3000 -Mode http" } function Prompt-NonEmpty([string]$Prompt) { while ($true) { $value = (Read-Host $Prompt).Trim() if ($value) { return $value } } } function Prompt-YesNo([string]$Prompt) { while ($true) { $value = (Read-Host $Prompt).Trim().ToLowerInvariant() switch ($value) { "y" { return $true } "yes" { return $true } "n" { return $false } "no" { return $false } default { Write-Host "Please answer Y or N." } } } } function Prompt-Choice([string]$Prompt, [string[]]$Allowed, [string]$ErrorText) { while ($true) { $value = (Read-Host $Prompt).Trim().ToLowerInvariant() if ($Allowed -contains $value) { return $value } Write-Host $ErrorText } } function Prompt-Port() { while ($true) { $value = (Read-Host "Local port to tunnel").Trim() $parsed = 0 if ([int]::TryParse($value, [ref]$parsed) -and $parsed -ge 1 -and $parsed -le 65535) { return $parsed } Write-Host "Enter a numeric port between 1 and 65535." } } function Prompt-AccessMode() { Write-Host "" Write-Host "Security options:" Write-Host " public - anyone on the internet can connect" Write-Host " allowlist - only specific IPs or CIDR ranges can connect" Write-Host " dynamic - the first client IP that connects claims the tunnel" Write-Host "Press Enter to keep the default: public." while ($true) { $value = (Read-Host "Security mode [public/allowlist/dynamic]").Trim().ToLowerInvariant() if ([string]::IsNullOrWhiteSpace($value)) { return "public" } if (@("public", "allowlist", "dynamic") -contains $value) { return $value } Write-Host "Choose public, allowlist, or dynamic." } } function Format-Command([string[]]$Items) { return ($Items | ForEach-Object { if ($_ -match '[\s''"]') { "'" + ($_ -replace "'", "''") + "'" } else { $_ } }) -join " " } Invoke-WebRequest "https://tunneltime.dev/downloads/tunneltime.exe" -OutFile "tunneltime.exe" if ($Token -eq "--help" -or $Token -eq "-h") { Show-Usage exit 0 } $interactive = [string]::IsNullOrWhiteSpace($Token) if ($interactive) { Write-Host "TunnelTime installer" Write-Host "Downloaded .\\tunneltime.exe." Write-Host "" $Token = Prompt-NonEmpty "Agent token" } & .\tunneltime.exe login $Token if ($interactive) { Write-Host "" if (-not (Prompt-YesNo "Start a tunnel now? [Y/N]")) { Write-Host "" Write-Host "Saved your token. You can start a tunnel later with commands like:" Write-Host " .\\tunneltime.exe http 3000" Write-Host " .\\tunneltime.exe tcp 22" exit 0 } Write-Host "" Write-Host "Let's set up your first tunnel." Write-Host "Choose http for websites, webhooks, dashboards, and local dev servers." Write-Host "Choose tcp for SSH, databases, and other raw TCP services." $Mode = Prompt-Choice "Tunnel type [http/tcp]" @("http", "tcp") "Enter http or tcp." Write-Host "" Write-Host "Enter the local port where your app or service is listening." $Port = Prompt-Port Write-Host "" Write-Host "If you leave the subdomain blank, TunnelTime will generate one for you." $Subdomain = (Read-Host "Optional custom subdomain (press Enter to auto-generate)").Trim() $AccessMode = Prompt-AccessMode $Allowed = @() if ($AccessMode -eq "allowlist") { Write-Host "" Write-Host "Enter one or more source IPs or CIDR ranges separated by commas." while ($Allowed.Count -eq 0) { $rawAllowed = (Read-Host "Allowed IPs/CIDRs (comma-separated)").Trim() $Allowed = @($rawAllowed.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ }) if ($Allowed.Count -eq 0) { Write-Host "Enter at least one IP or CIDR." } } } } else { $Subdomain = "" $AccessMode = "public" $Allowed = @() } $TunnelArgs = @($Mode, [string]$Port) if ($Subdomain) { $TunnelArgs += @("--subdomain", $Subdomain) } $TunnelArgs += @("--access", $AccessMode) foreach ($entry in $Allowed) { $TunnelArgs += @("--allow", $entry) } Write-Host "" Write-Host "Running command:" $CommandPreview = @(".\\tunneltime.exe") + $TunnelArgs $CommandText = Format-Command -Items $CommandPreview Write-Host $CommandText if ($Mode -eq "tcp") { & .\tunneltime.exe @TunnelArgs } else { & .\tunneltime.exe @TunnelArgs }