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
137 changes: 137 additions & 0 deletions .github/workflows/install-standalone.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
name: Install standalone

on:
push:
branches:
- main
paths:
- install.sh
- install.ps1
- .github/workflows/install-standalone.yml
pull_request:
branches:
- '**'
paths:
- install.sh
- install.ps1
- .github/workflows/install-standalone.yml

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Shellcheck
run: shellcheck install.sh
sh:
name: Sh (${{ matrix.os_name }})
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
include:
- os: ubuntu-latest
os_name: Linux
- os: macos-latest
os_name: macOS
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install latest release
run: |
sh install.sh
"$HOME/.local/bin/seam" --version
- name: Check installed completions
if: ${{ runner.os == 'Linux' }}
run: grep 'seam completion --loader' "$HOME/.bashrc"
- name: Install pinned version to a custom directory
run: |
sh install.sh --version v0.29.0 --bin-path "$RUNNER_TEMP/seam-bin" --no-install-completion
"$RUNNER_TEMP/seam-bin/seam" --version | grep -F 0.29.0
- name: Install to XDG_BIN_HOME
run: |
XDG_BIN_HOME="$RUNNER_TEMP/xdg-bin" SEAM_NO_COMPLETIONS=1 \
SEAM_NO_MODIFY_PATH=1 sh install.sh
"$RUNNER_TEMP/xdg-bin/seam" --version
- name: Check modified PATH
if: ${{ runner.os == 'Linux' }}
run: |
grep -xF "export PATH=\"$RUNNER_TEMP/seam-bin:\$PATH\"" "$HOME/.bashrc"
! grep -F "$RUNNER_TEMP/xdg-bin" "$HOME/.bashrc"
- name: Refuse to install on checksum mismatch
run: |
os=$(uname -s | tr '[:upper:]' '[:lower:]')
case "$(uname -m)" in
x86_64 | amd64) arch=x64 ;;
arm64 | aarch64) arch=arm64 ;;
esac
asset="seam-v0.0.1-${os}-${arch}"
mkdir -p "$RUNNER_TEMP/srv/v0.0.1"
echo 'not the real binary' > "$RUNNER_TEMP/srv/v0.0.1/$asset"
printf '%s %s\n' \
'0000000000000000000000000000000000000000000000000000000000000000' \
"$asset" > "$RUNNER_TEMP/srv/v0.0.1/checksums.txt"
python3 -m http.server 8931 --directory "$RUNNER_TEMP/srv" &
server_pid=$!
sleep 2
if SEAM_DOWNLOAD_URL=http://127.0.0.1:8931 \
sh install.sh --version v0.0.1 --bin-path "$RUNNER_TEMP/tamper-bin" \
--no-install-completion; then
echo 'The install must fail on a checksum mismatch' >&2
kill "$server_pid"
exit 1
fi
kill "$server_pid"
if [ -e "$RUNNER_TEMP/tamper-bin/seam" ]; then
echo 'Nothing must be installed on a checksum mismatch' >&2
exit 1
fi
powershell:
name: PowerShell (Windows)
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install latest release
run: |
.\install.ps1
seam --version
- name: Install pinned version to a custom directory
run: |
.\install.ps1 -Version v0.29.0 -BinPath "$env:RUNNER_TEMP\seam-bin"
$version = & "$env:RUNNER_TEMP\seam-bin\seam.exe" --version
if ($version -notmatch '0\.29\.0') {
throw "Unexpected version: $version"
}
- name: Refuse to install on checksum mismatch
run: |
$srv = "$env:RUNNER_TEMP\srv\v0.0.1"
New-Item -ItemType Directory -Path $srv -Force | Out-Null
Set-Content -Path "$srv\seam-v0.0.1-windows-x64.exe" -Value 'not the real binary'
Set-Content -Path "$srv\checksums.txt" -Value ('0' * 64 + ' seam-v0.0.1-windows-x64.exe')
$server = Start-Process python -ArgumentList '-m', 'http.server', '8931', '--directory', "$env:RUNNER_TEMP\srv" -PassThru -NoNewWindow
Start-Sleep -Seconds 2
$env:SEAM_DOWNLOAD_URL = 'http://127.0.0.1:8931'
$failed = $false
try {
.\install.ps1 -Version v0.0.1 -BinPath "$env:RUNNER_TEMP\tamper-bin"
} catch {
$failed = $true
}
$env:SEAM_DOWNLOAD_URL = ''
Stop-Process -Id $server.Id -Force
if (-not $failed) {
throw 'The install must fail on a checksum mismatch'
}
if (Test-Path "$env:RUNNER_TEMP\tamper-bin\seam.exe") {
throw 'Nothing must be installed on a checksum mismatch'
}
38 changes: 33 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,40 @@ $ brew install seam

