generate priv/pub keyt

This commit is contained in:
Lenrui committed 2026-11-06 09:41:54 -08:00
1 parent f2c44dbc0d
commit 044de4e730
1 file changed
+222
+222
View File
@@ -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, <name>! You've successfully authenticated' message means it worked."
}
Write-Step "Done"