# ===================================================================== # VuePoint Edge — remote-access beachhead (Stage 1) v3 # --------------------------------------------------------------------- # Run ONCE in an *Administrator* PowerShell. Connects this PC to VuePoint's # private network (Tailscale) and enables secure remote management (OpenSSH) # so VuePoint can finish setup remotely. Nothing exposed to the public net. # Idempotent — safe to re-run (skips Tailscale if already connected, so it # does NOT need a fresh key on a re-run). ~2 minutes. # # Usage (VuePoint provides the one-liner incl. the key): # $env:TS_KEY='tskey-...'; irm https://edge.vuepointsecure.com/edge-connect.ps1 | iex # ===================================================================== $ErrorActionPreference = 'Continue' function Info($m){ Write-Host "[vuepoint] $m" -ForegroundColor Cyan } function Warn($m){ Write-Host "[vuepoint] $m" -ForegroundColor Yellow } # 0) must be admin if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Warn "Please run in an ADMINISTRATOR PowerShell (Start > 'powershell' > right-click > Run as administrator), then paste again."; return } # 1) Tailscale — install + connect (skip 'up' if already connected → no fresh key needed on re-run) $ts = "C:\Program Files\Tailscale\tailscale.exe" if (-not (Test-Path $ts)) { Info "Installing Tailscale..." $msi = Join-Path $env:TEMP 'tailscale-setup.msi' Invoke-WebRequest -Uri 'https://pkgs.tailscale.com/stable/tailscale-setup-latest-amd64.msi' -OutFile $msi -UseBasicParsing Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /quiet /norestart" -Wait Start-Sleep -Seconds 5 } $connected = $false try { $connected = ((& $ts status --json | ConvertFrom-Json).BackendState -eq 'Running') } catch {} if ($connected) { Info "Tailscale already connected — skipping (no key needed)." } elseif ($env:TS_KEY) { Info "Joining VuePoint's private network..." & $ts up --authkey $env:TS_KEY --unattended --hostname ("vuepoint-" + $env:COMPUTERNAME) --accept-dns=false } else { Warn "Tailscale not connected and no TS_KEY provided — use the one-line command VuePoint gave you."; return } Start-Sleep -Seconds 2 $tsip = (& $ts ip -4 2>$null | Select-Object -First 1) # 2) OpenSSH server — install ROBUSTLY (wait for capability, then start), firewall on all profiles Info "Enabling secure remote management (OpenSSH)..." $cap = Get-WindowsCapability -Online -Name 'OpenSSH.Server*' | Select-Object -First 1 if ($cap.State -ne 'Installed') { Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null # wait for the service to be registered for ($i=0; $i -lt 20 -and -not (Get-Service sshd -ErrorAction SilentlyContinue); $i++) { Start-Sleep 2 } } Set-Service -Name sshd -StartupType Automatic -ErrorAction SilentlyContinue Set-Service -Name ssh-agent -StartupType Automatic -ErrorAction SilentlyContinue for ($i=0; $i -lt 5; $i++) { Start-Service sshd -ErrorAction SilentlyContinue; if ((Get-Service sshd -ErrorAction SilentlyContinue).Status -eq 'Running') { break }; Start-Sleep 2 } # firewall: enable the capability's rule + our own, all profiles Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue | Enable-NetFirewallRule -ErrorAction SilentlyContinue if (-not (Get-NetFirewallRule -Name 'vuepoint-sshd' -ErrorAction SilentlyContinue)) { New-NetFirewallRule -Name 'vuepoint-sshd' -DisplayName 'OpenSSH Server (VuePoint)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 -Profile Any | Out-Null } # make the Tailscale adapter a Private network (belt-and-suspenders) Get-NetConnectionProfile | Where-Object { $_.InterfaceAlias -like 'Tailscale*' } | Set-NetConnectionProfile -NetworkCategory Private -ErrorAction SilentlyContinue # 3) Deterministic admin account 'vuepoint' — so VuePoint always logs in as vuepoint@ $user = 'vuepoint' try { if (-not (Get-LocalUser -Name $user -ErrorAction SilentlyContinue)) { $pw = ConvertTo-SecureString ([guid]::NewGuid().ToString() + 'Aa1!') -AsPlainText -Force New-LocalUser -Name $user -Password $pw -FullName 'VuePoint' -Description 'VuePoint remote management' -AccountNeverExpires -PasswordNeverExpires | Out-Null } Add-LocalGroupMember -Group 'Administrators' -Member $user -ErrorAction SilentlyContinue Info "Admin account '$user' ready." } catch { Warn "Could not create '$user' account: $($_.Exception.Message)" } # 4) Install VuePoint's management key — admins_authorized_keys (correct ACL, no BOM) $pub = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDOOecLl4WySU6dq2+Fzae7V8oVStmPFzQp8vOnbIszZ vuepoint-platform' $sshDir = 'C:\ProgramData\ssh' if (-not (Test-Path $sshDir)) { New-Item -ItemType Directory -Path $sshDir -Force | Out-Null } $aak = Join-Path $sshDir 'administrators_authorized_keys' [System.IO.File]::WriteAllText($aak, $pub + "`n") icacls $aak /inheritance:r /grant 'SYSTEM:F' /grant 'BUILTIN\Administrators:F' | Out-Null New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' -PropertyType String -Force -ErrorAction SilentlyContinue | Out-Null Restart-Service sshd -ErrorAction SilentlyContinue # 5) Keep the edge awake — an unattended edge must NEVER sleep/hibernate # (if it sleeps, every remote path drops at once and we lose it until a physical wake). Info "Disabling sleep / hibernate (unattended edge)..." powercfg /change standby-timeout-ac 0 2>$null powercfg /change standby-timeout-dc 0 2>$null powercfg /change hibernate-timeout-ac 0 2>$null powercfg /change hibernate-timeout-dc 0 2>$null powercfg /change monitor-timeout-ac 0 2>$null powercfg /change disk-timeout-ac 0 2>$null powercfg /hibernate off 2>$null # system unattended-sleep timeout -> never powercfg /setacvalueindex SCHEME_CURRENT 238C9FA8-0AAD-41ED-83F4-97BE242C8F20 7bc4a2f9-d8fc-4469-b07b-33eb785aaca0 0 2>$null # lid close -> do nothing (covers laptop/NUC form factors; harmless on desktops) powercfg /setacvalueindex SCHEME_CURRENT 4f971e89-eebd-4455-a8de-9e59040e7347 5ca83367-6e45-459f-a27b-476b1d01c936 0 2>$null powercfg /setdcvalueindex SCHEME_CURRENT 4f971e89-eebd-4455-a8de-9e59040e7347 5ca83367-6e45-459f-a27b-476b1d01c936 0 2>$null powercfg /SetActive SCHEME_CURRENT 2>$null # 6) report $sshOk = (Get-Service sshd -ErrorAction SilentlyContinue).Status Write-Host "" Info "===================== DONE =====================" Info (" Tailscale IP : " + $tsip) Info (" Name : vuepoint-" + $env:COMPUTERNAME) Info (" SSH service : " + $sshOk + " (login: vuepoint@" + $tsip + ")") Info "VuePoint will finish the setup remotely. You can close this window." Info "==============================================="