# setup-ssh.ps1 — OpenSSH server + GitHub keys + Cloudflare quick tunnel # Run in an ADMIN PowerShell: irm | iex $ErrorActionPreference = 'Stop' if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators')) { Write-Host 'Run this from an Administrator PowerShell.' -ForegroundColor Red; return } # 1. OpenSSH Server $cap = Get-WindowsCapability -Online -Name 'OpenSSH.Server*' if ($cap.State -ne 'Installed') { Write-Host 'Installing OpenSSH Server (takes a minute)...' Add-WindowsCapability -Online -Name $cap.Name | Out-Null } Set-Service sshd -StartupType Automatic Start-Service sshd # first start generates host keys + sshd_config # 2. karch's public key $keys = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF5gIGKlCGK3TNXkY9Fs6NjHGpzsd9uyHF2zPxse3a9w karch' # Admin accounts use this file (Windows quirk), with strict permissions $adminKeys = "$env:ProgramData\ssh\administrators_authorized_keys" Set-Content -Path $adminKeys -Value $keys -Encoding ascii icacls $adminKeys /inheritance:r /grant '*S-1-5-32-544:F' /grant '*S-1-5-18:F' | Out-Null # Non-admin accounts use the normal per-user file $userSsh = "$env:USERPROFILE\.ssh" New-Item -ItemType Directory -Force $userSsh | Out-Null Set-Content -Path "$userSsh\authorized_keys" -Value $keys -Encoding ascii # 3. Keys only (no passwords), PowerShell as default shell $cfg = "$env:ProgramData\ssh\sshd_config" (Get-Content $cfg) -replace '^#?\s*PasswordAuthentication\s+\w+', 'PasswordAuthentication no' | Set-Content $cfg -Encoding ascii New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell ` -Value "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force | Out-Null Restart-Service sshd # 4. cloudflared quick tunnel (no account needed) $cf = "$env:ProgramData\cloudflared\cloudflared.exe" if (-not (Test-Path $cf)) { Write-Host 'Downloading cloudflared...' New-Item -ItemType Directory -Force (Split-Path $cf) | Out-Null Invoke-WebRequest 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe' -OutFile $cf -UseBasicParsing } Get-Process cloudflared -ErrorAction SilentlyContinue | Stop-Process -Force $log = "$env:ProgramData\cloudflared\tunnel.log" Remove-Item $log -ErrorAction SilentlyContinue Start-Process $cf -WindowStyle Hidden -ArgumentList 'tunnel', '--no-autoupdate', '--logfile', $log, '--url', 'ssh://localhost:22' Write-Host 'Waiting for tunnel URL...' $url = $null for ($i = 0; $i -lt 60 -and -not $url; $i++) { Start-Sleep 1 if (Test-Path $log) { $fs = [IO.File]::Open($log, 'Open', 'Read', 'ReadWrite') $txt = (New-Object IO.StreamReader($fs)).ReadToEnd(); $fs.Close() if ($txt -match 'https://[a-z0-9-]+\.trycloudflare\.com') { $url = $Matches[0] } } } if (-not $url) { Write-Host "No URL yet — check $log" -ForegroundColor Red; return } $hostName = ([uri]$url).Host Write-Host '' Write-Host " $hostName" -ForegroundColor Green Write-Host " ssh $env:USERNAME@$hostName" -ForegroundColor Cyan Write-Host ''