### Standalone binary

Download a standalone binary for your platform from the [latest GitHub
release]. The macOS binaries are signed with the Seam Labs, Inc. Apple
Developer ID and notarized by Apple, so macOS runs them without a Gatekeeper
prompt.
Install the latest release on Linux and macOS with

Then install the shell completions with
```
$ curl -fsSL https://raw.githubusercontent.com/seamapi/cli/main/install.sh | sh
```

or on Windows with

```
> iwr -useb https://raw.githubusercontent.com/seamapi/cli/main/install.ps1 | iex
```

The script downloads the binary for your platform, verifies its SHA-256
checksum against the release's `checksums.txt` before installing, and runs
`seam completion --install`. It installs to the first of `$SEAM_BIN_PATH`,
`$XDG_BIN_HOME`, `$XDG_DATA_HOME/../bin`, or `~/.local/bin`
(`%LOCALAPPDATA%\seam\bin` on Windows). If the install directory is not on
your `PATH`, the script adds it to your shell configuration, detecting bash,
fish, or zsh the same way as `seam completion --install`. Pass
`--version <tag>` to pin a version, `--bin-path <dir>` to choose the install
directory, and `--no-modify-path` or `--no-install-completion` to leave your
shell configuration untouched, e.g.

```
$ curl -fsSL https://raw.githubusercontent.com/seamapi/cli/main/install.sh \
| sh -s -- --version v0.29.0 --no-install-completion
```

Alternatively, download a standalone binary for your platform from the
[latest GitHub release]. The macOS binaries are signed with the Seam Labs,
Inc. Apple Developer ID and notarized by Apple, so macOS runs them without a
Gatekeeper prompt.

After a manual download, install the shell completions with

```
$ seam completion --install
Expand Down
156 changes: 156 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<#
.SYNOPSIS
Install the Seam CLI on Windows.

.DESCRIPTION
Downloads the standalone seam binary from GitHub Releases, verifies its
SHA-256 checksum against the release's checksums.txt in a temporary
directory, installs it, and adds the install directory to the user PATH.

Run directly from the web (parameters are not available this way, use the
environment variables instead):

iwr -useb https://raw.githubusercontent.com/seamapi/cli/main/install.ps1 | iex

Or download first to pass parameters:

iwr -useb https://raw.githubusercontent.com/seamapi/cli/main/install.ps1 -OutFile install.ps1
.\install.ps1 -Version v0.29.0

.PARAMETER Version
Version to install, e.g. v0.29.0 (default: latest).

.PARAMETER BinPath
Install directory (default: SEAM_BIN_PATH environment variable, then
%LOCALAPPDATA%\seam\bin).

.PARAMETER NoInstallCompletion
Accepted for symmetry with install.sh. Shell completions support bash, fish,
and zsh only, so they are never installed on Windows.

.NOTES
Environment variables: SEAM_BIN_PATH (install directory), SEAM_DOWNLOAD_URL
(base URL for release downloads, for mirrors and testing).
#>
[CmdletBinding()]
param(
[string]$Version = '',
[string]$BinPath = '',
[switch]$NoInstallCompletion
)

$ErrorActionPreference = 'Stop'

$AppName = 'seam'
$Repo = 'seamapi/cli'
$DownloadBase = if ($env:SEAM_DOWNLOAD_URL) {
$env:SEAM_DOWNLOAD_URL
} else {
"https://github.com/$Repo/releases/download"
}

# Windows PowerShell 5.1 defaults to TLS 1.0, which GitHub rejects.
if ($PSVersionTable.PSVersion.Major -lt 6) {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
}

function Get-LatestTag {
$release = Invoke-RestMethod -UseBasicParsing `
-Uri "https://api.github.com/repos/$Repo/releases/latest"
return $release.tag_name
}

