#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'