Last active
August 17, 2026 23:04
-
-
Save jarulsamy/d326a32e8db094f0df2a03c84d972c79 to your computer and use it in GitHub Desktop.
tmp
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
| @echo off | |
| setlocal enabledelayedexpansion | |
| rem =========================================================================== | |
| rem backup.bat - daily restic backup of a user profile. | |
| rem | |
| rem Restic equivalent of the rsync --link-dest script: content-addressed | |
| rem dedup instead of hard links, encrypted at rest, VSS for locked files. | |
| rem | |
| rem Snapshots are browsable with: | |
| rem restic snapshots | |
| rem restic mount Z: (needs WinFsp) | |
| rem restic restore latest --target C:\restore | |
| rem =========================================================================== | |
| rem --- Configuration --------------------------------------------------------- | |
| set "SOURCE_DIR=%SystemDrive%\Users\joshua" | |
| set "RESTIC_REPOSITORY=D:\backups\restic-home" | |
| set "RESTIC_PASSWORD_FILE=C:\Scripts\restic-password.txt" | |
| set "EXCLUDE_FILE=%~dp0excludes.txt" | |
| set "LOG_DIR=D:\backups\logs" | |
| set "RESTIC=restic.exe" | |
| rem Retention. Roughly: two weeks of dailies, then thinning out. | |
| set "KEEP=--keep-daily 14 --keep-weekly 8 --keep-monthly 12 --keep-yearly 3" | |
| rem Run a full repository integrity check on this day of the week (0=Sunday). | |
| set "CHECK_DOW=0" | |
| rem --- Preflight ------------------------------------------------------------- | |
| net session >nul 2>&1 | |
| if errorlevel 1 ( | |
| echo Please run elevated ^(--use-fs-snapshot needs admin for VSS^). | |
| exit /b 1 | |
| ) | |
| where %RESTIC% >nul 2>&1 | |
| if errorlevel 1 ( | |
| echo restic.exe not found in PATH. | |
| exit /b 1 | |
| ) | |
| if not exist "%RESTIC_PASSWORD_FILE%" ( | |
| echo Password file not found: %RESTIC_PASSWORD_FILE% | |
| exit /b 1 | |
| ) | |
| if not exist "%LOG_DIR%" mkdir "%LOG_DIR%" | |
| for /f %%i in ('powershell -NoProfile -Command "Get-Date -Format yyyy-MM-dd_HH-mm-ss"') do set "STAMP=%%i" | |
| for /f %%i in ('powershell -NoProfile -Command "[int](Get-Date).DayOfWeek"') do set "DOW=%%i" | |
| set "LOG=%LOG_DIR%\restic_%STAMP%.log" | |
| call :log "=== restic backup started: %STAMP% ===" | |
| rem --- Initialise the repo on first run -------------------------------------- | |
| %RESTIC% cat config >nul 2>&1 | |
| if errorlevel 1 ( | |
| call :log "Repository not found, initialising %RESTIC_REPOSITORY%" | |
| %RESTIC% init >>"%LOG%" 2>&1 | |
| if errorlevel 1 ( | |
| call :log "FATAL: restic init failed" | |
| exit /b 1 | |
| ) | |
| ) | |
| rem --- Backup ---------------------------------------------------------------- | |
| rem --use-fs-snapshot reads from a VSS snapshot, so locked files (NTUSER.DAT, | |
| rem PST, database files) are captured consistently. Note that VSS skips Outlook | |
| rem OST files by default; that is a Windows policy, not restic. | |
| %RESTIC% backup "%SOURCE_DIR%" ^ | |
| --use-fs-snapshot ^ | |
| --exclude-file="%EXCLUDE_FILE%" ^ | |
| --exclude-caches ^ | |
| --tag daily ^ | |
| --verbose >>"%LOG%" 2>&1 | |
| set "RC=%errorlevel%" | |
| if "%RC%"=="0" ( | |
| call :log "Backup OK" | |
| ) else if "%RC%"=="3" ( | |
| call :log "WARNING: backup completed but some files could not be read (exit 3)" | |
| ) else ( | |
| call :log "FATAL: backup failed with exit code %RC%" | |
| exit /b %RC% | |
| ) | |
| rem --- Retention ------------------------------------------------------------- | |
| %RESTIC% forget %KEEP% --prune >>"%LOG%" 2>&1 | |
| if errorlevel 1 call :log "WARNING: forget/prune failed" | |
| rem --- Weekly integrity check ------------------------------------------------ | |
| if "%DOW%"=="%CHECK_DOW%" ( | |
| call :log "Running integrity check with 5%% data read" | |
| %RESTIC% check --read-data-subset=5%% >>"%LOG%" 2>&1 | |
| if errorlevel 1 call :log "WARNING: restic check reported problems" | |
| ) | |
| rem --- Trim old logs --------------------------------------------------------- | |
| forfiles /p "%LOG_DIR%" /m restic_*.log /d -60 /c "cmd /c del @path" >nul 2>&1 | |
| call :log "=== finished ===" | |
| exit /b 0 | |
| rem --------------------------------------------------------------------------- | |
| :log | |
| echo [%date% %time%] %~1 | |
| echo [%date% %time%] %~1>>"%LOG%" | |
| exit /b 0 |
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
| #!/usr/bin/env bash | |
| # | |
| # Backup home directory (MSYS2 / Windows port of the Linux backup.sh) | |
| # | |
| # Install: pacman -S rsync | |
| # MSYS2 maps drives at /c, /d, ... (not /cygdrive/c like plain Cygwin). | |
| # | |
| set -o errexit | |
| set -o nounset | |
| set -o pipefail | |
| readonly SOURCE_DIR="/c/Users/joshua" | |
| readonly BACKUP_DIR="/d/backups/home" | |
| readonly DATETIME="$(date '+%Y-%m-%d_%H-%M-%S')" | |
| readonly BACKUP_PATH="${BACKUP_DIR}/${DATETIME}" | |
| readonly LATEST_LINK="${BACKUP_DIR}/latest" | |
| readonly LOG_DIR="${BACKUP_DIR}/logs" | |
| # NTFS allows at most 1024 hard links per file, so an unchanged file cannot | |
| # survive more than ~1023 snapshots. Prune well under that. | |
| readonly KEEP_SNAPSHOTS=300 | |
| if [ ! -d "${SOURCE_DIR}" ]; then | |
| echo "Source does not exist: ${SOURCE_DIR}" >&2 | |
| exit 1 | |
| fi | |
| mkdir -p "${BACKUP_DIR}" "${LOG_DIR}" | |
| readonly LOG="${LOG_DIR}/${DATETIME}.log" | |
| # -rlt instead of -a: Cygwin/MSYS2 fakes POSIX ownership and modes over NTFS | |
| # ACLs, and -p -g -o against a real Windows profile produces a stream of | |
| # chown/chmod failures for no benefit. | |
| # --modify-window=1 absorbs the FAT/NTFS timestamp granularity difference. | |
| rsync -rlt --delete --stats --human-readable \ | |
| --modify-window=1 \ | |
| "${SOURCE_DIR}/" \ | |
| --link-dest "${LATEST_LINK}" \ | |
| --exclude=".cache" \ | |
| --exclude=".cpan" \ | |
| --exclude=".npm" \ | |
| --exclude=".vim" \ | |
| --exclude=".vscode-server" \ | |
| --exclude="node_modules" \ | |
| --exclude="__pycache__" \ | |
| --exclude="AppData/Local/Temp" \ | |
| --exclude="AppData/Local/Packages" \ | |
| --exclude="AppData/Local/CrashDumps" \ | |
| --exclude="AppData/Local/Microsoft/Windows/INetCache" \ | |
| --exclude="AppData/Local/Microsoft/Windows/WebCache" \ | |
| --exclude="AppData/Local/Google/Chrome/User Data/*/Cache" \ | |
| --exclude="AppData/Local/D3DSCache" \ | |
| --exclude="AppData/Local/NVIDIA" \ | |
| --exclude="NTUSER.DAT*" \ | |
| --exclude="UsrClass.dat*" \ | |
| --exclude="Thumbs.db" \ | |
| --exclude="desktop.ini" \ | |
| --exclude="*.tmp" \ | |
| "${BACKUP_PATH}" 2>&1 | tee "${LOG}" | |
| # Repoint 'latest'. A junction is used rather than ln -s: MSYS2 symlinks are | |
| # not visible as links to Explorer or native tools unless MSYS=winsymlinks: | |
| # nativestrict is set, and native symlinks need admin or Developer Mode. | |
| # cmd //c avoids MSYS2 rewriting the /c as a path. | |
| win_latest="$(cygpath -w "${LATEST_LINK}")" | |
| win_backup="$(cygpath -w "${BACKUP_PATH}")" | |
| if [ -e "${LATEST_LINK}" ]; then | |
| # rmdir removes the junction only, never the tree it points at. | |
| MSYS2_ARG_CONV_EXCL='*' cmd //c rmdir "${win_latest}" >/dev/null | |
| fi | |
| MSYS2_ARG_CONV_EXCL='*' cmd //c mklink /J "${win_latest}" "${win_backup}" >/dev/null | |
| # Prune old snapshots (oldest first, keep the newest KEEP_SNAPSHOTS). | |
| if [ "${KEEP_SNAPSHOTS}" -gt 0 ]; then | |
| find "${BACKUP_DIR}" -maxdepth 1 -mindepth 1 -type d \ | |
| -regextype posix-extended \ | |
| -regex '.*/[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}$' \ | |
| | sort -r \ | |
| | tail -n "+$((KEEP_SNAPSHOTS + 1))" \ | |
| | while read -r old; do | |
| echo "Pruning ${old}" | |
| rm -rf "${old}" | |
| done | |
| fi | |
| find "${LOG_DIR}" -name '*.log' -mtime +60 -delete | |
| echo "Backup complete: ${BACKUP_PATH}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment