Instantly share code, notes, and snippets.
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save kitsumed/65effded3297e152d972907f8c1a8b75 to your computer and use it in GitHub Desktop.
Restic Backup Helper In Powershell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # PowerShell script for ImDisk and Restic setup (process-scoped TEMP/TMP for RAM DISK APPS) with menu. Quickly made with help of AI once I realised that on Windows restic actually write a lot of GB in %temp% to disk, causing wear on HDD/SSD. | |
| # NOTE: With default restic config, around 400/500MB of RAM DISK should be enough, up to 1GB if there are issues. | |
| <# | |
| Copyright 2025 Kitsumed (Med) | |
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| #> | |
| # Function to write colored text | |
| function Write-Color { | |
| param ( | |
| [string]$Text, | |
| [ConsoleColor]$Color = "White" | |
| ) | |
| $currentColor = $Host.UI.RawUI.ForegroundColor | |
| $Host.UI.RawUI.ForegroundColor = $Color | |
| Write-Host $Text | |
| $Host.UI.RawUI.ForegroundColor = $currentColor | |
| } | |
| # --- Check for ImDisk --- | |
| try { | |
| $imdiskVer = imdisk --version 2>$null # Suppress error output, do not use imdiskVer as it is probably empty, or contains the full GPL license | |
| if ($LASTEXITCODE -eq 0) { | |
| Write-Color "✅ ImDisk detected on your system." Green | |
| Write-Color "It is recommended to use a RAM Disk of around 500MB or more to reduce HDD/SSD wear when handling temporary data." Cyan | |
| Start-Sleep -Seconds 2 | |
| } | |
| else { | |
| throw "ImDisk not found" | |
| } | |
| } | |
| catch { | |
| Write-Color "⚠️ ImDisk was not detected." Red | |
| Write-Color "Consider installing ImDisk (https://github.com/DavidXanatos/ImDisk) or using another RAM Disk application." Yellow | |
| Write-Color "This helps reduce HDD/SSD wear since %TEMP% and %TMP% are often heavily written and deleted." Cyan | |
| Write-Color "Waiting 10 seconds before continuing..." Magenta | |
| Start-Sleep -Seconds 10 | |
| } | |
| # --- If ImDisk detected, ask for Drive letter/path --- | |
| if ($LASTEXITCODE -eq 0) { | |
| $ramTEMPPath = Read-Host "Enter the full path to use for %TEMP% files (e.g. R:\TEMP). Leave empty to use system %TEMP%" | |
| if ([string]::IsNullOrWhiteSpace($ramTEMPPath)) { | |
| Write-Color "`nNo input detected. Using system TEMP: $env:TEMP" Cyan | |
| } | |
| else { | |
| if (-not (Test-Path $ramTEMPPath)) { | |
| Write-Color "`nRAM disk path not found. Creating: $ramTEMPPath" Yellow | |
| try { | |
| New-Item -ItemType Directory -Path $ramTEMPPath -Force | Out-Null | |
| Write-Color "Successfully created $ramTEMPPath" Green | |
| } catch { | |
| Write-Color "❌ Failed to create RAM disk directory. Check permissions or path validity." Red | |
| } | |
| } | |
| $env:TEMP = $ramTEMPPath | |
| $env:TMP = $ramTEMPPath | |
| Write-Color "`n%TEMP% and %TMP% updated for this process to: $ramTEMPPath" Green | |
| } | |
| } | |
| # --- Check for Restic in current directory --- | |
| $resticPath = Join-Path -Path (Get-Location) -ChildPath "restic.exe" | |
| if (Test-Path $resticPath) { | |
| Write-Color "`n✅ Restic found in current directory." Green | |
| # Prompt for repository, default to current directory if Enter pressed | |
| $repoPath = Read-Host "Enter the path to your Restic repository (Empty to use current directory)" | |
| if ([string]::IsNullOrWhiteSpace($repoPath)) { | |
| $repoPath = (Get-Location).Path | |
| Write-Color "No input detected. Using current directory as repository: $repoPath" Cyan | |
| } | |
| else { | |
| Write-Color "Repository set to: $repoPath" Cyan | |
| } | |
| } | |
| else { | |
| Write-Color "`n❌ restic.exe not found in current directory!" Red | |
| Write-Color "Please download it from https://github.com/restic/restic and place it here." Yellow | |
| exit 1 | |
| } | |
| Write-Color "`nSetup complete." Green | |
| # --- Menu --- | |
| function Show-Menu { | |
| Clear-Host | |
| Write-Color "======================================" Cyan | |
| Write-Color " Restic Backup Menu " Yellow | |
| Write-Color "======================================" Cyan | |
| Write-Color "1. Show instructions / Help" Green | |
| Write-Color "2. Perform a Health Check of Snapshots" Green | |
| Write-Color "3. Create a New Snapshot" Green | |
| Write-Color "4. Clean leftover files (Forget & Purge)" Green | |
| Write-Color "5. List Snapshots" Green | |
| Write-Color "6. Browse Snapshots Files" Green | |
| Write-Color "7. Restore Snapshot" Green | |
| Write-Color "8. Exit" Green | |
| Write-Color "======================================" Cyan | |
| } | |
| do { | |
| Show-Menu | |
| $choice = Read-Host "Select an option [1-8]" | |
| switch ($choice) { | |
| '1' { | |
| Write-Color "`n📘 Instructions / Help:" Yellow | |
| Write-Color "- Start by performing a basic health check (Choice 2, then 1)." Cyan | |
| Write-Color "- Backup your files (Choice 3)." Cyan | |
| Write-Color "- You can perform another basic health check or a Reading Data check (to ensure blobs files are not corrupted and still matches saved hashes)." Cyan | |
| Write-Color "- Clean leftover blobs files and old snapshots (Choice 4)." Cyan | |
| Write-Color "`n📘 Recommendations:" Yellow | |
| Write-Color "* Use a RAM Disk of around 500MB+ for %TEMP% files to reduce HDD/SSD wear." Cyan | |
| Write-Color "`n📘 Restic for Beginners:" Yellow | |
| Write-Color "* Restic stores your backups as `"snapshots`" inside a `"repository`"." Cyan | |
| Write-Color "* Each snapshot represents the state of your files at a specific point in time." Cyan | |
| Write-Color "* Restic uses `"deduplication`" to save space — it splits files into small `"blobs`" (data chunks) and only stores unique blobs once." Cyan | |
| Write-Color "* When a file hasn't changed since the last backup, Restic reuses the same blob references instead of saving the file again." Cyan | |
| Write-Color "* This makes backups fast and keeps your repository from growing too large." Cyan | |
| Write-Color "* When cleaning old data (Forget & Purge), Restic first removes outdated snapshots according to a `"retention policy`"," Cyan | |
| Write-Color " then deletes any blob files that are no longer referenced by any remaining snapshot." Cyan | |
| Write-Color "* This script uses the following retention policy:" Cyan | |
| Write-Color " - Keep 12 monthly snapshots (one per month for the past year)" Cyan | |
| Write-Color " - Keep 1 yearly snapshot (a long-term backup for the last year)" Cyan | |
| Write-Color "* By default, snapshots are grouped using two keys:" Cyan | |
| Write-Color " - The name of the computer that created the backup (`host`)" Cyan | |
| Write-Color " - The full path of the directory being backed up (`paths`)" Cyan | |
| Write-Color "* This ensures that different computers, or different folders on the same machine," Cyan | |
| Write-Color " each maintain their own independent snapshot history and cleanup cycle." Cyan | |
| Write-Color " Note that even if snapshots are separate, Restic still reuses existing `"blobs`" whenever possible." Cyan | |
| Write-Color " This means that if you move a directory to a new location and back it up again," Cyan | |
| Write-Color " Restic will show the snapshot as independent but WILL NOT duplicate the data - it will detect that the file contents are the same," Cyan | |
| Write-Color " and simply reference the already existing blobs instead of re-copying them." Cyan | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| '2' { | |
| Write-Color "`n🔍 Perform a Health Check" Yellow | |
| Write-Color "--------------------------------------" Cyan | |
| Write-Color "1. Basic Health Check (fast, minimal writes)" Green | |
| Write-Color "2. Check by Reading Data (more intensive, reads backup data)" Green | |
| Write-Color "*. Press anything else to return to main menu" Green | |
| Write-Color "--------------------------------------" Cyan | |
| $subChoice = Read-Host "Select an option [1-3]" | |
| switch ($subChoice) { | |
| '1' { | |
| # Basic Health Check | |
| $cmd = "`"$resticPath`" --repo `"$repoPath`" check" | |
| Write-Color "`n🧾 Command preview:" Yellow | |
| Write-Color $cmd Cyan | |
| $confirm = Read-Host "Run this command now? (y/n)" | |
| if ($confirm -match '^[Yy]$') { | |
| Write-Color "`nRunning Basic Health Check..." Green | |
| & "$resticPath" --repo "$repoPath" check | |
| } | |
| else { | |
| Write-Color "Skipped Basic Health Check." Yellow | |
| } | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| '2' { | |
| # Check if ImDisk was detected and show warning if not available | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Color "`n⚠️ Warning: ImDisk was not detected earlier." Red | |
| Write-Color "Running a read-data check will cause A LOT of WRITE operations on your OS %TEMP% disk. We recommend setting up a RAM DISK." Yellow | |
| $cont = Read-Host "Are you sure you want to continue? (y/n)" | |
| if ($cont -notmatch '^[Yy]$') { | |
| Write-Color "Cancelled by user." Yellow | |
| break | |
| } | |
| } | |
| # Ask user for percentage of data to read | |
| $inputPercent = Read-Host "Enter the percentage of data to read (1–100, press Enter for 100%)" | |
| $percent = 1 | |
| if ([string]::IsNullOrWhiteSpace($inputPercent)) { $inputPercent = "100" } | |
| if (-not [int]::TryParse($inputPercent, [ref]$percent)) { | |
| Write-Color "`n❌ Invalid input. Please enter a numeric value." Red | |
| Read-Host "`nPress Enter to return to menu..." | |
| break | |
| } | |
| # Build argument array | |
| $cmdArgs = @("--repo", "$($repoPath)", "check", "--no-cache") | |
| if ([int]$percent -ge 1 -and [int]$percent -lt 100) { | |
| $cmdArgs += "--read-data-subset=${percent}%" # Read % of whole data | |
| } | |
| elseif ([int]$percent -ge 100) { | |
| $cmdArgs += "--read-data" # full read data 100% | |
| } | |
| elseif ([int]$percent -lt 1) { | |
| Write-Color "`n❌ Invalid percentage value. Must be 1 or higher." Red | |
| Read-Host "`nPress Enter to return to menu..." | |
| break | |
| } | |
| # Preview command | |
| Write-Color "`n🧾 Command preview:" Yellow | |
| Write-Color "`"$resticPath`" $($cmdArgs -join ' ')" Cyan | |
| # Confirm and run | |
| $confirm = Read-Host "Run this command now? (y/n)" | |
| if ($confirm -match '^[Yy]$') { | |
| Write-Color "`nRunning Health Check (read-data $percent%)..." Green | |
| & $resticPath @cmdArgs | |
| } | |
| else { | |
| Write-Color "Skipped Health Check with read-data." Yellow | |
| } | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| default { | |
| Write-Color "`n❌ Invalid option.." Red | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| } | |
| } | |
| '3' { | |
| Write-Color "`n💾 Create a New Snapshot" Yellow | |
| Write-Color "--------------------------------------" Cyan | |
| Write-Color "This will create a new Restic backup (snapshot)." Cyan | |
| Write-Color "You must enter a valid folder path to back up. Press Ctrl+C to cancel." Cyan | |
| Write-Color "--------------------------------------" Cyan | |
| # Check if ImDisk was detected and show warning if not available | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Color "`n⚠️ Warning: ImDisk was not detected earlier." Red | |
| Write-Color "Running a backup will cause A LOT of WRITE operations on your OS %TEMP% disk. We recommend setting up a RAM DISK." Yellow | |
| $cont = Read-Host "Are you sure you want to continue? (y/n)" | |
| if ($cont -notmatch '^[Yy]$') { | |
| Write-Color "Cancelled by user." Yellow | |
| break | |
| } | |
| } | |
| # Prompt until a non-empty, existing directory is provided | |
| while ($true) { | |
| $sourcePath = Read-Host "Enter the path to back up (no default; must be a folder)" | |
| if ([string]::IsNullOrWhiteSpace($sourcePath)) { | |
| Write-Color "❌ Empty input is not allowed. Please enter a valid folder path." Red | |
| continue | |
| } | |
| if (-not (Test-Path -Path $sourcePath -PathType Container)) { | |
| Write-Color "❌ Path not found or not a folder: $sourcePath" Red | |
| continue | |
| } | |
| break | |
| } | |
| Write-Color "Selected source: $sourcePath" Cyan | |
| # Build the command | |
| $cmd = "`"$resticPath`" --repo `"$repoPath`" backup `"$sourcePath`" --no-cache" | |
| Write-Color "`n🧾 Command preview:" Yellow | |
| Write-Color $cmd Cyan | |
| $confirm = Read-Host "Run this command now? (y/n)" | |
| if ($confirm -match '^[Yy]$') { | |
| Write-Color "`nRunning backup..." Green | |
| & "$resticPath" --repo "$repoPath" backup "$sourcePath" --no-cache | |
| } | |
| else { | |
| Write-Color "Backup cancelled by user." Yellow | |
| } | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| '4' { | |
| Write-Color "`n🧹 Clean Leftover Files (Forget & Prune)" Yellow | |
| Write-Color "--------------------------------------" Cyan | |
| Write-Color "This operation will remove old snapshots based on the retention policy:" Cyan | |
| Write-Color " - Keep 12 monthly snapshots" Cyan | |
| Write-Color " - Keep 1 yearly snapshot" Cyan | |
| Write-Color " TLDR; Keep max 1 backup (snapshot) per month up to one year." Cyan | |
| Write-Color "It will also prune unreferenced data to free disk space." Cyan | |
| Write-Color "--------------------------------------" Cyan | |
| # Ask for dry-run | |
| $dryRun = Read-Host "Do you want to do a dry-run (test-run without I/O operations) first? (y/n)" | |
| $dryRunOption = "" | |
| if ($dryRun -match '^[Yy]$') { | |
| $dryRunOption = "--dry-run" | |
| Write-Color "Dry-run enabled. Restic will show what would be deleted without actually removing anything." Cyan | |
| } | |
| # Build the arguments array | |
| $cmdArgs = @( | |
| "--repo", $repoPath, | |
| "forget", | |
| "--prune", | |
| "--keep-monthly", "12", | |
| "--keep-yearly", "1", | |
| "--group-by", "host,paths" | |
| ) | |
| # Add dry-run if enabled | |
| if ($dryRunOption) { | |
| $cmdArgs += $dryRunOption | |
| } | |
| # Preview command | |
| Write-Color "`n🧾 Command preview:" Yellow | |
| Write-Color "`"$resticPath`" $($cmdArgs -join ' ')" Cyan | |
| # Confirm and run | |
| $confirm = Read-Host "Run this cleanup command now? (y/n)" | |
| if ($confirm -match '^[Yy]$') { | |
| Write-Color "`nRunning cleanup (forget & prune)..." Green | |
| & $resticPath @cmdArgs | |
| } | |
| else { | |
| Write-Color "Cleanup cancelled by user." Yellow | |
| } | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| '5' { | |
| Write-Color "`n🗂️ List All Snapshots" Yellow | |
| Write-Color "--------------------------------------" Cyan | |
| Write-Color "This will list all backups (snapshots) currently stored in the repository." Cyan | |
| Write-Color "Useful for verifying existing backups and retention status." Cyan | |
| Write-Color "--------------------------------------" Cyan | |
| # Build the command | |
| $cmd = "`"$resticPath`" --repo `"$repoPath`" snapshots" | |
| Write-Color "`n🧾 Command preview:" Yellow | |
| Write-Color $cmd Cyan | |
| $confirm = Read-Host "Run this command now? (y/n)" | |
| if ($confirm -match '^[Yy]$') { | |
| Write-Color "`nListing snapshots..." Green | |
| & "$resticPath" --repo "$repoPath" snapshots | |
| } | |
| else { | |
| Write-Color "Listing cancelled by user." Yellow | |
| } | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| '6' { | |
| Write-Color "`n📂 View Content of a Snapshot" Yellow | |
| Write-Color "--------------------------------------" Cyan | |
| Write-Color "This option lists all files and directories stored inside a specific snapshot." Cyan | |
| Write-Color "You can also search for keywords or filter specific filenames or paths." Cyan | |
| Write-Color "--------------------------------------" Cyan | |
| # Ask for snapshot ID | |
| while ($true) { | |
| $snapshotID = Read-Host "Enter the snapshot ID or 'latest'" | |
| if ([string]::IsNullOrWhiteSpace($snapshotID)) { | |
| Write-Color "❌ Snapshot ID cannot be empty." Red | |
| continue | |
| } | |
| break | |
| } | |
| # Ask if user wants to filter results | |
| $filter = Read-Host "Enter one or more keywords to search for (comma-separated), or press Enter to show everything" | |
| $filters = @() | |
| if (-not [string]::IsNullOrWhiteSpace($filter)) { | |
| $filters = $filter -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } | |
| Write-Color "`n🔍 Filter enabled: showing only items that contain: $($filters -join ', ')" Cyan | |
| } | |
| # Build restic command | |
| $cmdArgs = @("--repo", "$repoPath", "ls", "$snapshotID") | |
| # Preview | |
| Write-Color "`n🧾 Command preview:" Yellow | |
| Write-Color "`"$resticPath`" $($cmdArgs -join ' ')" Cyan | |
| $confirm = Read-Host "Run this command now? (y/n)" | |
| if ($confirm -match '^[Yy]$') { | |
| Write-Color "`nListing content of snapshot $snapshotID..." Green | |
| if ($filters.Count -eq 0) { | |
| # No filter: show all results directly | |
| & $resticPath @cmdArgs | |
| } | |
| else { | |
| # Filter results live while streaming | |
| & $resticPath @cmdArgs | ForEach-Object { | |
| $line = $_ | |
| foreach ($f in $filters) { | |
| if ($line -match [Regex]::Escape($f)) { | |
| Write-Host $line | |
| break | |
| } | |
| } | |
| } | |
| } | |
| } | |
| else { | |
| Write-Color "Operation cancelled by user." Yellow | |
| } | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| '7' { | |
| Write-Color "`n🔄 Restore Files from Snapshot" Yellow | |
| Write-Color "--------------------------------------" Cyan | |
| Write-Color "You can restore either a full snapshot path or a specific sub-directory inside the snapshot." Cyan | |
| Write-Color "The '--include <path>' option tells Restic to only restore the specified sub-directory path from the snapshot." Cyan | |
| Write-Color "If you don't specify --include, the entire snapshot is restored." Cyan | |
| Write-Color "`n⚠️ Warning: The default behavior of the restore operation is to overwrite existing files with restored file inside the output path." Red | |
| Write-Color "--------------------------------------" Cyan | |
| # Ask for snapshot ID | |
| while ($true) { | |
| $snapshotID = Read-Host "Enter the snapshot ID or 'latest' to restore from" | |
| if ([string]::IsNullOrWhiteSpace($snapshotID)) { | |
| Write-Color "❌ Snapshot ID cannot be empty." Red | |
| continue | |
| } | |
| break | |
| } | |
| # Ask if user wants to restore a specific folder | |
| $includePath = Read-Host "Enter the path to restore (relative to snapshot browsing tree) or press Enter to restore full snapshot" | |
| if (-not [string]::IsNullOrWhiteSpace($includePath)) { | |
| $includeOption = "--include `"$includePath`"" | |
| Write-Color "`nThe restore will include only the specified path: $includePath" Cyan | |
| } | |
| else { | |
| $includeOption = "" | |
| Write-Color "`nThe restore will include the full snapshot." Cyan | |
| } | |
| # Ask for restore target directory | |
| while ($true) { | |
| $targetPath = Read-Host "Enter the target directory where files will be restored" | |
| if ([string]::IsNullOrWhiteSpace($targetPath)) { | |
| Write-Color "❌ Target directory cannot be empty." Red | |
| continue | |
| } | |
| # Create target folder if it does not exist | |
| if (-not (Test-Path $targetPath)) { | |
| New-Item -ItemType Directory -Path $targetPath | Out-Null | |
| Write-Color "Created target directory: $targetPath" Green | |
| } | |
| break | |
| } | |
| # Ask for dry-run | |
| $dryRun = Read-Host "Do you want to do a dry-run (test-run without I/O operations) first? (y/n)" | |
| # Build the arguments array | |
| $cmdArgs = @( | |
| "--repo", $repoPath, | |
| "restore", $snapshotID, | |
| "--target", $targetPath | |
| ) | |
| # Add include option if specified | |
| if (-not [string]::IsNullOrWhiteSpace($includePath)) { | |
| $cmdArgs += "--include" | |
| $cmdArgs += $includePath | |
| } | |
| # Add dry-run option if enabled | |
| if ($dryRun -match '^[Yy]$') { | |
| $cmdArgs += "--dry-run" | |
| Write-Color "Dry-run enabled. Restic will show what would be restored without actually restoring." Cyan | |
| } | |
| # Preview command | |
| Write-Color "`n🧾 Command preview:" Yellow | |
| Write-Color "`"$resticPath`" $($cmdArgs -join ' ')" Cyan | |
| # Confirm and run | |
| $confirm = Read-Host "Run this restore command now? (y/n)" | |
| if ($confirm -match '^[Yy]$') { | |
| Write-Color "`nRunning restore..." Green | |
| & $resticPath @cmdArgs | |
| } | |
| else { | |
| Write-Color "Restore cancelled by user." Yellow | |
| } | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| '8' { | |
| Write-Color "`nExiting... Goodbye!" Green | |
| } | |
| default { | |
| Write-Color "`n❌ Invalid option. Please select a number between 1 and 7." Red | |
| Read-Host "`nPress Enter to return to menu..." | |
| } | |
| } | |
| } while ($choice -ne '8') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment