generate priv/pub keyt
This commit is contained in:
1 parent
f2c44dbc0d
commit
044de4e730
1 file changed
+222
@@ -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"
|
||||||
Reference in new issue
Block a user