From dd388a74698d97ec01679efc2a22bf689f814b41 Mon Sep 17 00:00:00 2001 From: Cametendo Date: Tue, 26 May 2026 13:01:20 +0200 Subject: [PATCH] Add Windows Terminal installer (install.ps1 / install.bat) install.ps1 copies bash.exe to %LOCALAPPDATA%\Programs\BashForWindows, adds it to the user PATH, and injects a "Bash for Windows" profile into Windows Terminal's settings.json so the shell appears in the + dropdown. install.bat is a double-click wrapper that bypasses the PS execution policy. build.sh --release produces a release/ folder ready to zip and distribute. Supports -Uninstall flag to cleanly remove everything. Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + build.sh | 29 ++++++- install.bat | 19 +++++ install.ps1 | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 4 deletions(-) create mode 100644 install.bat create mode 100644 install.ps1 diff --git a/.gitignore b/.gitignore index af80efe..d14335b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ +release/ *.exe *.o tmp/ diff --git a/build.sh b/build.sh index 70ffc13..67bead7 100755 --- a/build.sh +++ b/build.sh @@ -2,18 +2,39 @@ set -e BUILD_DIR="build" +RELEASE_DIR="release" mkdir -p "$BUILD_DIR" echo "=== Building bash-for-windows ===" -# Linux build -echo " -> Linux..." +# Linux debug build +echo " -> Linux (debug)..." go build -ldflags="-s -w" -o "$BUILD_DIR/bash-windows" . -# Windows cross-compile +# Windows release build echo " -> Windows (x86_64)..." -GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o "$BUILD_DIR/bash-windows.exe" . +GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \ + go build -ldflags="-s -w" -o "$BUILD_DIR/bash-windows.exe" . echo "" echo "=== Build complete ===" ls -lh "$BUILD_DIR/" + +# ── Release package ─────────────────────────────────────────────────────────── +if [[ "${1:-}" == "--release" ]]; then + echo "" + echo "=== Building release package ===" + rm -rf "$RELEASE_DIR" + mkdir -p "$RELEASE_DIR" + + cp "$BUILD_DIR/bash-windows.exe" "$RELEASE_DIR/bash.exe" + cp install.ps1 "$RELEASE_DIR/" + cp install.bat "$RELEASE_DIR/" + + echo "" + echo "Release folder: $RELEASE_DIR/" + ls -lh "$RELEASE_DIR/" + echo "" + echo "Distribute the contents of $RELEASE_DIR/ as a zip." + echo "Users run install.bat (or install.ps1) to install." +fi diff --git a/install.bat b/install.bat new file mode 100644 index 0000000..2d7092e --- /dev/null +++ b/install.bat @@ -0,0 +1,19 @@ +@echo off +setlocal + +REM Run the PowerShell installer from the same directory as this .bat file. +REM Works even if PowerShell's execution policy is Restricted. + +echo. +echo bash-for-windows installer +echo. + +powershell.exe -NoProfile -ExecutionPolicy Bypass ^ + -File "%~dp0install.ps1" %* + +if %ERRORLEVEL% NEQ 0 ( + echo. + echo Installation failed. See error above. + pause + exit /b %ERRORLEVEL% +) diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..0996862 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,216 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Installs bash-for-windows and registers it as a Windows Terminal profile. +.DESCRIPTION + Copies bash.exe to a permanent location, adds it to the user PATH, + and adds a "Bash for Windows" entry to Windows Terminal so it appears + in the new-tab dropdown. +.EXAMPLE + .\install.ps1 + .\install.ps1 -InstallDir "$env:APPDATA\BashForWindows" + .\install.ps1 -NoPath -NoTerminal + .\install.ps1 -Uninstall +#> + +param( + [string]$InstallDir = "$env:LOCALAPPDATA\Programs\BashForWindows", + [switch]$NoPath, + [switch]$NoTerminal, + [switch]$Uninstall +) + +$ErrorActionPreference = "Stop" + +$ExeName = "bash.exe" +$ProfileGuid = "{7a86baf2-f5b9-4b78-b3f8-5e1e4f3d9b2a}" +$ProfileName = "Bash for Windows" + +# ── Pretty output helpers ───────────────────────────────────────────────────── + +function Write-Header { + Write-Host "" + Write-Host " bash-for-windows" -ForegroundColor White -NoNewline + Write-Host " installer" -ForegroundColor DarkGray + Write-Host " ─────────────────────────────────────────" -ForegroundColor DarkGray +} + +function Write-Step { param([string]$s) Write-Host "`n ▸ $s" -ForegroundColor Cyan } +function Write-Ok { param([string]$s) Write-Host " ✓ $s" -ForegroundColor Green } +function Write-Warn { param([string]$s) Write-Host " ⚠ $s" -ForegroundColor Yellow } +function Write-Fatal { param([string]$s) Write-Host " ✗ $s" -ForegroundColor Red; exit 1 } + +# ── Locate source bash.exe (must live next to this script) ──────────────────── + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$SourceExe = Join-Path $ScriptDir $ExeName + +if (-not (Test-Path $SourceExe)) { + Write-Header + Write-Fatal "'$ExeName' not found next to this script. Expected: $SourceExe" +} + +# ── Windows Terminal settings.json locations ────────────────────────────────── + +function Get-WTSettingsPath { + @( + "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json", + "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\LocalState\settings.json", + "$env:LOCALAPPDATA\Microsoft\Windows Terminal\settings.json" + ) | Where-Object { Test-Path $_ } | Select-Object -First 1 +} + +# ── PATH helpers ────────────────────────────────────────────────────────────── + +function Add-ToUserPath { + param([string]$Dir) + $current = [Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | + Where-Object { $_ -ne "" } + if ($Dir -in $current) { + Write-Warn "Already in user PATH: $Dir" + return + } + [Environment]::SetEnvironmentVariable("PATH", (($current + $Dir) -join ";"), "User") + Write-Ok "Added to user PATH: $Dir" + Write-Warn "Re-open your terminal for the PATH change to take effect." +} + +function Remove-FromUserPath { + param([string]$Dir) + $current = [Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | + Where-Object { $_ -ne "" -and $_ -ne $Dir } + [Environment]::SetEnvironmentVariable("PATH", ($current -join ";"), "User") + Write-Ok "Removed from user PATH: $Dir" +} + +# ── Windows Terminal profile helpers ────────────────────────────────────────── + +function Register-WTProfile { + param([string]$ExePath) + + $settingsPath = Get-WTSettingsPath + if (-not $settingsPath) { + Write-Warn "Windows Terminal not found — skipping profile registration." + Write-Warn "Install Windows Terminal from the Microsoft Store and re-run the installer." + return + } + + try { + $raw = Get-Content $settingsPath -Raw -Encoding UTF8 + $json = $raw | ConvertFrom-Json + } catch { + Write-Warn "Could not parse settings.json: $_" + return + } + + # Build the profile object + $newProfile = [PSCustomObject]@{ + guid = $ProfileGuid + name = $ProfileName + commandline = $ExePath + startingDirectory = "%USERPROFILE%" + hidden = $false + } + + # Ensure profiles.list exists + if (-not $json.PSObject.Properties["profiles"]) { + Add-Member -InputObject $json -MemberType NoteProperty ` + -Name "profiles" -Value ([PSCustomObject]@{ list = @() }) + } + if (-not $json.profiles.PSObject.Properties["list"]) { + Add-Member -InputObject $json.profiles -MemberType NoteProperty ` + -Name "list" -Value @() -Force + } + + $list = @($json.profiles.list) + + # Find existing entry by GUID + $existingIdx = -1 + for ($i = 0; $i -lt $list.Count; $i++) { + if ($list[$i].guid -eq $ProfileGuid) { $existingIdx = $i; break } + } + + if ($existingIdx -ge 0) { + $list[$existingIdx] = $newProfile + Write-Ok "Updated existing '$ProfileName' profile." + } else { + # Prepend so it appears near the top of the dropdown + $list = @($newProfile) + $list + Write-Ok "Added '$ProfileName' profile." + } + + $json.profiles.list = $list + + # Write back — high depth so nested keybindings etc. aren't truncated + $updated = $json | ConvertTo-Json -Depth 50 + [System.IO.File]::WriteAllText($settingsPath, $updated, [System.Text.UTF8Encoding]::new($false)) + Write-Ok "Saved settings: $settingsPath" + Write-Ok "Open Windows Terminal, click '+', and select '$ProfileName'." +} + +function Remove-WTProfile { + $settingsPath = Get-WTSettingsPath + if (-not $settingsPath) { return } + + try { + $json = Get-Content $settingsPath -Raw -Encoding UTF8 | ConvertFrom-Json + } catch { return } + + if (-not $json.profiles -or -not $json.profiles.list) { return } + + $list = @($json.profiles.list) | Where-Object { $_.guid -ne $ProfileGuid } + $json.profiles.list = $list + $updated = $json | ConvertTo-Json -Depth 50 + [System.IO.File]::WriteAllText($settingsPath, $updated, [System.Text.UTF8Encoding]::new($false)) + Write-Ok "Removed '$ProfileName' from Windows Terminal." +} + +# ───────────────────────────────────────────────────────────────────────────── +# UNINSTALL +# ───────────────────────────────────────────────────────────────────────────── + +if ($Uninstall) { + Write-Header + Write-Step "Removing files" + if (Test-Path $InstallDir) { + Remove-Item $InstallDir -Recurse -Force + Write-Ok "Deleted: $InstallDir" + } else { + Write-Warn "Not found (already removed?): $InstallDir" + } + + Write-Step "Removing from PATH" + Remove-FromUserPath $InstallDir + + Write-Step "Removing Windows Terminal profile" + Remove-WTProfile + + Write-Host "`n Uninstall complete." -ForegroundColor Green + Write-Host "" + exit 0 +} + +# ───────────────────────────────────────────────────────────────────────────── +# INSTALL +# ───────────────────────────────────────────────────────────────────────────── + +Write-Header + +Write-Step "Installing binary" +New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null +$DestExe = Join-Path $InstallDir $ExeName +Copy-Item -Path $SourceExe -Destination $DestExe -Force +Write-Ok "Copied to: $DestExe" + +if (-not $NoPath) { + Write-Step "Updating user PATH" + Add-ToUserPath $InstallDir +} + +if (-not $NoTerminal) { + Write-Step "Registering Windows Terminal profile" + Register-WTProfile $DestExe +} + +Write-Host "`n Install complete." -ForegroundColor Green +Write-Host ""