# Backup Browser read-only helper for Windows PowerShell 5.1 and PowerShell 7. # The access key is kept only in this process. Do not put it in command history. $script:BackupApiBase = 'https://s3.itcode.team' $script:BackupApiHeaders = $null function Connect-BackupApi { [CmdletBinding()] param( [string]$BaseUrl = 'https://s3.itcode.team', [switch]$ForChildProcesses ) if ($BaseUrl.TrimEnd('/') -ne 'https://s3.itcode.team') { throw 'Only https://s3.itcode.team is allowed.' } $secure = Read-Host 'Temporary Backup Browser agent key' -AsSecureString $pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) try { $plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer) $script:BackupApiHeaders = @{ Authorization = "Bearer $plain" } $script:BackupApiBase = $BaseUrl.TrimEnd('/') $null = Invoke-RestMethod -Uri "$script:BackupApiBase/api/v1/catalog" -Headers $script:BackupApiHeaders -Method Get if ($ForChildProcesses) { $env:BACKUP_API_BASE = $script:BackupApiBase $env:BACKUP_API_TOKEN = $plain } } finally { if ($null -ne $plain) { $plain = $null } [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer) } Write-Host 'Connected to the read-only Backup API.' } function Grant-BackupAccessToCodexApp { [CmdletBinding()] param() try { Connect-BackupApi $path = Save-BackupAgentAccess } catch { Disconnect-BackupApi throw } Disconnect-BackupApi Write-Host '' Write-Host 'READY_FOR_CODEX_APP' Write-Host "Windows saved the temporary key in an encrypted file for this user: $path" Write-Host 'This window will close now. The waiting agent will continue automatically.' } function Enable-BackupApiForChildProcesses { Assert-BackupApiConnected $env:BACKUP_API_BASE = $script:BackupApiBase $env:BACKUP_API_TOKEN = $script:BackupApiHeaders.Authorization.Substring(7) Write-Host 'Child processes started from this PowerShell window can now use the temporary key.' } function Disconnect-BackupApi { $script:BackupApiHeaders = $null Remove-Item Env:BACKUP_API_BASE -ErrorAction SilentlyContinue Remove-Item Env:BACKUP_API_TOKEN -ErrorAction SilentlyContinue Write-Host 'The in-memory Backup API access has been cleared from this process.' } function Get-DefaultBackupAgentAccessPath { Join-Path $env:LOCALAPPDATA 'BackupBrowser\agent-access.json' } function Save-BackupAgentAccess { [CmdletBinding()] param() Assert-BackupApiConnected if ($env:OS -ne 'Windows_NT') { throw 'DPAPI agent access files are supported only on Windows.' } $Path = Get-DefaultBackupAgentAccessPath $directory = Split-Path -Parent $Path $null = New-Item -ItemType Directory -Path $directory -Force $raw = $script:BackupApiHeaders.Authorization.Substring(7) try { $secure = ConvertTo-SecureString -String $raw -AsPlainText -Force $protectedToken = ConvertFrom-SecureString -SecureString $secure [ordered]@{ version = 1 baseUrl = 'https://s3.itcode.team' protectedToken = $protectedToken createdAt = [DateTime]::UtcNow.ToString('o') } | ConvertTo-Json | Set-Content -LiteralPath $Path -Encoding UTF8 } finally { $raw = $null } Write-Output (Resolve-Path -LiteralPath $Path).Path } function Import-BackupAgentAccess { [CmdletBinding()] param() $Path = Get-DefaultBackupAgentAccessPath if ($env:BACKUP_API_TOKEN) { if (-not $env:BACKUP_API_TOKEN.StartsWith('bba_') -or $env:BACKUP_API_TOKEN.Length -ne 47) { throw 'BACKUP_API_TOKEN has an invalid format.' } $script:BackupApiBase = 'https://s3.itcode.team' $script:BackupApiHeaders = @{ Authorization = "Bearer $env:BACKUP_API_TOKEN" } } else { if ($env:OS -ne 'Windows_NT') { throw 'The DPAPI access file can be opened only by the Windows user that created it.' } $record = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json if ($record.version -ne 1 -or $record.baseUrl -ne 'https://s3.itcode.team') { throw 'The agent access file is invalid.' } $secure = ConvertTo-SecureString -String $record.protectedToken $pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) try { $plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer) if (-not $plain.StartsWith('bba_') -or $plain.Length -ne 47) { throw 'The protected agent key has an invalid format.' } $script:BackupApiBase = 'https://s3.itcode.team' $script:BackupApiHeaders = @{ Authorization = "Bearer $plain" } } finally { if ($null -ne $plain) { $plain = $null } [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer) } } $null = Get-BackupCatalog Write-Host 'Agent access imported and verified.' } function Remove-BackupAgentAccess { [CmdletBinding()] param() $Path = Get-DefaultBackupAgentAccessPath if (Test-Path -LiteralPath $Path) { Remove-Item -LiteralPath $Path -Force } Disconnect-BackupApi } function Assert-BackupApiConnected { if ($null -eq $script:BackupApiHeaders) { throw 'Run Connect-BackupApi first.' } } function Get-BackupCatalog { Assert-BackupApiConnected Invoke-RestMethod -Uri "$script:BackupApiBase/api/v1/catalog" -Headers $script:BackupApiHeaders -Method Get } function Get-BackupSnapshots { [CmdletBinding()] param([Parameter(Mandatory)][ValidatePattern('^[a-z0-9-]+$')][string]$Server) Assert-BackupApiConnected Invoke-RestMethod -Uri "$script:BackupApiBase/api/v1/repositories/$Server/snapshots" -Headers $script:BackupApiHeaders -Method Get } function Get-BackupEntries { [CmdletBinding()] param( [Parameter(Mandatory)][ValidatePattern('^[a-z0-9-]+$')][string]$Server, [string]$Snapshot = 'latest', [string]$Path = '/' ) Assert-BackupApiConnected $escapedPath = [Uri]::EscapeDataString($Path) Invoke-RestMethod -Uri "$script:BackupApiBase/api/v1/repositories/$Server/snapshots/$Snapshot/entries?path=$escapedPath" -Headers $script:BackupApiHeaders -Method Get } function Save-BackupItem { [CmdletBinding()] param( [Parameter(Mandatory)][ValidatePattern('^[a-z0-9-]+$')][string]$Server, [Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][string]$OutFile, [string]$Snapshot = 'latest' ) Assert-BackupApiConnected $escapedPath = [Uri]::EscapeDataString($Path) Invoke-WebRequest -UseBasicParsing -Uri "$script:BackupApiBase/api/v1/repositories/$Server/snapshots/$Snapshot/download?path=$escapedPath" -Headers $script:BackupApiHeaders -OutFile $OutFile Get-Item -LiteralPath $OutFile } function Get-BackupInstruction { [CmdletBinding()] param( [Parameter(Mandatory)][ValidatePattern('^[A-Za-z0-9][A-Za-z0-9._-]{0,126}\.(md|txt|json)$')][string]$Name, [string]$OutFile ) Assert-BackupApiConnected $uri = "$script:BackupApiBase/api/v1/instructions/$Name" if ($OutFile) { Invoke-WebRequest -UseBasicParsing -Uri "$uri?download=1" -Headers $script:BackupApiHeaders -OutFile $OutFile return Get-Item -LiteralPath $OutFile } (Invoke-WebRequest -UseBasicParsing -Uri $uri -Headers $script:BackupApiHeaders).Content }