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 <noreply@anthropic.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
build/
|
build/
|
||||||
|
release/
|
||||||
*.exe
|
*.exe
|
||||||
*.o
|
*.o
|
||||||
tmp/
|
tmp/
|
||||||
|
|||||||
29
build.sh
29
build.sh
@@ -2,18 +2,39 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
BUILD_DIR="build"
|
BUILD_DIR="build"
|
||||||
|
RELEASE_DIR="release"
|
||||||
mkdir -p "$BUILD_DIR"
|
mkdir -p "$BUILD_DIR"
|
||||||
|
|
||||||
echo "=== Building bash-for-windows ==="
|
echo "=== Building bash-for-windows ==="
|
||||||
|
|
||||||
# Linux build
|
# Linux debug build
|
||||||
echo " -> Linux..."
|
echo " -> Linux (debug)..."
|
||||||
go build -ldflags="-s -w" -o "$BUILD_DIR/bash-windows" .
|
go build -ldflags="-s -w" -o "$BUILD_DIR/bash-windows" .
|
||||||
|
|
||||||
# Windows cross-compile
|
# Windows release build
|
||||||
echo " -> Windows (x86_64)..."
|
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 ""
|
||||||
echo "=== Build complete ==="
|
echo "=== Build complete ==="
|
||||||
ls -lh "$BUILD_DIR/"
|
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
|
||||||
|
|||||||
19
install.bat
Normal file
19
install.bat
Normal file
@@ -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%
|
||||||
|
)
|
||||||
216
install.ps1
Normal file
216
install.ps1
Normal file
@@ -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 ""
|
||||||
Reference in New Issue
Block a user