function Test-IsWindows {
# $IsWindows does not exist on Windows PowerShell 5.1.
return ($PSVersionTable.PSVersion.Major -lt 6) -or $IsWindows
}

if (Test-IsWindows) {
$arch = $env:PROCESSOR_ARCHITECTURE
if ($arch -eq 'ARM64') {
Write-Host 'No arm64 binary is published for Windows: installing the x64 binary, which runs under emulation.'
} elseif ($arch -ne 'AMD64') {
throw "Unsupported architecture: $arch (supported: AMD64, ARM64)."
}
}

if (-not $Version) {
$Version = Get-LatestTag
} elseif ($Version -notmatch '^v') {
$Version = "v$Version"
}

if (-not $BinPath) {
if ($env:SEAM_BIN_PATH) {
$BinPath = $env:SEAM_BIN_PATH
} elseif ($env:LOCALAPPDATA) {
$BinPath = Join-Path $env:LOCALAPPDATA "$AppName\bin"
} else {
throw 'Cannot determine the install directory: set SEAM_BIN_PATH or LOCALAPPDATA.'
}
}

$asset = "$AppName-$Version-windows-x64.exe"
$assetUrl = "$DownloadBase/$Version/$asset"
$checksumsUrl = "$DownloadBase/$Version/checksums.txt"

$tmpDir = Join-Path ([IO.Path]::GetTempPath()) "$AppName-install-$([Guid]::NewGuid())"
New-Item -ItemType Directory -Path $tmpDir | Out-Null

try {
$assetFile = Join-Path $tmpDir $asset
$checksumsFile = Join-Path $tmpDir 'checksums.txt'

Write-Host "Downloading $assetUrl"
Invoke-WebRequest -UseBasicParsing -Uri $assetUrl -OutFile $assetFile

Write-Host "Verifying the checksum with $checksumsUrl"
Invoke-WebRequest -UseBasicParsing -Uri $checksumsUrl -OutFile $checksumsFile

# checksums.txt is in sha256sum format: '<sha256> <filename>' per line.
$expected = ''
foreach ($line in Get-Content $checksumsFile) {
$parts = $line.Trim() -split '\s+', 2
if ($parts.Count -eq 2 -and $parts[1].TrimStart('*') -eq $asset) {
$expected = $parts[0]
break
}
}
if (-not $expected) {
throw "$asset is not listed in checksums.txt: refusing to install."
}
$actual = (Get-FileHash -Path $assetFile -Algorithm SHA256).Hash
if ($actual -ne $expected) {
throw "Checksum mismatch for ${asset}: refusing to install.`n expected: $expected`n actual: $actual"
}

New-Item -ItemType Directory -Path $BinPath -Force | Out-Null
$target = Join-Path $BinPath "$AppName.exe"
Move-Item -Path $assetFile -Destination $target -Force
Write-Host "Installed $target ($Version)"
} finally {
Remove-Item -Path $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
}

# Add the install directory to the user PATH (registry) and to the current
# session, so seam is available in new shells without manual setup.
if (Test-IsWindows) {
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if (-not $userPath) { $userPath = '' }
$onPath = ($userPath -split ';' | Where-Object { $_ -eq $BinPath }).Count -gt 0
if (-not $onPath) {
$newPath = if ($userPath) { "$userPath;$BinPath" } else { $BinPath }
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host "Added $BinPath to the user PATH. Open a new shell to use $AppName."
}
}
if (($env:PATH -split [IO.Path]::PathSeparator) -notcontains $BinPath) {
$env:PATH = "$BinPath$([IO.Path]::PathSeparator)$env:PATH"
}
if ($env:GITHUB_PATH) {
Add-Content -Path $env:GITHUB_PATH -Value $BinPath
}

if (-not $NoInstallCompletion) {
Write-Host "Shell completions support bash, fish, and zsh only, so they are not installed on Windows. In one of those shells, run: $AppName completion --install"
}
Loading
Loading