Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ docker run --rm squarebox:test bash -c '
| `setup.sh` | First-run interactive setup (AI tools, editors, SDKs) |
| `install.sh` | Host-side install script for Linux/macOS (clone, build, create container) |
| `install.ps1` | Host-side install script for Windows/PowerShell 7+ |
| `uninstall.sh` | Host-side uninstall script for Linux/macOS/Git Bash |
| `uninstall.ps1` | Host-side uninstall script for Windows/PowerShell 7+ |
| `scripts/squarebox-update.sh` | In-container tool updater (`sqrbx-update`) |
| `scripts/update-versions.sh` | Fetches latest Dockerfile-tier releases and updates checksums |
| `checksums.txt` | SHA256 checksums for Dockerfile binary tools |
Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,13 +377,32 @@ using `gh codespace ssh`.
Uninstall
---------

docker stop squarebox 2>/dev/null; docker rm squarebox
docker rmi squarebox
rm -rf ~/squarebox
sqrbx-uninstall

Then remove the `sqrbx` and `sqrbx-rebuild` aliases from your shell config
(`~/.bashrc`, `~/.zshrc`, or `~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1`).
Back up `~/squarebox/workspace` first if you need your code.
Removes the container, image, and shell integration but **keeps**
`~/squarebox` (including `workspace/`) so your code is safe by default.
Pass `--purge` to also remove `~/squarebox`:

sqrbx-uninstall --purge

A second confirmation is required if `~/squarebox/workspace` is non-empty.
Pass `-y` (or `-Yes` on PowerShell) to skip all prompts for scripting.
Idempotent: safe to re-run once uninstalled.

**Windows (PowerShell 7+):**

sqrbx-uninstall # keep ~/squarebox
sqrbx-uninstall -Purge # also remove ~/squarebox
sqrbx-uninstall -Yes # skip confirmations

**Broken-state recovery** (e.g. shell functions are missing, or after partial
install): run the script directly from the install directory:

~/squarebox/uninstall.sh # Linux / macOS / Git Bash
~/squarebox/uninstall.ps1 # Windows PowerShell

Start a new shell afterwards (or `exec bash` / `exec zsh`) so the `sqrbx` and
`squarebox` functions are dropped from the current session.

