<# .SYNOPSIS Restore ClaudeTools secrets + machine identity from a recovery bundle (produced by the Windows bootstrap backup) back to their real locations. .DESCRIPTION Two restore groups: [home] -> out-of-repo secrets that live under the user profile (SOPS age key, SSH keys, Claude/grok/gemini auth, git config, PowerShell profile). These are needed BEFORE cloning repos. [repo] -> repo-local, gitignored files that go back into D:\claudetools (identity.json, settings.local.json, current-mode, .mcp.json, .claude/state, ticktick tokens, dataforth oauth). These require the claudetools repo to already be cloned. Idempotent. Only restores files that exist in the bundle. Never overwrites a newer file unless -Force is given. .PARAMETER BundlePath Path to the recovery bundle root (the folder containing 'secrets' and 'identity'). Auto-detected from F:\ then E:\ if not supplied. .PARAMETER ClaudeToolsRoot Where claudetools is / will be cloned. Default D:\claudetools. .PARAMETER Group home | repo | all (default all). .EXAMPLE .\restore-secrets.ps1 -Group home # before cloning repos .\restore-secrets.ps1 -Group repo # after cloning claudetools #> [CmdletBinding()] param( [string]$BundlePath, [string]$ClaudeToolsRoot = 'D:\claudetools', [ValidateSet('home','repo','all')][string]$Group = 'all', [switch]$Force ) $ErrorActionPreference = 'Stop' function Find-Bundle { foreach ($d in 'F:','E:','D:') { $p = "$d\claudetools-recovery" if (Test-Path "$p\secrets") { return $p } } return $null } if (-not $BundlePath) { $BundlePath = Find-Bundle } if (-not $BundlePath -or -not (Test-Path "$BundlePath\secrets")) { throw "Recovery bundle not found. Plug in the drive or pass -BundlePath. Looked for :\claudetools-recovery\secrets" } Write-Host "[INFO] Using recovery bundle: $BundlePath" -ForegroundColor Cyan function Restore-One($src, $dst) { if (-not (Test-Path -LiteralPath $src)) { Write-Host "[SKIP] not in bundle: $src"; return } $parent = Split-Path $dst -Parent if ($parent -and -not (Test-Path $parent)) { New-Item -ItemType Directory -Force -Path $parent | Out-Null } if ((Test-Path -LiteralPath $dst) -and -not $Force) { Write-Host "[KEEP] exists (use -Force to overwrite): $dst" -ForegroundColor Yellow return } Copy-Item -LiteralPath $src -Destination $dst -Force Write-Host "[OK] $dst" -ForegroundColor Green } # ---------------------------------------------------------------- HOME secrets if ($Group -in 'home','all') { Write-Host "`n=== Restoring home-profile secrets ===" -ForegroundColor Cyan $u = $env:USERPROFILE $s = "$BundlePath\secrets" # SOPS age key (CRITICAL - vault is undecryptable without it) New-Item -ItemType Directory -Force -Path "$u\.config\sops\age" | Out-Null New-Item -ItemType Directory -Force -Path "$env:APPDATA\sops\age" | Out-Null Restore-One "$s\sops-age\keys.txt" "$u\.config\sops\age\keys.txt" Restore-One "$s\sops-age\keys.txt" "$env:APPDATA\sops\age\keys.txt" # SSH New-Item -ItemType Directory -Force -Path "$u\.ssh" | Out-Null if (Test-Path "$s\ssh") { Get-ChildItem "$s\ssh" -File | ForEach-Object { Restore-One $_.FullName "$u\.ssh\$($_.Name)" } # lock down private key perms (remove inheritance, owner-only) Get-ChildItem "$u\.ssh" -File | Where-Object { $_.Name -notmatch '\.pub$' -and $_.Name -ne 'known_hosts' -and $_.Name -ne 'config' } | ForEach-Object { icacls $_.FullName /inheritance:r /grant:r "$($env:USERNAME):(F)" 2>$null | Out-Null } } # Claude Code auth/config Restore-One "$s\claude\.claude.json" "$u\.claude.json" Restore-One "$s\claude\.credentials.json" "$u\.claude\.credentials.json" Restore-One "$s\claude\settings.json" "$u\.claude\settings.json" Restore-One "$s\claude\keybindings.json" "$u\.claude\keybindings.json" Restore-One "$s\claude\statusline-command.sh" "$u\.claude\statusline-command.sh" # grok Restore-One "$s\grok\auth.json" "$u\.grok\auth.json" Restore-One "$s\grok\config.toml" "$u\.grok\config.toml" Restore-One "$s\grok\agent_id" "$u\.grok\agent_id" # gemini Restore-One "$s\gemini\oauth_creds.json" "$u\.gemini\oauth_creds.json" Restore-One "$s\gemini\google_accounts.json" "$u\.gemini\google_accounts.json" Restore-One "$s\gemini\settings.json" "$u\.gemini\settings.json" Restore-One "$s\gemini\installation_id" "$u\.gemini\installation_id" # user-global Claude commands + plugins (not in the repo) if (Test-Path "$s\claude-global\commands") { New-Item -ItemType Directory -Force -Path "$u\.claude\commands" | Out-Null Copy-Item "$s\claude-global\commands\*" "$u\.claude\commands\" -Recurse -Force Write-Host "[OK] $u\.claude\commands\*" -ForegroundColor Green } if (Test-Path "$s\claude-global\plugins") { New-Item -ItemType Directory -Force -Path "$u\.claude\plugins" | Out-Null Copy-Item "$s\claude-global\plugins\*" "$u\.claude\plugins\" -Recurse -Force Write-Host "[OK] $u\.claude\plugins\*" -ForegroundColor Green } # git global config Restore-One "$s\git\.gitconfig" "$u\.gitconfig" # PowerShell profile Restore-One "$s\powershell\Microsoft.PowerShell_profile.ps1" $PROFILE } # ---------------------------------------------------------------- REPO-local if ($Group -in 'repo','all') { Write-Host "`n=== Restoring repo-local identity files ===" -ForegroundColor Cyan if (-not (Test-Path $ClaudeToolsRoot)) { Write-Host "[WARN] $ClaudeToolsRoot does not exist yet. Clone the repo first, then re-run with -Group repo." -ForegroundColor Yellow } else { $i = "$BundlePath\identity" Restore-One "$i\identity.json" "$ClaudeToolsRoot\.claude\identity.json" Restore-One "$i\settings.local.json" "$ClaudeToolsRoot\.claude\settings.local.json" Restore-One "$i\current-mode" "$ClaudeToolsRoot\.claude\current-mode" Restore-One "$i\coord-broadcasts-seen" "$ClaudeToolsRoot\.claude\coord-broadcasts-seen" Restore-One "$i\mcp.json" "$ClaudeToolsRoot\.mcp.json" Restore-One "$i\ticktick-tokens.json" "$ClaudeToolsRoot\mcp-servers\ticktick\.tokens.json" Restore-One "$i\dataforth-oauth.txt" "$ClaudeToolsRoot\clients\dataforth\Oauth.txt" if (Test-Path "$i\state") { New-Item -ItemType Directory -Force -Path "$ClaudeToolsRoot\.claude\state" | Out-Null Copy-Item "$i\state\*" "$ClaudeToolsRoot\.claude\state\" -Recurse -Force Write-Host "[OK] $ClaudeToolsRoot\.claude\state\*" -ForegroundColor Green } } } Write-Host "`n[DONE] restore-secrets.ps1 ($Group)" -ForegroundColor Cyan