From f66e8ca5ceebd4194c3d60c6aa681e41932db43a Mon Sep 17 00:00:00 2001 From: Wouter Horlings Date: Thu, 24 Dec 2020 15:17:05 +0100 Subject: [PATCH] Initial commit --- .gitignore | 5 + README.md | 47 ++++++ backup.ps1 | 391 +++++++++++++++++++++++++++++++++++++++++++ config.ps1 | 36 ++++ install.ps1 | 61 +++++++ secrets_template.ps1 | 17 ++ windows.exclude | 67 ++++++++ 7 files changed, 624 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 backup.ps1 create mode 100644 config.ps1 create mode 100644 install.ps1 create mode 100644 secrets_template.ps1 create mode 100644 windows.exclude diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4df3d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +local.exclude +logs +restic.exe +secrets.ps1 +state.xml \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..638cd28 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# restic-windows-backup +Powershell scripts to run Restic backups on Windows. +Simplifies the process of installation and running daily backups. + +# Features +* **VSS (Volume Snapshot Service) support** - backup everything, don't worry about what files are open/in-use +* **Easy Installation** - `install.ps1` script downloads Restic, initializes the restic repository, and setups up a Windows Task Scheduler task to run the backup daily +* **Backup, Maintenance and Monitoring are Automated** - `backup.ps1` script handles + * Emailing the results of each execution, including log files when there are problems + * Runs routine maintenence (pruning and checking the repo for errors on a regular basis) + * And, of course backing up your files. + +# Installation Instructions + +1. Create your restic repository + 1. This is up to you to sort out where you want the data to go to. *Minio, B2, S3, oh my.* +1. Install Scripts + 1. Create script directory: `C:\restic` + 1. Download scripts from https://github.com/kmwoley/restic-windows-backup, and unzip them into `C:\restic` + 1. Launch PowerShell as Administrator + 1. Change your working directory to `C:\restic` + 1. If you downloaded the files as a ZIP file, you may have to 'unblock' the execution of the scripts by running `Unblock-File *.ps1` +1. Create `secrets.ps1` file + 1. The secrets file contains location and passwords for your restic repository. + 1. `secrets_template.ps1` is a template for the `secrets.ps1` file - copy or rename this file to `secrets.ps1` and edit. + 1. restic will pick up the repo destination from the environment variables you set in this file - see this doc for more information about configuring restic repos https://restic.readthedocs.io/en/latest/030_preparing_a_new_repo.html + 1. Email sending configuration is also contained with this file. The scripts assume you want to get emails about the success/failure of each backup attempt. +1. Run `install.ps1` file + 1. From the elevated (Run as Administrator) Powershell window, run `.\install.ps1` + 1. This will initialize the repro, create your logfile directory, and create a scheduled task in Windows Task Scheduler to run the task daily. +1. Add files/paths not to backup to `local.exclude` + 1. If you don't want to modify the included exclude file, you can add any files/paths you want to exclude from the backup to `local.exclude` +1. Add `restic.exe` to the Windows Defender / Virus & Threat Detection Exclude list + 1. Backups on Windows are really slow if you don't set the Antivirus to ignore restic. + 1. Navigate from the Start menu to: *Virus & threat protection > Manage Settings > Exclusions (Add or remove exclusions) > Add an exclusion (Process) > Process Name: "restic.exe"* +1. *(Recommended)* To a test backup triggered from Task Scheduler + 1. It's recommended to open Windows Task Scheduler and trigger the task to run manually to test your first backup. + 1. *Open Task Scheduler > Find "Restic Backup" > Right Click > Run* + 1. The backup script will be executed as the SYSTEM user. Some of your files might not be accessible by this user. If you run into this, add the SYSTEM user to the files where you get "Access Denied" errors. + 1. *Folder > Properties > Security > Advanced > Add ("SYSTEM" Principal/User) > Check "Replace all child object permission entries with inheritable permission entries from this object" > Apply > OK* +1. *(Recommended)* Do a test restore + 1. These scripts make it easy to work with Restic from the Powershell command line. If you run `. .\config.ps1; . .\secrets.ps1` you can then easily invoke restic commands like + 1. `& $ResticExe find -i "*filename*"` + 1. `& $ResticExe restore ...` + +# Feedback? +Feel free to open issues or create PRs! diff --git a/backup.ps1 b/backup.ps1 new file mode 100644 index 0000000..dd19dfb --- /dev/null +++ b/backup.ps1 @@ -0,0 +1,391 @@ +# +# Restic Windows Backup Script +# + +# =========== start configuration =========== # + +# set restic configuration parmeters (destination, passwords, etc.) +$SecretsScript = Join-Path $PSScriptRoot "secrets.ps1" + +# backup configuration variables +$ConfigScript = Join-Path $PSScriptRoot "config.ps1" + +# =========== end configuration =========== # + +# globals for state storage +$Script:ResticStateRepositoryInitialized = $null +$Script:ResticStateLastMaintenance = $null +$Script:ResticStateLastDeepMaintenance = $null +$Script:ResticStateMaintenanceCounter = $null + +# restore backup state from disk +function Get-BackupState { + if(Test-Path $StateFile) { + Import-Clixml $StateFile | ForEach-Object{ Set-Variable -Scope Script $_.Name $_.Value } + } +} +function Set-BackupState { + Get-Variable ResticState* | Export-Clixml $StateFile +} + +# unlock the repository if need be +function Invoke-Unlock { + Param($SuccessLog, $ErrorLog) + + $locks = & $ResticExe list locks --no-lock -q 3>&1 2>> $ErrorLog + if($locks.Length -gt 0) { + # unlock the repository (assumes this machine is the only one that will ever use it) + & $ResticExe unlock 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog + Write-Output "[[Unlock]] Repository was locked. Unlocking. Past script failure?" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog + Start-Sleep 120 + } +} + +# run maintenance on the backup set +function Invoke-Maintenance { + Param($SuccessLog, $ErrorLog) + + # skip maintenance if disabled + if($SnapshotMaintenanceEnabled -eq $false) { + Write-Output "[[Maintenance]] Skipped - maintenance disabled" | Tee-Object -Append $SuccessLog + return + } + + # skip maintenance if it's been done recently + if(($null -ne $ResticStateLastMaintenance) -and ($null -ne $ResticStateMaintenanceCounter)) { + $Script:ResticStateMaintenanceCounter += 1 + $delta = New-TimeSpan -Start $ResticStateLastMaintenance -End $(Get-Date) + if(($delta.Days -lt $SnapshotMaintenanceDays) -and ($ResticStateMaintenanceCounter -lt $SnapshotMaintenanceInterval)) { + Write-Output "[[Maintenance]] Skipped - last maintenance $ResticStateLastMaintenance ($($delta.Days) days, $ResticStateMaintenanceCounter backups ago)" | Tee-Object -Append $SuccessLog + return + } + } + + Write-Output "[[Maintenance]] Start $(Get-Date)" | Tee-Object -Append $SuccessLog + $maintenance_success = $true + Start-Sleep 120 + + # forget snapshots based upon the retention policy + Write-Output "[[Maintenance]] Start forgetting..." | Tee-Object -Append $SuccessLog + & $ResticExe --verbose -q forget $SnapshotRetentionPolicy 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog + if(-not $?) { + Write-Output "[[Maintenance]] Forget operation completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog + $maintenance_success = $false + } + + # prune (remove) data from the backup step. Running this separate from `forget` because + # `forget` only prunes when it detects removed snapshots upon invocation, not previously removed + Write-Output "[[Maintenance]] Start pruning..." | Tee-Object -Append $SuccessLog + & $ResticExe --verbose -q prune 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog + if(-not $?) { + Write-Output "[[Maintenance]] Prune operation completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog + $maintenance_success = $false + } + + # check data to ensure consistency + Write-Output "[[Maintenance]] Start checking..." | Tee-Object -Append $SuccessLog + + # check to determine if we want to do a full data check or not + $data_check = @() + if($null -ne $ResticStateLastDeepMaintenance) { + $delta = New-TimeSpan -Start $ResticStateLastDeepMaintenance -End $(Get-Date) + if($delta.Days -ge $SnapshotDeepMaintenanceDays) { + Write-Output "[[Maintenance]] Performing full data check - deep '--read-data' check last ran $ResticStateLastDeepMaintenance ($($delta.Days) days ago)" | Tee-Object -Append $SuccessLog + $data_check = @("--read-data") + $Script:ResticStateLastDeepMaintenance = Get-Date + } + else { + Write-Output "[[Maintenance]] Performing fast data check - deep '--read-data' check last ran $ResticStateLastDeepMaintenance ($($delta.Days) days ago)" | Tee-Object -Append $SuccessLog + } + } + else { + # set the date, but don't do a deep check if we've never done a full data read + $Script:ResticStateLastDeepMaintenance = Get-Date + } + + & $ResticExe --verbose -q check @data_check 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog + if(-not $?) { + Write-Output "[[Maintenance]] Check completed with errors" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog + $maintenance_success = $false + } + + Write-Output "[[Maintenance]] End $(Get-Date)" | Tee-Object -Append $SuccessLog + + if($maintenance_success -eq $true) { + $Script:ResticStateLastMaintenance = Get-Date + $Script:ResticStateMaintenanceCounter = 0; + } +} + +# Run restic backup +function Invoke-Backup { + Param($SuccessLog, $ErrorLog) + + Write-Output "[[Backup]] Start $(Get-Date)" | Tee-Object -Append $SuccessLog + $return_value = $true + + # Launch Restic + & $ResticExe --verbose backup --use-fs-snapshot --files-from=$LocalIncludeFile --iexclude-file=$WindowsExcludeFile --iexclude-file=$LocalExcludeFile 3>&1 2>> $ErrorLog | Tee-Object -Append $SuccessLog + Switch ($LastExitCode) + { + 1 { + Write-Output "[[Backup]] Failed with errors, see log for more information" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog + $return_value = $false + } + 3 { + if ($FailOnIncomplete) { + Write-Output "[[Backup]] Failed with errors, see log for more information" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog + $return_value = $false + } else { + Write-Output "[[Backup]] Completed with errors, see log for more information" | Tee-Object -Append $ErrorLog | Tee-Object -Append $SuccessLog + } + } + } + Write-Output "[[Backup]] End $(Get-Date)" | Tee-Object -Append $SuccessLog + + return $return_value +} + +function Send-Email { + Param($SuccessLog, $ErrorLog) + $password = ConvertTo-SecureString $ResticEmailPassword -AsPlainText -Force + $credentials = New-Object System.Management.Automation.PSCredential ($ResticEmailUsername, $password) + + $status = "SUCCESS" + $success_after_failure = $false + $body = "" + if (($null -ne $SuccessLog) -and (Test-Path $SuccessLog) -and (Get-Item $SuccessLog).Length -gt 0) { + $body = $(Get-Content -Raw $SuccessLog) + # if previous run contained an error, send the success email confirming that the error has been resolved + # (i.e. get previous error log, if it's not empty, trigger the send of the success-after-failure email) + $previous_error_log = Get-ChildItem $LogPath -Filter '*err.txt' | Sort-Object -Descending LastWriteTime | Select-Object -Skip 1 | Select-Object -First 1 + if(($null -ne $previous_error_log) -and ($previous_error_log.Length -gt 0)){ + $success_after_failure = $true + } + } + else { + $body = "Critical Error! Restic backup log is empty or missing. Check log file path." + $status = "ERROR" + } + $attachments = @{} + if (($null -ne $ErrorLog) -and (Test-Path $ErrorLog) -and (Get-Item $ErrorLog).Length -gt 0) { + $attachments = @{Attachments = $ErrorLog} + $status = "ERROR" + } + if((($status -eq "SUCCESS") -and ($SendEmailOnSuccess -ne $false)) -or ((($status -eq "ERROR") -or $success_after_failure) -and ($SendEmailOnError -ne $false))) { + $subject = "$env:COMPUTERNAME Restic Backup Report [$status]" + Send-MailMessage @ResticEmailConfig -From $ResticEmailFrom -To $ResticEmailTo -Credential $credentials -Subject $subject -Body $body @attachments + } +} + +function Invoke-ConnectivityCheck { + Param($SuccessLog, $ErrorLog) + # skip the internet connectivity check for local repos + if(Test-Path $env:RESTIC_REPOSITORY) { + Write-Output "[[Internet]] Skipping internet connectivity check." | Tee-Object -Append $SuccessLog + return $true + } + + $repository_host = '' + + # use generic internet service for non-specific repo types (e.g. swift:, rclone:, etc. ) + if(($env:RESTIC_REPOSITORY -match "^swift:") -or + ($env:RESTIC_REPOSITORY -match "^rclone:")) { + $repository_host = "cloudflare.com" + } + elseif($env:RESTIC_REPOSITORY -match "^b2:") { + $repository_host = "api.backblazeb2.com" + } + elseif($env:RESTIC_REPOSITORY -match "^azure:") { + $repository_host = "azure.microsoft.com" + } + elseif($env:RESTIC_REPOSITORY -match "^gs:") { + $repository_host = "storage.googleapis.com" + } + else { + # parse connection string for hostname + # Uri parser doesn't handle leading connection type info (s3:, sftp:, rest:) + $connection_string = $env:RESTIC_REPOSITORY -replace "^s3:" -replace "^sftp:" -replace "^rest:" + $repository_host = ([System.Uri]$connection_string).host + } + + if([string]::IsNullOrEmpty($repository_host)) { + Write-Output "[[Internet]] Repository string could not be parsed." | Tee-Object -Append $SuccessLog | Tee-Object -Append $ErrorLog + return $false + } + + # test for internet connectivity + $connections = 0 + $sleep_count = $InternetTestAttempts + if (Test-NetMetered) { + if ($BackupoverMetered) { + Write-Output "[[Internet]] Current connection is metered. Change config to DISALLOW backup over metered connection." | Tee-Object -Append $SuccessLog | Tee-Object -Append $Successlog + } + else { + Write-Output "[[Internet]] Current connection is metered. Change config to allow backup over metered." | Tee-Object -Append $SuccessLog | Tee-Object -Append $ErrorLog + return $false + } + } + + while($true) { + $connections = Get-NetRoute | Where-Object DestinationPrefix -eq '0.0.0.0/0' | Get-NetIPInterface | Where-Object ConnectionState -eq 'Connected' | Measure-Object | ForEach-Object{$_.Count} + if($sleep_count -le 0) { + Write-Output "[[Internet]] Connection to repository ($repository_host) could not be established." | Tee-Object -Append $SuccessLog | Tee-Object -Append $ErrorLog + return $false + } + if(($null -eq $connections) -or ($connections -eq 0)) { + Write-Output "[[Internet]] Waiting for internet connectivity... $sleep_count" | Tee-Object -Append $SuccessLog + Start-Sleep 30 + } + elseif(!(Test-Connection -Server $repository_host -Quiet)) { + Write-Output "[[Internet]] Waiting for connection to repository ($repository_host)... $sleep_count" | Tee-Object -Append $SuccessLog + Start-Sleep 30 + } + else { + return $true + } + $sleep_count-- + } +} + +# check previous logs +function Invoke-HistoryCheck { + Param($SuccessLog, $ErrorLog) + $logs = Get-ChildItem $LogPath -Filter '*err.txt' | ForEach-Object{$_.Length -gt 0} + $logs_with_success = ($logs | Where-Object {($_ -eq $false)}).Count + if($logs.Count -gt 0) { + Write-Output "[[History]] Backup success rate: $logs_with_success / $($logs.Count) ($(($logs_with_success / $logs.Count).tostring("P")))" | Tee-Object -Append $SuccessLog + } +} + +# +# .SYNOPSIS +# Returns if current connection is a metered connection or not. +# +# .DESCRIPTION +# This cmdlet checks if connection is metered or not. +# +# This is based on the example in https://msdn.microsoft.com/en-us/library/windows/apps/xaml/jj835821.aspx +# +# .EXAMPLE +# Check if connected to a metered connection +# +# If(Test-NetMetered) { Write-Host "Metered" } + +function Test-NetMetered +{ + [void][Windows.Networking.Connectivity.NetworkInformation, Windows, ContentType = WindowsRuntime] + $networkprofile = [Windows.Networking.Connectivity.NetworkInformation]::GetInternetConnectionProfile() + + if ($networkprofile -eq $null) + { + Write-Warning "Can't find any internet connections!" + return $false + } + + $cost = $networkprofile.GetConnectionCost() + + + if ($cost -eq $null) + { + Write-Warning "Can't find any internet connections with a cost!" + return $false + } + + if ($cost.Roaming -or $cost.OverDataLimit) + { + return $true + } + + if ($cost.NetworkCostType -eq [Windows.Networking.Connectivity.NetworkCostType]::Fixed -or + $cost.NetworkCostType -eq [Windows.Networking.Connectivity.NetworkCostType]::Variable) + { + return $true + } + + if ($cost.NetworkCostType -eq [Windows.Networking.Connectivity.NetworkCostType]::Unrestricted) + { + return $false + } + + throw "Network cost type is unknown!" +} + +# main function +function Invoke-Main { + + # check for elevation, required for creation of shadow copy (VSS) + if (-not (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) + { + Write-Error "[[Backup]] Elevation required (run as administrator). Exiting." + exit + } + + # initialize secrets + . $SecretsScript + + # initialize config + . $ConfigScript + + Get-BackupState + + if(!(Test-Path $LogPath)) { + Write-Error "[[Backup]] Log file directory $LogPath does not exist. Exiting." + Send-Email + exit + } + + $error_count = 0; + $attempt_count = $GlobalRetryAttempts + while ($attempt_count -gt 0) { + # setup logfiles + $timestamp = Get-Date -Format FileDateTime + $success_log = Join-Path $LogPath ($timestamp + ".log.txt") + $error_log = Join-Path $LogPath ($timestamp + ".err.txt") + + $internet_available = Invoke-ConnectivityCheck $success_log $error_log + if($internet_available -eq $true) { + Invoke-Unlock $success_log $error_log + $backup_success = Invoke-Backup $success_log $error_log + if($backup_success) { + Invoke-Maintenance $success_log $error_log + } + + if (!(Test-Path $error_log) -or ((Get-Item $error_log).Length -eq 0)) { + # successful with no errors; end + $total_attempts = $GlobalRetryAttempts - $attempt_count + 1 + Write-Output "Succeeded after $total_attempts attempt(s)" | Tee-Object -Append $success_log + Invoke-HistoryCheck $success_log $error_log + Send-Email $success_log $error_log + break; + } + } + + Write-Warning "Errors found! Error Log: $error_log" + $error_count++ + + $attempt_count-- + if($attempt_count -gt 0) { + Write-Output "Sleeping for 15 min and then retrying..." | Tee-Object -Append $success_log + } + else { + Write-Output "Retry limit has been reached. No more attempts to backup will be made." | Tee-Object -Append $success_log + } + if($internet_available -eq $true) { + Invoke-HistoryCheck $success_log $error_log + Send-Email $success_log $error_log + } + if($attempt_count -gt 0) { + Start-Sleep (15*60) + } + } + + Set-BackupState + + # cleanup older log files + Get-ChildItem $LogPath | Where-Object {$_.CreationTime -lt $(Get-Date).AddDays(-$LogRetentionDays)} | Remove-Item + + exit $error_count +} + +Invoke-Main diff --git a/config.ps1 b/config.ps1 new file mode 100644 index 0000000..4d52897 --- /dev/null +++ b/config.ps1 @@ -0,0 +1,36 @@ +# backup configuration +$ExeName = "restic.exe" +$InstallPath = "C:\restic" +$ResticExe = Join-Path $InstallPath $ExeName +$StateFile = Join-Path $InstallPath "state.xml" +$WindowsExcludeFile = Join-Path $InstallPath "windows.exclude" +$LocalExcludeFile = Join-Path $InstallPath "local.exclude" +$LocalIncludeFile = Join-Path $InstallPath "local.include" +$LogPath = Join-Path $InstallPath "logs" +$LogRetentionDays = 30 +$InternetTestAttempts = 10 +$GlobalRetryAttempts = 1 +$FailOnIncomplete = $false + +# maintenance configuration +$SnapshotMaintenanceEnabled = $false +$SnapshotRetentionPolicy = @("--keep-daily", "30", "--keep-weekly", "52", "--keep-monthly", "24", "--keep-yearly", "10") +$SnapshotMaintenanceInterval = 7 +$SnapshotMaintenanceDays = 30 +$SnapshotDeepMaintenanceDays = 90; + +# Network configuration +$BackupoverMetered = $false + +# email configuration +$SendEmailOnSuccess = $false +$SendEmailOnError = $false + +# Paths to backup +$BackupSources = @{} +$BackupSources["C:\"] = @( +# 'Users' +) +#$BackupSources["D:\"] = @( +# 'Software' +#) diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..b0c13ea --- /dev/null +++ b/install.ps1 @@ -0,0 +1,61 @@ +. .\config.ps1 +. .\secrets.ps1 + +# download restic +if(-not (Test-Path $ResticExe)) { + $url = $null + if([Environment]::Is64BitOperatingSystem){ + $url = "https://github.com/restic/restic/releases/download/v0.11.0/restic_0.11.0_windows_amd64.zip" + } + else { + $url = "https://github.com/restic/restic/releases/download/v0.11.0/restic_0.11.0_windows_386.zip" + } + $output = Join-Path $InstallPath "restic.zip" + Invoke-WebRequest -Uri $url -OutFile $output + Expand-Archive -LiteralPath $output $InstallPath + Remove-Item $output + Get-ChildItem *.exe | Rename-Item -NewName $ExeName +} + + +# Create log directory if it doesn't exit +if(-not (Test-Path $LogPath)) { + New-Item -ItemType Directory -Force -Path $LogPath | Out-Null + Write-Output "[[Init]] Repository successfully initialized." +} + +# Create the local exclude file +if(-not (Test-Path $LocalExcludeFile)) { + New-Item -Type File -Path $LocalExcludeFile | Out-Null +} + +# Initialize the restic repository +& $ResticExe --verbose init +if($?) { + Write-Output "[[Init]] Repository successfully initialized." +} +else { + Write-Warning "[[Init]] Repository initialization failed. Check errors and resolve." +} + +# Scheduled Windows Task Scheduler to run the backup +$backup_task_name = "Restic Backup" +$backup_task = Get-ScheduledTask $backup_task_name -ErrorAction SilentlyContinue +if($null -eq $backup_task) { + try { + $task_action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NonInteractive -NoLogo -NoProfile -Command ".\backup.ps1; exit $LASTEXITCODE"' -WorkingDirectory $InstallPath + $task_user = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -RunLevel Highest + $task_settings = New-ScheduledTaskSettingsSet -RestartCount 4 -RestartInterval (New-TimeSpan -Minutes 15) -ExecutionTimeLimit (New-TimeSpan -Days 3) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -MultipleInstances IgnoreNew -IdleDuration 0 -IdleWaitTimeout 0 -StartWhenAvailable -RestartOnIdle + $task_trigger = New-ScheduledTaskTrigger -Daily -At 4:00am + Register-ScheduledTask $backup_task_name -Action $task_action -Principal $task_user -Settings $task_settings -Trigger $task_trigger | Out-Null + Write-Output "[[Scheduler]] Backup task scheduled." + } + catch { + Write-Warning "[[Scheduler]] Scheduling failed." + } +} +else { + Write-Warning "[[Scheduler]] Backup task not scheduled: there is already a task with the name '$backup_task_name'." +} + + diff --git a/secrets_template.ps1 b/secrets_template.ps1 new file mode 100644 index 0000000..4ef9884 --- /dev/null +++ b/secrets_template.ps1 @@ -0,0 +1,17 @@ +# Template file for backup destination configuration and email passwords. +# Update this file to point to your restic repository and email service. +# Rename to `secrets.ps1` + +# restic backup repository configuration +$Env:AWS_ACCESS_KEY_ID='' +$Env:AWS_SECRET_ACCESS_KEY='' +$Env:RESTIC_REPOSITORY='' +$Env:RESTIC_PASSWORD='' + +# email configuration +$PSEmailServer='' +$ResticEmailConfig=@{UseSsl=$true; Port="587"} +$ResticEmailTo='' +$ResticEmailFrom='' +$ResticEmailUsername='' +$ResticEmailPassword='' diff --git a/windows.exclude b/windows.exclude new file mode 100644 index 0000000..a2236c7 --- /dev/null +++ b/windows.exclude @@ -0,0 +1,67 @@ +# default excludes +# examples https://github.com/duplicati/duplicati/blob/master/Duplicati/Library/Utility/FilterGroups.cs +# note, because we're using a VSS directory, we can use that as the root dir for exclude rules (i.e. resticVSS) +hiberfil.sys +pagefile.sys +swapfile.sys +$$Recycle.Bin +autoexec.bat +Config.Msi +Documents and Settings +Recycled +Recycler +System Volume Information +Recovery +Program Files +Program Files (x86) +ProgramData +PerfLogs +Windows +Windows.old +Microsoft\Windows\Recent +Microsoft\**\RecoveryStore* +Microsoft\**\Windows\*.edb +Microsoft\**\Windows\*.log +Microsoft\**\Windows\Cookies* +MSOCache +NTUSER* +ntuser* +UsrClass.dat + +# cloud services + +Dropbox +AppData\Local\Google\Drive +Google Drive\.tmp.drivedownload +resticVSS\OneDriveTemp + +# browsers +Google\Chrome + +# AppData +AppData\Local\Microsoft +AppData\Local\Duplicati +AppData\Local\D3DSCache +AppData\Local\ConnectedDevicesPlatform +AppData\Local\Packages +AppData\Roaming\Signal +AppData\Local\ElevatedDiagnostics +AppData\Local\Microsoft\Windows\Explorer +AppData\Local\Microsoft\Windows\INetCache +AppData\Local\Microsoft\Windows\WebCache +AppData\Local\Microsoft\Windows Store +AppData\Local\restic +AppData\LocalLow\Microsoft\CryptnetUrlCache +AppData\Local\IsolatedStorage +AppData\Local\Spotify +AppData\Local\Programs\signal-desktop +AppData\Roaming\Code +AppData\Roaming\Slack +AppData\Roaming\Spotify +AppData\Roaming\Zoom + +# misc. temporary files +Temporary Internet Files +Thumbs.db +AppData\Local\Temp +Users\Public\AccountPictures