Make it your own
-----------------
Expand Down
13 changes: 11 additions & 2 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ if (Test-Path $PROFILE) {
if ($line -match '^# >>> squarebox >>>') { $skip = $true; continue }
if ($line -match '^# <<< squarebox <<<') { $skip = $false; continue }
if ($skip) { continue }
if ($line -match '^\s*function\s+(sqrbx|squarebox|sqrbx-rebuild|squarebox-rebuild)\s*(\{|$)') { continue }
if ($line -match '^\s*function\s+(sqrbx|squarebox|sqrbx-rebuild|squarebox-rebuild|sqrbx-uninstall|squarebox-uninstall)\s*(\{|$)') { continue }
$filtered.Add($line)
}
Set-Content -Path $PROFILE -Value ($filtered -join "`n")
Expand All @@ -227,8 +227,15 @@ if (Test-Path $PROFILE) {
$profileBlock = @'
# >>> squarebox >>>
# squarebox shell integration - managed by install.ps1.
Remove-Item Alias:sqrbx, Alias:squarebox, Alias:sqrbx-rebuild, Alias:squarebox-rebuild -ErrorAction SilentlyContinue
Remove-Item Alias:sqrbx, Alias:squarebox, Alias:sqrbx-rebuild, Alias:squarebox-rebuild, Alias:sqrbx-uninstall, Alias:squarebox-uninstall -ErrorAction SilentlyContinue
function sqrbx {
# Dispatch subcommands (currently: uninstall) so 'sqrbx uninstall' is
# symmetric with the standalone sqrbx-uninstall function below.
if ($args.Count -gt 0 -and $args[0] -eq 'uninstall') {
$rest = if ($args.Count -gt 1) { $args[1..($args.Count - 1)] } else { @() }
& "__INSTALL_DIR__\uninstall.ps1" @rest
return
}
# If the container was left running after an ungraceful exit (closed
# terminal instead of 'exit'), attaching to PID1 bash drops you onto a
# prompt it already printed to the dead TTY - blinking cursor, no output.
Expand All @@ -242,6 +249,8 @@ function sqrbx {
function squarebox { sqrbx @args }
function sqrbx-rebuild { & "__INSTALL_DIR__\install.ps1" @args }
function squarebox-rebuild { sqrbx-rebuild @args }
function sqrbx-uninstall { & "__INSTALL_DIR__\uninstall.ps1" @args }
function squarebox-uninstall { sqrbx-uninstall @args }
# <<< squarebox <<<
'@
$profileBlock = $profileBlock -replace '__INSTALL_DIR__', $InstallDir
Expand Down
15 changes: 12 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,15 @@ SQRBX_INIT="${HOME}/.squarebox-shell-init"
cat > "$SQRBX_INIT" <<SQRBXEOF
# Managed by squarebox install.sh — overwritten on every install.
# Drop any stale aliases with these names so they don't shadow the functions.
unalias sqrbx squarebox sqrbx-rebuild squarebox-rebuild 2>/dev/null || true
unalias sqrbx squarebox sqrbx-rebuild squarebox-rebuild sqrbx-uninstall squarebox-uninstall 2>/dev/null || true
sqrbx() {
# Dispatch subcommands (currently: uninstall) so \`sqrbx uninstall\` is
# symmetric with the standalone \`sqrbx-uninstall\` function below.
if [ "\${1:-}" = "uninstall" ]; then
shift
"${INSTALL_DIR}/uninstall.sh" "\$@"
return
fi
# If the container was left running after an ungraceful exit (closed
# terminal instead of \`exit\`), attaching to PID1 bash drops you onto a
# prompt it already printed to the dead TTY — blinking cursor, no
Expand All @@ -219,6 +226,8 @@ sqrbx() {
squarebox() { sqrbx "\$@"; }
sqrbx-rebuild() { "${INSTALL_DIR}/install.sh" "\$@"; }
squarebox-rebuild() { sqrbx-rebuild "\$@"; }
sqrbx-uninstall() { "${INSTALL_DIR}/uninstall.sh" "\$@"; }
squarebox-uninstall() { sqrbx-uninstall "\$@"; }
SQRBXEOF

# Scrub legacy content from $SHELL_RC and append a fresh sentinel block that
Expand All @@ -229,8 +238,8 @@ if [ -f "$SHELL_RC" ]; then
/^# >>> squarebox >>>/ { skip=1; next }
/^# <<< squarebox <<</ { skip=0; next }
skip { next }
/^[[:space:]]*alias[[:space:]]+(sqrbx|squarebox|sqrbx-rebuild|squarebox-rebuild)=/ { next }
/^(sqrbx|squarebox|sqrbx-rebuild|squarebox-rebuild)\(\)[[:space:]]*\{/ { next }
/^[[:space:]]*alias[[:space:]]+(sqrbx|squarebox|sqrbx-rebuild|squarebox-rebuild|sqrbx-uninstall|squarebox-uninstall)=/ { next }
/^(sqrbx|squarebox|sqrbx-rebuild|squarebox-rebuild|sqrbx-uninstall|squarebox-uninstall)\(\)[[:space:]]*\{/ { next }
{ print }
' "$SHELL_RC" > "$_rc_tmp" && mv "$_rc_tmp" "$SHELL_RC"
_rc_tmp=""
Expand Down
255 changes: 255 additions & 0 deletions uninstall.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Windows uninstaller for squarebox.
.DESCRIPTION
Removes the squarebox container, image, PowerShell profile sentinel block,
and (optionally, via -Purge) the install directory.

Usage:
.\uninstall.ps1 # remove container, image, profile block; keep ~/squarebox
.\uninstall.ps1 -Purge # additionally rm -rf ~/squarebox
.\uninstall.ps1 -Yes # skip all confirmations
.\uninstall.ps1 -Runtime podman # force podman

Broken-state recovery: run "%USERPROFILE%\squarebox\uninstall.ps1" directly
if the sqrbx-uninstall function is not available.

Idempotent: safe to run when nothing is installed.
#>
#Requires -Version 7.0

param(
[switch]$Purge,
[Alias('y')][switch]$Yes,
[ValidateSet('docker', 'podman')]
[string]$Runtime
)

$ErrorActionPreference = 'Stop'

$ImageName = 'squarebox'
$ContainerName = 'squarebox'
$InstallDir = Join-Path $env:USERPROFILE 'squarebox'

function Abort([string]$msg) {
Write-Host "Error: $msg" -ForegroundColor Red
exit 1
}

function Test-RuntimeHasState([string]$rt) {
$names = & $rt ps -a --format '{{.Names}}' 2>$null
if ($LASTEXITCODE -eq 0 -and $names -and ($names -split "`n" | Where-Object { $_.Trim() -eq $ContainerName })) {
return $true
}
$images = & $rt images --format '{{.Repository}}' 2>$null
if ($LASTEXITCODE -eq 0 -and $images -and ($images -split "`n" | Where-Object { $_.Trim() -eq $ImageName })) {
return $true
}
return $false
}

# Runtime detection: -Runtime > $env:SQUAREBOX_RUNTIME > auto-detect. Auto-detect
# prefers the runtime that has squarebox state; if both do, prefer docker and
# warn about podman (matches install.ps1's preference).
$hasDocker = [bool](Get-Command docker -ErrorAction SilentlyContinue)
$hasPodman = [bool](Get-Command podman -ErrorAction SilentlyContinue)

$SelectedRuntime = $null
$SecondaryRuntime = $null

if ($Runtime) {
$SelectedRuntime = $Runtime
if (-not (Get-Command $SelectedRuntime -ErrorAction SilentlyContinue)) {
Abort "-Runtime $SelectedRuntime but '$SelectedRuntime' is not installed."
}
} elseif ($env:SQUAREBOX_RUNTIME) {
if ($env:SQUAREBOX_RUNTIME -notin @('docker', 'podman')) {
Abort "SQUAREBOX_RUNTIME must be 'docker' or 'podman' (got '$($env:SQUAREBOX_RUNTIME)')."
}
$SelectedRuntime = $env:SQUAREBOX_RUNTIME
if (-not (Get-Command $SelectedRuntime -ErrorAction SilentlyContinue)) {
Abort "SQUAREBOX_RUNTIME=$SelectedRuntime but '$SelectedRuntime' is not installed."
}
} else {
$dockerState = $hasDocker -and (Test-RuntimeHasState 'docker')
$podmanState = $hasPodman -and (Test-RuntimeHasState 'podman')

if ($dockerState -and $podmanState) {
$SelectedRuntime = 'docker'
$SecondaryRuntime = 'podman'
} elseif ($dockerState) {
$SelectedRuntime = 'docker'
} elseif ($podmanState) {
$SelectedRuntime = 'podman'
} elseif ($hasDocker) {
$SelectedRuntime = 'docker'
} elseif ($hasPodman) {
$SelectedRuntime = 'podman'
}
}

# Probe state.
$hasContainer = $false
$hasImage = $false
if ($SelectedRuntime) {
$names = & $SelectedRuntime ps -a --format '{{.Names}}' 2>$null
if ($LASTEXITCODE -eq 0 -and $names) {
$hasContainer = [bool]($names -split "`n" | Where-Object { $_.Trim() -eq $ContainerName })
}
$images = & $SelectedRuntime images --format '{{.Repository}}' 2>$null
if ($LASTEXITCODE -eq 0 -and $images) {
$hasImage = [bool]($images -split "`n" | Where-Object { $_.Trim() -eq $ImageName })
}
}

$hasProfileBlock = $false
if (Test-Path $PROFILE) {
$hasProfileBlock = [bool](Select-String -Path $PROFILE -Pattern '^# >>> squarebox >>>' -Quiet)
if (-not $hasProfileBlock) {
# Also scrub orphan function defs from a legacy install with no sentinel.
$hasProfileBlock = [bool](Select-String -Path $PROFILE -Pattern '^\s*function\s+(sqrbx|squarebox|sqrbx-rebuild|squarebox-rebuild|sqrbx-uninstall|squarebox-uninstall)\s*(\{|$)' -Quiet)
}
}

$hasInstallDir = Test-Path $InstallDir

# Summary.
Write-Host "squarebox uninstall"
Write-Host "==================="
Write-Host ""

if ($SelectedRuntime) {
Write-Host "Container runtime: $SelectedRuntime"
} else {
Write-Host "Container runtime: none detected (skipping container/image cleanup)"
}

if ($SecondaryRuntime) {
Write-Host ""
Write-Host "Note: squarebox state also detected in $SecondaryRuntime."
Write-Host " This run will clean $SelectedRuntime only. To also clean ${SecondaryRuntime}:"
Write-Host " `$env:SQUAREBOX_RUNTIME = '$SecondaryRuntime'; .\uninstall.ps1"
}

$anythingToDo = $false

Write-Host ""
Write-Host "Will remove:"
if ($hasContainer) { Write-Host " - Container: $ContainerName ($SelectedRuntime)"; $anythingToDo = $true }
if ($hasImage) { Write-Host " - Image: $ImageName ($SelectedRuntime)"; $anythingToDo = $true }
if ($hasProfileBlock) { Write-Host " - Profile block: $PROFILE"; $anythingToDo = $true }
if ($Purge -and $hasInstallDir) {
Write-Host " - Install dir: $InstallDir"
$anythingToDo = $true
}
if (-not $anythingToDo) {
Write-Host " (nothing)"
}

if (-not $Purge -and $hasInstallDir) {
Write-Host ""
Write-Host "Will KEEP:"
Write-Host " - $InstallDir (re-run with -Purge to remove, including workspace)"
}

Write-Host ""

if (-not $anythingToDo) {
Write-Host "Nothing to do - squarebox appears to be already uninstalled."
exit 0
}

# Confirmation.
if (-not $Yes -and [System.Console]::IsInputRedirected) {
Abort "stdin is not a terminal; pass -Yes to run non-interactively."
}

if (-not $Yes) {
$answer = Read-Host "Proceed? [y/N]"
if ($answer -notmatch '^[yY]([eE][sS])?$') {
Write-Host "Aborted." -ForegroundColor Yellow
exit 1
}
}

if ($Purge -and $hasInstallDir -and -not $Yes) {
$workspaceDir = Join-Path $InstallDir 'workspace'
if (Test-Path $workspaceDir) {
$items = @(Get-ChildItem -Force -LiteralPath $workspaceDir -ErrorAction SilentlyContinue)
if ($items.Count -gt 0) {
Write-Host ""
Write-Host "Warning: $workspaceDir contains $($items.Count) item(s)." -ForegroundColor Yellow
Write-Host " Purging will permanently delete them."
$answer = Read-Host "Really purge workspace? [y/N]"
if ($answer -notmatch '^[yY]([eE][sS])?$') {
Write-Host "Aborted." -ForegroundColor Yellow
exit 1
}
}
}
}

# Perform the work. Set-Location away from the install dir so a later remove
# doesn't fail on "directory in use" (or succeed and leave the process in a
# deleted cwd).
Set-Location $env:USERPROFILE

$removedContainer = $false
$removedImage = $false
$removedProfileBlock = $false
$removedInstallDir = $false

if ($hasContainer) {
Write-Host "Stopping container..."
& $SelectedRuntime stop $ContainerName 2>$null | Out-Null
Write-Host "Removing container..."
& $SelectedRuntime rm -f $ContainerName 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) { $removedContainer = $true }
}

if ($hasImage) {
Write-Host "Removing image..."
& $SelectedRuntime rmi -f $ImageName 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) { $removedImage = $true }
}

# Scrub profile. Mirrors install.ps1's strip logic at install.ps1:211-223,
# extended to match -uninstall function names.
if ($hasProfileBlock -and (Test-Path $PROFILE)) {
$lines = Get-Content $PROFILE
$filtered = [System.Collections.Generic.List[string]]::new()
$skip = $false
foreach ($line in $lines) {
if ($line -match '^# >>> squarebox >>>') { $skip = $true; continue }
if ($line -match '^# <<< squarebox <<<') { $skip = $false; continue }
if ($skip) { continue }
if ($line -match '^\s*function\s+(sqrbx|squarebox|sqrbx-rebuild|squarebox-rebuild|sqrbx-uninstall|squarebox-uninstall)\s*(\{|$)') { continue }
$filtered.Add($line)
}
Set-Content -Path $PROFILE -Value ($filtered -join "`n")
$removedProfileBlock = $true
}

if ($Purge -and $hasInstallDir) {
Write-Host "Removing install directory..."
Remove-Item -Recurse -Force -LiteralPath $InstallDir
$removedInstallDir = $true
}

Write-Host ""
Write-Host "Done."
if ($removedContainer) { Write-Host " Removed container $ContainerName from $SelectedRuntime." }
if ($removedImage) { Write-Host " Removed image $ImageName from $SelectedRuntime." }
if ($removedProfileBlock) { Write-Host " Scrubbed squarebox block from $PROFILE." }
if ($removedInstallDir) { Write-Host " Removed $InstallDir." }

if (-not $Purge -and $hasInstallDir) {
Write-Host ""
Write-Host "Kept $InstallDir (including workspace). Remove manually with:"
Write-Host " Remove-Item -Recurse -Force $InstallDir"
}

Write-Host ""
Write-Host "Note: sqrbx, squarebox, and related functions may still be defined in"
Write-Host " your current shell. Start a new PowerShell session to drop them."
Loading
Loading