From 044de4e730e4f28b8ef0989274da7fcd959d435c Mon Sep 17 00:00:00 2001 From: Lenrui Date: Fri, 6 Nov 2026 09:41:54 -0800 Subject: [PATCH] generate priv/pub keyt --- git-ssh-setup.ps1 | 222 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 git-ssh-setup.ps1 diff --git a/git-ssh-setup.ps1 b/git-ssh-setup.ps1 new file mode 100644 index 0000000..14fe684 --- /dev/null +++ b/git-ssh-setup.ps1 @@ -0,0 +1,222 @@ +<# +.SYNOPSIS + Sets up SSH for Git on Windows: creates a key in ~/.ssh, loads it into the + Windows ssh-agent, adds a host entry to ~/.ssh/config, and points Git at + Windows' OpenSSH so pushes (and Git LFS) use the agent instead of asking + for the passphrase every time. + +.DESCRIPTION + Safe to re-run: an existing key is reused (unless -Force), a key already in + the agent is not added again, and an existing Host entry in ~/.ssh/config + is never modified. + + Run it as your normal user, NOT "Run as administrator". It asks for admin + rights (UAC prompt) only if the ssh-agent service needs to be enabled. + +.EXAMPLE + powershell -ExecutionPolicy Bypass -File .\setup-git-ssh.ps1 + +.EXAMPLE + powershell -ExecutionPolicy Bypass -File .\setup-git-ssh.ps1 -Comment "you@example.com" + +.EXAMPLE + powershell -ExecutionPolicy Bypass -File .\setup-git-ssh.ps1 -HostName github.com -KeyName id_ed25519_github +#> +[CmdletBinding()] +param( + # Host to add to ~/.ssh/config and test against. Use -HostName "" to skip both. + [string]$HostName = "tea.lenrui.net", + + # SSH user for the host (Gitea, GitHub and GitLab all use "git"). + [string]$User = "git", + + [ValidateRange(1, 65535)] + [int]$Port = 22, + + # File name of the key inside ~/.ssh. + [ValidatePattern('^[A-Za-z0-9._-]+$')] + [string]$KeyName = "id_ed25519_gitea", + + # Label stored in the key; it shows up in Gitea's key list. + [ValidateNotNullOrEmpty()] + [string]$Comment = "$env:USERNAME@$env:COMPUTERNAME", + + # Replace an existing key with the same name instead of reusing it. + [switch]$Force, + + # Skip the connection test at the end. + [switch]$NoTest +) + +function Write-Step([string]$Message) { Write-Host "`n==> $Message" -ForegroundColor Cyan } +function Write-Ok([string]$Message) { Write-Host " $Message" -ForegroundColor Green } +function Write-Note([string]$Message) { Write-Host " $Message" -ForegroundColor Yellow } +function Stop-Setup([string]$Message) { + Write-Host "`nERROR: $Message" -ForegroundColor Red + exit 1 +} + +$openSshDir = Join-Path $env:WINDIR 'System32\OpenSSH' +$sshExe = Join-Path $openSshDir 'ssh.exe' +$keygenExe = Join-Path $openSshDir 'ssh-keygen.exe' +$addExe = Join-Path $openSshDir 'ssh-add.exe' +$sshDir = Join-Path $HOME '.ssh' +$keyPath = Join-Path $sshDir $KeyName +$pubPath = "$keyPath.pub" +$configPath = Join-Path $sshDir 'config' + +# --- 1. ssh-agent service ---------------------------------------------------- +# Windows ships it disabled. Enabling it (and installing the OpenSSH client if +# it is missing) needs admin, so only that part runs elevated. +Write-Step "Checking the ssh-agent service" +$agent = Get-Service ssh-agent -ErrorAction SilentlyContinue +if (-not $agent -or $agent.StartType -ne 'Automatic' -or $agent.Status -ne 'Running') { + $fixAgent = @' +$ErrorActionPreference = 'Stop' +if (-not (Get-Service ssh-agent -ErrorAction SilentlyContinue)) { + Write-Host 'Installing the Windows OpenSSH client (this can take a few minutes)...' + Add-WindowsCapability -Online -Name 'OpenSSH.Client~~~~0.0.1.0' | Out-Null +} +Set-Service ssh-agent -StartupType Automatic +Start-Service ssh-agent +'@ + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( + [Security.Principal.WindowsBuiltInRole]::Administrator) + + if ($isAdmin) { + try { & ([scriptblock]::Create($fixAgent)) } + catch { Stop-Setup "Could not enable ssh-agent: $($_.Exception.Message)" } + } else { + Write-Note "Enabling ssh-agent needs admin rights - approve the UAC prompt." + $encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($fixAgent)) + try { + Start-Process powershell.exe -Verb RunAs -Wait ` + -ArgumentList "-NoProfile -ExecutionPolicy Bypass -EncodedCommand $encoded" + } catch { + Stop-Setup "The admin prompt was cancelled. Re-run the script and approve it." + } + } + + $agent = Get-Service ssh-agent -ErrorAction SilentlyContinue + if (-not $agent -or $agent.Status -ne 'Running') { + Stop-Setup "ssh-agent still isn't running. In an admin PowerShell run:`n Set-Service ssh-agent -StartupType Automatic; Start-Service ssh-agent" + } +} +Write-Ok "ssh-agent is running (startup type: $($agent.StartType))" + +foreach ($exe in @($sshExe, $keygenExe, $addExe)) { + if (-not (Test-Path $exe)) { Stop-Setup "Windows OpenSSH tool not found: $exe" } +} + +# --- 2. Key ------------------------------------------------------------------ +Write-Step "Preparing the key $keyPath" +New-Item -ItemType Directory -Path $sshDir -Force | Out-Null + +if ((Test-Path $keyPath) -and -not $Force) { + Write-Ok "Reusing the existing key (pass -Force to replace it)." + if (-not (Test-Path $pubPath)) { + Write-Note "Public key file is missing - recreating it from the private key." + $recreated = & $keygenExe -y -f $keyPath + if ($LASTEXITCODE -ne 0 -or -not $recreated) { Stop-Setup "Could not read $keyPath." } + Set-Content -Path $pubPath -Value $recreated -Encoding Ascii + } +} else { + if (Test-Path $keyPath) { + Write-Note "-Force given: replacing the existing key." + Remove-Item -Path $keyPath, $pubPath -Force -ErrorAction SilentlyContinue + } + Write-Note "Choose a passphrase when asked (or press Enter twice for none)." + & $keygenExe -t ed25519 -C $Comment -f $keyPath + if ($LASTEXITCODE -ne 0 -or -not (Test-Path $pubPath)) { Stop-Setup "ssh-keygen failed." } + Write-Ok "Created $keyPath" +} + +# --- 3. Load into the agent -------------------------------------------------- +Write-Step "Adding the key to ssh-agent" +$fingerprint = ((& $keygenExe -lf $pubPath) -split '\s+')[1] +if (-not $fingerprint) { Stop-Setup "Could not read the fingerprint of $pubPath." } + +$loaded = (& $addExe -l) -join "`n" +if ($loaded -match [regex]::Escape($fingerprint)) { + Write-Ok "Already loaded ($fingerprint)." +} else { + & $addExe $keyPath + if ($LASTEXITCODE -ne 0) { Stop-Setup "ssh-add failed." } + Write-Ok "Loaded $fingerprint. Windows keeps it in the agent across reboots." +} + +# --- 4. ~/.ssh/config entry -------------------------------------------------- +if ($HostName) { + Write-Step "Adding '$HostName' to $configPath" + $block = @( + "Host $HostName", + " HostName $HostName", + " User $User", + " Port $Port", + " IdentityFile ~/.ssh/$KeyName", + " IdentitiesOnly yes", + " AddKeysToAgent yes" + ) -join "`n" + + $existing = '' + if (Test-Path $configPath) { + $existing = Get-Content -Path $configPath -Raw + if ($null -eq $existing) { $existing = '' } + } + + $hostPattern = '(?im)^[ \t]*Host[ \t]+([^\r\n]*[ \t])?' + [regex]::Escape($HostName) + '([ \t]|\r?$)' + if ($existing -match $hostPattern) { + Write-Note "A 'Host $HostName' entry already exists, so it was left alone." + Write-Note "Check that it matches this:" + Write-Host "`n$block" + } else { + $prefix = '' + if ($existing.Length -gt 0) { + $prefix = if ($existing.EndsWith("`n")) { "`n" } else { "`n`n" } + } + # Write UTF-8 *without* a BOM; OpenSSH can choke on a BOM in its config. + [IO.File]::AppendAllText($configPath, "$prefix$block`n", (New-Object Text.UTF8Encoding($false))) + Write-Ok "Added." + } +} + +# --- 5. Git ------------------------------------------------------------------ +Write-Step "Configuring Git to use Windows OpenSSH (and its agent)" +if (Get-Command git -ErrorAction SilentlyContinue) { + $sshForGit = $sshExe -replace '\\', '/' + git config --global core.sshCommand $sshForGit + # Works around a git-lfs bug that can make SSH transfers hang. + # Harmless if you never use Git LFS. + git config --global lfs.ssh.automultiplex false + Write-Ok "core.sshCommand = $sshForGit" + Write-Ok "lfs.ssh.automultiplex = false" + if ($env:GIT_SSH_COMMAND) { + Write-Note "GIT_SSH_COMMAND is set ($env:GIT_SSH_COMMAND) and overrides core.sshCommand." + Write-Note "Remove it from your environment variables if pushes still ask for a passphrase." + } +} else { + Write-Note "Git is not on PATH, so Git was not configured. Install Git for Windows and re-run." +} + +# --- 6. Public key ----------------------------------------------------------- +Write-Step "Your public key" +$pub = (Get-Content -Path $pubPath -Raw).Trim() +Write-Host "`n$pub`n" +try { + Set-Clipboard -Value $pub + Write-Ok "Copied to the clipboard." +} catch { + Write-Note "Couldn't copy to the clipboard - copy the line above." +} +Write-Host " Add it in Gitea: your avatar > Settings > SSH / GPG Keys > Add Key" + +# --- 7. Test ----------------------------------------------------------------- +if ($HostName -and -not $NoTest) { + Write-Host "" + Read-Host "Press Enter once the key is added to test the connection (Ctrl+C to skip)" | Out-Null + & $sshExe -T -p $Port "$User@$HostName" + Write-Host "" + Write-Note "A 'Hi there, ! You've successfully authenticated' message means it worked." +} + +Write-Step "Done"