From 11c433cb8eff2f6559a6c7bd2bfe959fb056512b Mon Sep 17 00:00:00 2001 From: pablopunk Date: Sun, 5 Jul 2026 19:30:48 +0000 Subject: [PATCH 1/3] feat: add Windows install script --- README.md | 10 ++ scripts/install.ps1 | 260 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 scripts/install.ps1 diff --git a/README.md b/README.md index 3e5954f..cce2075 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,22 @@ ## Installation +### macOS / Linux + ```bash curl -fsSL https://raw.githubusercontent.com/pablopunk/dot/main/scripts/install.sh | bash ``` Downloads the latest binary to `~/.local/bin/dot`. +### Windows PowerShell + +```powershell +irm https://raw.githubusercontent.com/pablopunk/dot/main/scripts/install.ps1 | iex +``` + +Downloads the latest binary to `%LOCALAPPDATA%\Programs\dot\dot.exe` and adds it to your user `PATH`. + ## Quick Start 1. Create a `dot.toml` in your dotfiles repo: diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 0000000..0802986 --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,260 @@ +# dot Windows installer +# Usage: +# irm https://raw.githubusercontent.com/pablopunk/dot/main/scripts/install.ps1 | iex + +[CmdletBinding()] +param( + [switch]$Help, + [switch]$VerboseInstall +) + +$ErrorActionPreference = "Stop" + +function Write-Info { + param([string]$Message) + Write-Host "[INFO] $Message" -ForegroundColor Blue +} + +function Write-Success { + param([string]$Message) + Write-Host "[SUCCESS] $Message" -ForegroundColor Green +} + +function Write-WarningMessage { + param([string]$Message) + Write-Host "[WARNING] $Message" -ForegroundColor Yellow +} + +function Write-ErrorMessage { + param([string]$Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red +} + +function Show-Help { + Write-Host "dot installation script for Windows" + Write-Host "" + Write-Host "Usage:" + Write-Host " irm https://raw.githubusercontent.com/pablopunk/dot/main/scripts/install.ps1 | iex" + Write-Host "" + Write-Host "Options:" + Write-Host " -Help Show this help message" + Write-Host " -VerboseInstall Print verbose output" + Write-Host "" + Write-Host "This script will:" + Write-Host " 1. Detect your OS and architecture" + Write-Host " 2. Download the latest dot binary from GitHub" + Write-Host " 3. Install it to %LOCALAPPDATA%\Programs\dot\dot.exe" + Write-Host " 4. Update your user PATH" + Write-Host "" + Write-Host "Requirements:" + Write-Host " - PowerShell" + Write-Host " - Internet connection" +} + +function Assert-Windows { + if ($env:OS -ne "Windows_NT") { + Write-ErrorMessage "This installer is for Windows only. Use scripts/install.sh on macOS or Linux." + exit 1 + } +} + +function Get-DotArchitecture { + $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant() + + switch ($arch) { + "x64" { return "x64" } + "arm64" { + Write-ErrorMessage "Unsupported architecture: arm64" + Write-WarningMessage "The release workflow currently publishes dot-windows-x64.exe only." + exit 1 + } + default { + Write-ErrorMessage "Unsupported architecture: $arch" + exit 1 + } + } +} + +function Get-LatestRelease { + Write-Info "Fetching latest version..." + + $headers = @{ + "Accept" = "application/vnd.github.v3+json" + "User-Agent" = "dot-installer" + } + + $release = Invoke-RestMethod ` + -Uri "https://api.github.com/repos/pablopunk/dot/releases/latest" ` + -Headers $headers + + if (-not $release.tag_name) { + Write-ErrorMessage "Failed to fetch latest version" + exit 1 + } + + Write-Info "Latest version: $($release.tag_name)" + return $release +} + +function Save-DotBinary { + param( + [object]$Release, + [string]$Architecture + ) + + $binaryName = "dot-windows-$Architecture.exe" + $asset = $Release.assets | Where-Object { $_.name -eq $binaryName } | Select-Object -First 1 + + if (-not $asset) { + Write-ErrorMessage "No release asset found for $binaryName" + exit 1 + } + + $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("dot-install-" + [System.Guid]::NewGuid().ToString()) + New-Item -ItemType Directory -Path $tempDir -Force | Out-Null + + $tempFile = Join-Path $tempDir "dot.exe" + + Write-Info "Downloading from: $($asset.browser_download_url)" + Invoke-WebRequest ` + -Uri $asset.browser_download_url ` + -OutFile $tempFile ` + -UseBasicParsing + + if (-not (Test-Path $tempFile)) { + Write-ErrorMessage "Failed to download binary" + exit 1 + } + + Write-Success "Binary downloaded successfully" + + return @{ + TempDir = $tempDir + TempFile = $tempFile + } +} + +function Install-DotBinary { + param([string]$TempFile) + + $installDir = Join-Path $env:LOCALAPPDATA "Programs\dot" + $dotPath = Join-Path $installDir "dot.exe" + + if (-not (Test-Path $installDir)) { + Write-Info "Creating $installDir" + New-Item -ItemType Directory -Path $installDir -Force | Out-Null + } + + Write-Info "Installing binary to $dotPath" + Move-Item -Path $TempFile -Destination $dotPath -Force + + Write-Success "Binary installed to $dotPath" + + return @{ + InstallDir = $installDir + DotPath = $dotPath + } +} + +function Add-ToUserPath { + param([string]$InstallDir) + + $userPath = [Environment]::GetEnvironmentVariable("Path", "User") + $pathParts = @() + if ($userPath) { + $pathParts = $userPath -split ";" | Where-Object { $_ } + } + + $normalizedInstallDir = $InstallDir.TrimEnd("\") + $isInPath = $false + + foreach ($pathPart in $pathParts) { + if ($pathPart.TrimEnd("\") -ieq $normalizedInstallDir) { + $isInPath = $true + break + } + } + + if ($isInPath) { + Write-Info "$InstallDir is already in user PATH" + return + } + + Write-Info "Adding $InstallDir to user PATH" + + if ($userPath) { + $newPath = "$userPath;$InstallDir" + } else { + $newPath = $InstallDir + } + + [Environment]::SetEnvironmentVariable("Path", $newPath, "User") + $env:Path = "$env:Path;$InstallDir" + + Write-Success "Updated user PATH" + Write-WarningMessage "Restart your terminal to use dot from new sessions" +} + +function Test-Installation { + param([string]$DotPath) + + if (-not (Test-Path $DotPath)) { + Write-ErrorMessage "Installation failed: $DotPath does not exist" + exit 1 + } + + Write-Success "Installation verified: $DotPath exists" + + try { + & $DotPath --help *> $null + Write-Success "Binary runs successfully" + } catch { + Write-WarningMessage "Binary exists but may not run correctly" + } +} + +function Remove-TempDir { + param([string]$TempDir) + + if ($TempDir -and (Test-Path $TempDir)) { + Remove-Item -Path $TempDir -Recurse -Force + } +} + +function Main { + if ($Help) { + Show-Help + return + } + + if ($VerboseInstall) { + Set-PSDebug -Trace 1 + } + + Write-Info "Starting dot installation..." + + $tempDir = $null + + try { + Assert-Windows + $architecture = Get-DotArchitecture + Write-Info "Detected OS: windows, Architecture: $architecture" + + $release = Get-LatestRelease + $download = Save-DotBinary -Release $release -Architecture $architecture + $tempDir = $download.TempDir + + $installation = Install-DotBinary -TempFile $download.TempFile + Add-ToUserPath -InstallDir $installation.InstallDir + Test-Installation -DotPath $installation.DotPath + + Write-Success "dot installation completed successfully!" + Write-Info "You can now use 'dot' command (restart your terminal first if needed)" + Write-Info "For help, run: dot --help" + Write-Info "To get started, create a dot.toml file in your dotfiles directory" + } finally { + Remove-TempDir -TempDir $tempDir + } +} + +Main From 087e44a4fa33fedb1679eb59825f89472e277c9a Mon Sep 17 00:00:00 2001 From: pablopunk Date: Sun, 5 Jul 2026 19:32:31 +0000 Subject: [PATCH 2/3] fix: support Windows PowerShell architecture detection --- scripts/install.ps1 | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 0802986..aa2ba5e 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -59,9 +59,20 @@ function Assert-Windows { } function Get-DotArchitecture { - $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant() + $arch = $env:PROCESSOR_ARCHITECTURE - switch ($arch) { + if (-not $arch -and $env:PROCESSOR_ARCHITEW6432) { + $arch = $env:PROCESSOR_ARCHITEW6432 + } + + if (-not $arch) { + Write-ErrorMessage "Could not detect Windows architecture" + exit 1 + } + + switch ($arch.ToLowerInvariant()) { + "amd64" { return "x64" } + "x86_64" { return "x64" } "x64" { return "x64" } "arm64" { Write-ErrorMessage "Unsupported architecture: arm64" From ef02236dd23363f1fc0253fb5817d16c6a03ac5c Mon Sep 17 00:00:00 2001 From: pablopunk Date: Sun, 5 Jul 2026 19:38:55 +0000 Subject: [PATCH 3/3] fix: address Windows installer review comments --- scripts/install.ps1 | 100 +++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index aa2ba5e..c4d052d 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -53,8 +53,7 @@ function Show-Help { function Assert-Windows { if ($env:OS -ne "Windows_NT") { - Write-ErrorMessage "This installer is for Windows only. Use scripts/install.sh on macOS or Linux." - exit 1 + throw "This installer is for Windows only. Use scripts/install.sh on macOS or Linux." } } @@ -66,8 +65,7 @@ function Get-DotArchitecture { } if (-not $arch) { - Write-ErrorMessage "Could not detect Windows architecture" - exit 1 + throw "Could not detect Windows architecture" } switch ($arch.ToLowerInvariant()) { @@ -75,13 +73,11 @@ function Get-DotArchitecture { "x86_64" { return "x64" } "x64" { return "x64" } "arm64" { - Write-ErrorMessage "Unsupported architecture: arm64" - Write-WarningMessage "The release workflow currently publishes dot-windows-x64.exe only." - exit 1 + Write-WarningMessage "Detected Windows ARM64; installing the x64 binary under Windows emulation." + return "x64" } default { - Write-ErrorMessage "Unsupported architecture: $arch" - exit 1 + throw "Unsupported architecture: $arch" } } } @@ -99,8 +95,7 @@ function Get-LatestRelease { -Headers $headers if (-not $release.tag_name) { - Write-ErrorMessage "Failed to fetch latest version" - exit 1 + throw "Failed to fetch latest version" } Write-Info "Latest version: $($release.tag_name)" @@ -117,8 +112,7 @@ function Save-DotBinary { $asset = $Release.assets | Where-Object { $_.name -eq $binaryName } | Select-Object -First 1 if (-not $asset) { - Write-ErrorMessage "No release asset found for $binaryName" - exit 1 + throw "No release asset found for $binaryName" } $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("dot-install-" + [System.Guid]::NewGuid().ToString()) @@ -133,8 +127,7 @@ function Save-DotBinary { -UseBasicParsing if (-not (Test-Path $tempFile)) { - Write-ErrorMessage "Failed to download binary" - exit 1 + throw "Failed to download binary" } Write-Success "Binary downloaded successfully" @@ -170,55 +163,73 @@ function Install-DotBinary { function Add-ToUserPath { param([string]$InstallDir) - $userPath = [Environment]::GetEnvironmentVariable("Path", "User") - $pathParts = @() - if ($userPath) { - $pathParts = $userPath -split ";" | Where-Object { $_ } + $environmentKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey("Environment", $true) + if (-not $environmentKey) { + throw "Could not open HKCU\Environment for PATH update" } - $normalizedInstallDir = $InstallDir.TrimEnd("\") - $isInPath = $false + try { + $userPath = [string]$environmentKey.GetValue( + "Path", + "", + [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames + ) + + $pathParts = @() + if ($userPath) { + $pathParts = $userPath -split ";" | Where-Object { $_ } + } - foreach ($pathPart in $pathParts) { - if ($pathPart.TrimEnd("\") -ieq $normalizedInstallDir) { - $isInPath = $true - break + $normalizedInstallDir = $InstallDir.TrimEnd("\") + $isInPath = $false + + foreach ($pathPart in $pathParts) { + $expandedPathPart = [Environment]::ExpandEnvironmentVariables($pathPart) + if ($pathPart.TrimEnd("\") -ieq $normalizedInstallDir -or $expandedPathPart.TrimEnd("\") -ieq $normalizedInstallDir) { + $isInPath = $true + break + } } - } - if ($isInPath) { - Write-Info "$InstallDir is already in user PATH" - return - } + if ($isInPath) { + Write-Info "$InstallDir is already in user PATH" + return + } - Write-Info "Adding $InstallDir to user PATH" + Write-Info "Adding $InstallDir to user PATH" - if ($userPath) { - $newPath = "$userPath;$InstallDir" - } else { - $newPath = $InstallDir - } + if ($userPath) { + $newPath = "$userPath;$InstallDir" + } else { + $newPath = $InstallDir + } - [Environment]::SetEnvironmentVariable("Path", $newPath, "User") - $env:Path = "$env:Path;$InstallDir" + $environmentKey.SetValue("Path", $newPath, [Microsoft.Win32.RegistryValueKind]::ExpandString) + $env:Path = "$env:Path;$InstallDir" - Write-Success "Updated user PATH" - Write-WarningMessage "Restart your terminal to use dot from new sessions" + Write-Success "Updated user PATH" + Write-WarningMessage "Restart your terminal to use dot from new sessions" + } finally { + $environmentKey.Close() + } } function Test-Installation { param([string]$DotPath) if (-not (Test-Path $DotPath)) { - Write-ErrorMessage "Installation failed: $DotPath does not exist" - exit 1 + throw "Installation failed: $DotPath does not exist" } Write-Success "Installation verified: $DotPath exists" try { & $DotPath --help *> $null - Write-Success "Binary runs successfully" + if ($LASTEXITCODE -eq 0) { + Write-Success "Binary runs successfully" + } else { + Write-WarningMessage "Binary exists but may not run correctly" + } } catch { Write-WarningMessage "Binary exists but may not run correctly" } @@ -263,6 +274,9 @@ function Main { Write-Info "You can now use 'dot' command (restart your terminal first if needed)" Write-Info "For help, run: dot --help" Write-Info "To get started, create a dot.toml file in your dotfiles directory" + } catch { + Write-ErrorMessage $_.Exception.Message + return } finally { Remove-TempDir -TempDir $tempDir }