bdf405e2c1
SSH-ключи и Gitea API до clone Rules; init-cursor копирует из локального $HOME/CursorRules.
197 lines
5.7 KiB
PowerShell
197 lines
5.7 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
Генерирует SSH-ключи и ~/.ssh/config для Git-хостов из local/git-ssh-hosts.conf.
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$ConfigDir = "$HOME\CursorRules\local",
|
|
[string]$HostsConfig = '',
|
|
[switch]$WhatIf
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Write-Step([string]$Message) { Write-Host "[*] $Message" -ForegroundColor Cyan }
|
|
function Write-Ok([string]$Message) { Write-Host "[+] $Message" -ForegroundColor Green }
|
|
function Write-Warn([string]$Message) { Write-Host "[!] $Message" -ForegroundColor Yellow }
|
|
|
|
function Read-KeyValueFile {
|
|
param([string]$Path)
|
|
$data = @{}
|
|
if (-not (Test-Path -LiteralPath $Path)) { return $data }
|
|
Get-Content -LiteralPath $Path | ForEach-Object {
|
|
if ($_ -match '^([^#=]+)=(.*)$') {
|
|
$data[$Matches[1].Trim()] = $Matches[2].Trim()
|
|
}
|
|
}
|
|
return $data
|
|
}
|
|
|
|
function Read-GitSshHostsConfig {
|
|
param([string]$Path)
|
|
|
|
$profiles = @{}
|
|
$hosts = @{}
|
|
$currentProfile = $null
|
|
$currentHost = $null
|
|
|
|
Get-Content -LiteralPath $Path | ForEach-Object {
|
|
$line = $_.Trim()
|
|
if (-not $line -or $line.StartsWith('#')) { return }
|
|
|
|
if ($line -match '^\[profile:(.+)\]$') {
|
|
$currentProfile = $Matches[1]
|
|
$currentHost = $null
|
|
$profiles[$currentProfile] = @{}
|
|
return
|
|
}
|
|
if ($line -match '^\[host:(.+)\]$') {
|
|
$currentHost = $Matches[1]
|
|
$currentProfile = $null
|
|
$hosts[$currentHost] = @{ Name = $currentHost }
|
|
return
|
|
}
|
|
if ($line -match '^([^=]+)=(.*)$') {
|
|
$key = $Matches[1].Trim()
|
|
$value = $Matches[2].Trim()
|
|
if ($currentHost) { $hosts[$currentHost][$key] = $value }
|
|
elseif ($currentProfile) { $profiles[$currentProfile][$key] = $value }
|
|
}
|
|
}
|
|
|
|
return @{ Profiles = $profiles; Hosts = $hosts }
|
|
}
|
|
|
|
function Get-SshConfigBlock {
|
|
param(
|
|
[string]$HostAlias,
|
|
[hashtable]$HostEntry,
|
|
[string]$IdentityFile
|
|
)
|
|
|
|
@(
|
|
"# BEGIN Setup-SshAccess:$HostAlias"
|
|
"Host $HostAlias"
|
|
" HostName $($HostEntry.HostName)"
|
|
" User $($HostEntry.User)"
|
|
" Port $($HostEntry.Port)"
|
|
" IdentityFile $IdentityFile"
|
|
" IdentitiesOnly yes"
|
|
" AddKeysToAgent yes"
|
|
" StrictHostKeyChecking accept-new"
|
|
"# END Setup-SshAccess:$HostAlias"
|
|
) -join "`n"
|
|
}
|
|
|
|
function Update-SshConfig {
|
|
param(
|
|
[string]$ConfigPath,
|
|
[string]$HostAlias,
|
|
[string]$Block
|
|
)
|
|
|
|
$begin = "# BEGIN Setup-SshAccess:$HostAlias"
|
|
$end = "# END Setup-SshAccess:$HostAlias"
|
|
$existing = if (Test-Path -LiteralPath $ConfigPath) {
|
|
Get-Content -LiteralPath $ConfigPath -Raw
|
|
} else {
|
|
''
|
|
}
|
|
|
|
$pattern = "(?ms)^$([regex]::Escape($begin)).*?$([regex]::Escape($end))\r?\n?"
|
|
if ($existing -match $pattern) {
|
|
return ($existing -replace $pattern, ($Block + "`n"))
|
|
}
|
|
|
|
if ($existing -and -not $existing.EndsWith("`n")) {
|
|
$existing += "`n"
|
|
}
|
|
return $existing + $Block + "`n"
|
|
}
|
|
|
|
if (-not $HostsConfig) {
|
|
$HostsConfig = Join-Path $ConfigDir 'git-ssh-hosts.conf'
|
|
}
|
|
if (-not (Test-Path -LiteralPath $HostsConfig)) {
|
|
throw "Config not found: $HostsConfig"
|
|
}
|
|
|
|
$parsed = Read-GitSshHostsConfig -Path $HostsConfig
|
|
$sshDir = Join-Path $HOME '.ssh'
|
|
$sshConfigPath = Join-Path $sshDir 'config'
|
|
|
|
if (-not $WhatIf) {
|
|
New-Item -ItemType Directory -Path $sshDir -Force | Out-Null
|
|
}
|
|
|
|
$configText = if (Test-Path -LiteralPath $sshConfigPath) {
|
|
Get-Content -LiteralPath $sshConfigPath -Raw
|
|
} else {
|
|
''
|
|
}
|
|
|
|
$profileKeys = @{}
|
|
foreach ($profileName in $parsed.Profiles.Keys) {
|
|
$profile = $parsed.Profiles[$profileName]
|
|
$keyName = $profile.KeyName
|
|
if (-not $keyName -or $profileKeys.ContainsKey($keyName)) { continue }
|
|
|
|
$keyPath = Join-Path $sshDir $keyName
|
|
$comment = if ($profile.KeyComment) { $profile.KeyComment } else { $keyName }
|
|
|
|
if (-not (Test-Path -LiteralPath $keyPath)) {
|
|
Write-Step "Generating $keyName"
|
|
if ($WhatIf) {
|
|
Write-Warn "WhatIf: ssh-keygen -t ed25519 -f $keyPath"
|
|
}
|
|
else {
|
|
& ssh-keygen -t ed25519 -f $keyPath -N '""' -C $comment -q
|
|
Write-Ok "key $keyName created"
|
|
}
|
|
}
|
|
else {
|
|
Write-Ok "key $keyName exists"
|
|
}
|
|
|
|
$profileKeys[$keyName] = $profile
|
|
}
|
|
|
|
foreach ($hostAlias in ($parsed.Hosts.Keys | Sort-Object)) {
|
|
$hostEntry = $parsed.Hosts[$hostAlias]
|
|
$profileName = $hostEntry.Profile
|
|
if (-not $profileName -or -not $parsed.Profiles.ContainsKey($profileName)) {
|
|
throw "Host ${hostAlias}: unknown profile $profileName"
|
|
}
|
|
|
|
$profile = $parsed.Profiles[$profileName]
|
|
$identity = "~/.ssh/$($profile.KeyName)"
|
|
$block = Get-SshConfigBlock -HostAlias $hostAlias -HostEntry $hostEntry -IdentityFile $identity
|
|
|
|
if ($WhatIf) {
|
|
Write-Step "WhatIf: ssh config block for $hostAlias"
|
|
continue
|
|
}
|
|
|
|
$configText = Update-SshConfig -ConfigPath $sshConfigPath -HostAlias $hostAlias -Block $block
|
|
Write-Ok "ssh config: $hostAlias"
|
|
}
|
|
|
|
if (-not $WhatIf) {
|
|
Set-Content -LiteralPath $sshConfigPath -Value $configText.TrimEnd() -Encoding UTF8 -NoNewline
|
|
Add-Content -LiteralPath $sshConfigPath -Value '' -Encoding UTF8
|
|
|
|
Get-Service ssh-agent -ErrorAction SilentlyContinue | Where-Object { $_.Status -ne 'Running' } |
|
|
ForEach-Object { Start-Service ssh-agent | Out-Null }
|
|
|
|
foreach ($keyName in $profileKeys.Keys) {
|
|
$keyPath = Join-Path $sshDir $keyName
|
|
if (Test-Path -LiteralPath $keyPath) {
|
|
ssh-add $keyPath 2>$null | Out-Null
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Ok 'SSH setup complete'
|