<# HGVSupport remote-access installer - Windows (PowerShell). Public, non-secret. Served unauthenticated from https://get.hgvsupport.ddns.net/install.ps1. Run in an ELEVATED PowerShell (right-click Windows PowerShell -> "Run as administrator"): irm https://get.hgvsupport.ddns.net/install.ps1 | iex What it does: 1. installs the Tailscale VPN client (winget, then Chocolatey fallback) 2. installs the Linphone softphone (winget, then Chocolatey fallback) 3. joins the HGVSupport tailnet: tailscale up --login-server=... --accept-routes (browser sign-in) 4. writes the local Linphone config from the extension + phone (SIP) password from your welcome email. The password is typed hidden and is NEVER printed or uploaded anywhere. Fails closed: stops on any error. Safe to re-run. #> $ErrorActionPreference = 'Stop' # --- NON-SECRET constants (addresses, not credentials) --- $LoginServer = 'https://headscale.hgvsupport.ddns.net' $SipServer = '192.168.3.151' function Log ($m) { Write-Host "`n[HGV] $m" -ForegroundColor Cyan } function Warn ($m) { Write-Host "`n[HGV] $m" -ForegroundColor Yellow } function Die ($m) { Write-Host "`n[HGV ERROR] $m" -ForegroundColor Red; exit 1 } function Have ($c) { return [bool](Get-Command $c -ErrorAction SilentlyContinue) } # --- must be elevated (installing software + 'tailscale up' need admin) --- $isAdmin = ([Security.Principal.WindowsPrincipal] ` [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( [Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $isAdmin) { Die 'Please run this in an ADMINISTRATOR PowerShell: right-click Windows PowerShell and choose "Run as administrator", then run the command again.' } # --- installer helper: winget first, Chocolatey fallback --- function Install-Pkg ($winId, $chocoId, $friendly) { if (Have winget) { Log "Installing $friendly (winget)..." winget install --id $winId --silent --accept-source-agreements --accept-package-agreements if ($LASTEXITCODE -eq 0) { return } Warn "winget could not install $friendly; trying Chocolatey..." } if (Have choco) { Log "Installing $friendly (Chocolatey)..." choco install $chocoId -y if ($LASTEXITCODE -eq 0) { return } } Die "Could not install $friendly automatically. Reply to your welcome email and IT will help." } # --- 1 + 2: Tailscale and Linphone --- if (Have tailscale) { Log 'Tailscale is already installed.' } else { Install-Pkg 'Tailscale.Tailscale' 'tailscale' 'the Tailscale VPN' } # refresh PATH so 'tailscale' is found in this same session after install $env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User') $linphoneInstalled = (Have linphone) -or (Test-Path "$env:ProgramFiles\Linphone\linphone.exe") -or (Test-Path "${env:ProgramFiles(x86)}\Linphone\linphone.exe") if ($linphoneInstalled) { Log 'Linphone is already installed.' } else { Install-Pkg 'BelledonneCommunications.Linphone' 'linphone' 'the Linphone phone app' } # --- 3: join the tailnet --- $joined = $false try { $status = & tailscale status --json 2>$null | ConvertFrom-Json if ($status.Self.DNSName -and $status.ControlURL -eq $LoginServer) { $joined = $true } } catch { $joined = $false } if ($joined) { Log 'This computer is already connected to the HGVSupport VPN.' } else { Log 'Connecting to the HGVSupport VPN. A browser window will open - sign in with your username,' Log 'password, and the 6-digit code from your authenticator app.' & tailscale up --login-server=$LoginServer --accept-routes if ($LASTEXITCODE -ne 0) { Die 'Could not connect to the VPN. Finish the browser sign-in, then run this installer again.' } Log 'VPN connected.' } # --- 4: configure Linphone from the welcome-email values --- $ext = $env:HGV_EXT if (-not $ext) { $ext = Read-Host 'Enter your EXTENSION number (from your welcome email)' } if ($ext -notmatch '^[0-9]{2,6}$') { Die 'That extension does not look right (expected digits, e.g. 1004). Check your welcome email and re-run.' } $secure = $null if ($env:HGV_SIP_SECRET) { $secretPlain = $env:HGV_SIP_SECRET } else { $secure = Read-Host 'Enter your PHONE (SIP) PASSWORD from the email (hidden)' -AsSecureString $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) $secretPlain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } if (-not $secretPlain) { Die 'No phone password entered. Re-run and paste the SIP password from your welcome email.' } $rcDir = Join-Path $env:APPDATA 'linphone' New-Item -ItemType Directory -Force -Path $rcDir | Out-Null $rcFile = Join-Path $rcDir 'linphonerc' if (Test-Path $rcFile) { Copy-Item $rcFile "$rcFile.hgv-backup.$([int](Get-Date -UFormat %s))" -Force Warn 'An existing Linphone config was backed up before writing the new one.' } $rc = @" [sip] default_proxy=0 ping_with_options=0 [auth_info_0] username=$ext userid=$ext passwd=$secretPlain domain=$SipServer realm=asterisk [proxy_0] reg_proxy= reg_identity=sip:$ext@$SipServer reg_expires=600 reg_sendregister=1 publish=0 "@ # UTF-8 without BOM so Linphone parses the first section header correctly. [System.IO.File]::WriteAllText($rcFile, $rc, (New-Object System.Text.UTF8Encoding($false))) $secretPlain = $null Log "Linphone is configured for extension $ext. If Linphone was already open, close and reopen it." Log 'All done. Open Linphone - it should show "Connected/Registered". Reply to your welcome email for help.'