#Requires -Version 5.1 <# .SYNOPSIS Builds the DupFinder flash-drive installer bundle. .DESCRIPTION 1. Builds the Docker image 2. Saves it to dist\image\dupfinder.tar 3. Copies all installer scripts and source into dist\ Run this from the repo root before copying dist\ to a flash drive. .EXAMPLE .\build-release.ps1 .\build-release.ps1 -SkipBuild # Skip docker build (reuse existing image) #> param( [switch]$SkipBuild, [string]$ImageName = "dupfinder", [string]$ImageTag = "latest" ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $RepoRoot = $PSScriptRoot $DistDir = Join-Path $RepoRoot "dist" $ImageFull = "${ImageName}:${ImageTag}" function Write-Step([string]$msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan } function Write-OK([string]$msg) { Write-Host " [OK] $msg" -ForegroundColor Green } function Write-Fail([string]$msg) { Write-Host " [!!] $msg" -ForegroundColor Red } # ── Check Docker is running ─────────────────────────────────────────────────── Write-Step "Checking Docker..." docker info 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { Write-Fail "Docker is not running. Start Docker Desktop and try again." exit 1 } Write-OK "Docker is running" # ── Build image ─────────────────────────────────────────────────────────────── if (-not $SkipBuild) { Write-Step "Building Docker image ($ImageFull)..." docker build -t $ImageFull --progress=plain $RepoRoot if ($LASTEXITCODE -ne 0) { Write-Fail "Docker build failed."; exit 1 } Write-OK "Image built: $ImageFull" } else { Write-Step "Skipping build (-SkipBuild). Checking image exists..." $exists = docker images $ImageFull --format "{{.ID}}" 2>$null if (-not $exists) { Write-Fail "Image $ImageFull not found locally. Remove -SkipBuild to build it." exit 1 } Write-OK "Image found: $ImageFull" } # ── Clean dist\ ────────────────────────────────────────────────────────────── Write-Step "Preparing dist\ directory..." if (Test-Path $DistDir) { Remove-Item $DistDir -Recurse -Force } New-Item -ItemType Directory -Path $DistDir | Out-Null New-Item -ItemType Directory -Path "$DistDir\image" | Out-Null New-Item -ItemType Directory -Path "$DistDir\source" | Out-Null New-Item -ItemType Directory -Path "$DistDir\assets" | Out-Null Write-OK "dist\ ready" # ── Save Docker image ───────────────────────────────────────────────────────── Write-Step "Saving Docker image to dist\image\dupfinder.tar (this may take a minute)..." docker save -o "$DistDir\image\dupfinder.tar" $ImageFull if ($LASTEXITCODE -ne 0) { Write-Fail "docker save failed."; exit 1 } $tarSize = [math]::Round((Get-Item "$DistDir\image\dupfinder.tar").Length / 1MB, 1) Write-OK "Image saved (${tarSize} MB)" # ── Copy installer scripts ──────────────────────────────────────────────────── Write-Step "Copying installer scripts..." Copy-Item "$RepoRoot\installer\install.ps1" "$DistDir\install.ps1" Copy-Item "$RepoRoot\installer\uninstall.ps1" "$DistDir\uninstall.ps1" Copy-Item "$RepoRoot\installer\dupfinder-start-stop.ps1" "$DistDir\dupfinder-start-stop.ps1" Copy-Item "$RepoRoot\docker-compose.yml" "$DistDir\docker-compose.yml" # install.bat launcher (no-click PS1 execution for non-technical users) @' @echo off echo Starting DupFinder installer... PowerShell -ExecutionPolicy Bypass -File "%~dp0install.ps1" pause '@ | Set-Content "$DistDir\INSTALL.bat" -Encoding ASCII Write-OK "Scripts copied" # ── Copy source (fallback build) ────────────────────────────────────────────── Write-Step "Copying source files (offline build fallback)..." $excludeDirs = @('dist', '__pycache__', 'data', '.git', '.claude', 'installer') $excludeFiles = @('*.db', '*.db-shm', '*.db-wal', '*.pyc', '*.pyo') Get-ChildItem $RepoRoot -Recurse | Where-Object { $item = $_ $skip = $false foreach ($d in $excludeDirs) { if ($item.FullName -match [regex]::Escape($d)) { $skip = $true } } foreach ($f in $excludeFiles) { if ($item.Name -like $f) { $skip = $true } } -not $skip } | ForEach-Object { $rel = $_.FullName.Substring($RepoRoot.Length + 1) $dst = Join-Path "$DistDir\source" $rel if ($_.PSIsContainer) { New-Item -ItemType Directory -Path $dst -Force | Out-Null } else { $dstDir = Split-Path $dst -Parent if (-not (Test-Path $dstDir)) { New-Item -ItemType Directory -Path $dstDir -Force | Out-Null } Copy-Item $_.FullName $dst -Force } } Write-OK "Source copied" # ── README for flash drive ──────────────────────────────────────────────────── @" DupFinder Installer =================== Requirements: - Windows 10/11 (64-bit) - Docker Desktop for Windows (if not installed, the installer will guide you) To install: 1. Right-click INSTALL.bat -> "Run as administrator" OR Open PowerShell as Administrator and run: PowerShell -ExecutionPolicy Bypass -File install.ps1 2. Follow the prompts (photos path, data path) 3. A "DupFinder" shortcut will appear on the desktop when done. To uninstall: Run uninstall.ps1 as Administrator. Built: $(Get-Date -Format 'yyyy-MM-dd HH:mm') Image: $ImageFull "@ | Set-Content "$DistDir\README.txt" -Encoding UTF8 # ── Summary ─────────────────────────────────────────────────────────────────── $totalMB = [math]::Round((Get-ChildItem $DistDir -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB, 1) Write-Host "" Write-Host "============================================" -ForegroundColor Green Write-Host " Build complete! dist\ is ${totalMB} MB total" -ForegroundColor Green Write-Host " Copy the dist\ folder to your flash drive." -ForegroundColor Green Write-Host "============================================" -ForegroundColor Green