Files
dotfiles/scripts/import-ssh-keys-to-bitwarden.ps1
2026-07-07 18:28:35 -05:00

146 lines
4.2 KiB
PowerShell

#!/usr/bin/env pwsh
<#
.SYNOPSIS
Imports SSH private keys from ~/.ssh into Bitwarden as Secure Notes.
.DESCRIPTION
Creates a Bitwarden Secure Note for each private SSH key using the naming
convention expected by the dotfiles setup script:
"SSH Key - <filename>"
e.g. "SSH Key - zenbook_tinker", "SSH Key - stevan@freeborn.cloud"
Public keys (.pub files) and non-key files (config, known_hosts) are skipped.
Requires the Bitwarden CLI (bw) to be installed and your vault to be unlocked:
bw login # first time
$env:BW_SESSION = bw unlock --raw # each session
.EXAMPLE
# Unlock Bitwarden, then run the script
$env:BW_SESSION = bw unlock --raw
.\import-ssh-keys-to-bitwarden.ps1
.EXAMPLE
# Dry-run: see what would be imported without creating anything
.\import-ssh-keys-to-bitwarden.ps1 -WhatIf
#>
[CmdletBinding(SupportsShouldProcess)]
param (
[string]$SshDir = (Join-Path $env:USERPROFILE ".ssh")
)
$ErrorActionPreference = "Stop"
# --- Preflight checks ---
if (-not (Get-Command bw -ErrorAction SilentlyContinue)) {
Write-Error "Bitwarden CLI (bw) not found. Install it with: winget install Bitwarden.CLI"
exit 1
}
if (-not $env:BW_SESSION) {
Write-Error "BW_SESSION is not set. Unlock your vault first:`n `$env:BW_SESSION = bw unlock --raw"
exit 1
}
$vaultStatus = bw status 2>$null | ConvertFrom-Json
if ($vaultStatus.status -ne "unlocked") {
Write-Error "Bitwarden vault is not unlocked. Run: `$env:BW_SESSION = bw unlock --raw"
exit 1
}
if (-not (Test-Path $SshDir)) {
Write-Error "SSH directory not found: $SshDir"
exit 1
}
# --- Identify private keys ---
$skipNames = @("config", "known_hosts", "known_hosts.old", "authorized_keys")
$privateKeys = Get-ChildItem $SshDir -File | Where-Object {
$_.Extension -ne ".pub" -and $_.Name -notin $skipNames
}
if ($privateKeys.Count -eq 0) {
Write-Host "No private keys found in $SshDir" -ForegroundColor Yellow
exit 0
}
Write-Host "Found $($privateKeys.Count) private key(s) in $SshDir`n" -ForegroundColor Cyan
# --- Get existing Bitwarden items to avoid duplicates ---
Write-Host "Fetching existing Bitwarden items..." -ForegroundColor DarkGray
$existingItems = bw list items 2>$null | ConvertFrom-Json
$existingNames = $existingItems | ForEach-Object { $_.name }
# --- Import each key ---
$imported = 0
$skipped = 0
$failed = 0
foreach ($keyFile in $privateKeys) {
$itemName = "SSH Key - $($keyFile.Name)"
Write-Host " $($keyFile.Name)" -NoNewline
# Check for duplicate
if ($existingNames -contains $itemName) {
Write-Host " — already in Bitwarden, skipping" -ForegroundColor DarkGray
$skipped++
continue
}
if ($PSCmdlet.ShouldProcess($itemName, "Create Bitwarden Secure Note")) {
try {
$keyContent = Get-Content $keyFile.FullName -Raw -ErrorAction Stop
# Build Bitwarden Secure Note JSON
$item = [ordered]@{
organizationId = $null
collectionIds = @()
folderId = $null
type = 2 # 2 = Secure Note
name = $itemName
notes = $keyContent
favorite = $false
secureNote = @{ type = 0 }
reprompt = 0
}
$json = $item | ConvertTo-Json -Depth 5 -Compress
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($json))
$result = bw create item $encoded 2>&1
if ($LASTEXITCODE -ne 0) {
throw $result
}
Write-Host " — imported" -ForegroundColor Green
$imported++
}
catch {
Write-Host " — FAILED: $_" -ForegroundColor Red
$failed++
}
}
}
# --- Summary ---
Write-Host ""
Write-Host "Done: $imported imported, $skipped already existed, $failed failed" -ForegroundColor Cyan
if ($imported -gt 0) {
Write-Host ""
Write-Host "Syncing vault..." -ForegroundColor DarkGray
bw sync 2>$null | Out-Null
Write-Host "Vault synced." -ForegroundColor DarkGray
}