From c5169740b4a90e105aeab248b743e9a3dec0ca06 Mon Sep 17 00:00:00 2001 From: rustfuture <121799572+rustfuture@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:16:49 +0300 Subject: [PATCH] Publish prebuilt binaries with each release Pushing a v* tag now builds the CLI for Linux (x86_64, aarch64), macOS (Intel, Apple Silicon) and Windows, smoke-tests --version and attaches the archives with SHA-256 files to the GitHub release. install.sh and install.ps1 download the archive for the current machine and verify its checksum. Bumps the patch version, moves Unreleased into the new CHANGELOG section and adds a "Try the released CLI" section to the quick start. --- .github/workflows/release.yml | 158 +++++++++++++++++++++++++++++ CHANGELOG.md | 3 + Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 18 +++- install.ps1 | 129 +++++++++++++++++++++++ install.sh | 186 ++++++++++++++++++++++++++++++++++ packaging/README.txt | 27 +++++ 8 files changed, 522 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 install.ps1 create mode 100755 install.sh create mode 100644 packaging/README.txt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7b6fa15 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,158 @@ +name: Release + +# Pushing a tag such as v0.4.0 builds the `grainx` CLI on native runners, +# packages it and publishes a GitHub release. Running the workflow by hand +# (workflow_dispatch) builds and uploads the archives as workflow artifacts +# but never publishes. See docs/releasing.md. + +on: + push: + tags: [ "v*" ] + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + prepare: + name: Check version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - uses: actions/checkout@v4 + + - name: Read workspace version and compare with the tag + id: version + run: | + version="$(cargo metadata --no-deps --format-version 1 --locked \ + | jq -r '.packages[] | select(.name == "grainx") | .version')" + if [ -z "$version" ] || [ "$version" = "null" ]; then + echo "::error::could not read the grainx version from Cargo.toml" + exit 1 + fi + if [ "$GITHUB_REF_TYPE" = "tag" ] && [ "$GITHUB_REF_NAME" != "v$version" ]; then + echo "::error::tag $GITHUB_REF_NAME does not match the workspace version $version (expected v$version). Fix the version in Cargo.toml or push the right tag." + exit 1 + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + + build: + name: Build ${{ matrix.target }} + needs: prepare + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + include: + # Linux builds run on the oldest hosted images so the binaries link + # against an older glibc (2.35) and run on most current distributions. + - { target: x86_64-unknown-linux-gnu, os: ubuntu-22.04 } + - { target: aarch64-unknown-linux-gnu, os: ubuntu-22.04-arm } + - { target: x86_64-apple-darwin, os: macos-15-intel } + - { target: aarch64-apple-darwin, os: macos-latest } + - { target: x86_64-pc-windows-msvc, os: windows-latest } + env: + VERSION: ${{ needs.prepare.outputs.version }} + TARGET: ${{ matrix.target }} + steps: + - uses: actions/checkout@v4 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + targets: ${{ matrix.target }} + + - name: Build + run: cargo build --release --locked -p grainx --target ${{ matrix.target }} + + - name: Package (unix) + if: runner.os != 'Windows' + shell: bash + run: | + set -euo pipefail + name="grainx-${VERSION}-${TARGET}" + mkdir -p "dist/${name}" + cp "target/${TARGET}/release/grainx" LICENSE "dist/${name}/" + cp packaging/README.txt "dist/${name}/README.txt" + tar -C dist -czf "dist/${name}.tar.gz" "${name}" + cd dist + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "${name}.tar.gz" > "${name}.tar.gz.sha256" + else + shasum -a 256 "${name}.tar.gz" > "${name}.tar.gz.sha256" + fi + cat "${name}.tar.gz.sha256" + + - name: Package (windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $name = "grainx-$env:VERSION-$env:TARGET" + New-Item -ItemType Directory -Force -Path "dist/$name" | Out-Null + Copy-Item "target/$env:TARGET/release/grainx.exe" "dist/$name/" + Copy-Item LICENSE "dist/$name/" + Copy-Item packaging/README.txt "dist/$name/README.txt" + Compress-Archive -Path "dist/$name" -DestinationPath "dist/$name.zip" + $hash = (Get-FileHash "dist/$name.zip" -Algorithm SHA256).Hash.ToLower() + "$hash $name.zip" | Set-Content -Path "dist/$name.zip.sha256" -Encoding ascii + Get-Content "dist/$name.zip.sha256" + + - name: Smoke test the binary + shell: bash + run: | + set -euo pipefail + exe="target/${TARGET}/release/grainx" + if [ "$RUNNER_OS" = "Windows" ]; then exe="${exe}.exe"; fi + out="$("$exe" --version)" + echo "$out" + case "$out" in + *"${VERSION}"*) ;; + *) echo "::error::binary reports '$out', expected version ${VERSION}"; exit 1 ;; + esac + + - name: Upload archives + uses: actions/upload-artifact@v4 + with: + name: grainx-${{ matrix.target }} + path: | + dist/*.tar.gz + dist/*.zip + dist/*.sha256 + if-no-files-found: error + + release: + name: Publish release + # Only for pushed tags; a manual run stops after the build job. + if: github.ref_type == 'tag' + needs: [prepare, build] + runs-on: ubuntu-latest + permissions: + contents: write + env: + VERSION: ${{ needs.prepare.outputs.version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + pattern: grainx-* + merge-multiple: true + + - name: List files + run: ls -l dist + + - name: Create the GitHub release + run: | + set -euo pipefail + flags="" + case "$VERSION" in *-*) flags="--prerelease" ;; esac + gh release create "$GITHUB_REF_NAME" \ + --repo "$GITHUB_REPOSITORY" \ + --verify-tag \ + --title "grainx $VERSION" \ + --generate-notes \ + $flags \ + dist/*.tar.gz dist/*.zip dist/*.sha256 diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e328f..45392a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ versions, not published artifacts, so the release below is the first one a reade ## [Unreleased] +## [0.1.3] - 2026-10-05 + +- Prebuilt binaries for Linux (x86_64, aarch64), macOS (Intel, Apple Silicon) and Windows on each release, with SHA-256 files, plus `install.sh` and `install.ps1` installers. - Fix dashboard frame clearing, terminal bounds, footer overlap, and scrolling braille graph coordinates with values rising from bottom to top. - Add `--hide-processes` to omit the dashboard process table during screen sharing and recordings. diff --git a/Cargo.lock b/Cargo.lock index 4d386b6..977bae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "grainx" -version = "0.1.2" +version = "0.1.3" dependencies = [ "axum", "chrono", diff --git a/Cargo.toml b/Cargo.toml index ac0168b..f4b9178 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grainx" -version = "0.1.2" +version = "0.1.3" edition = "2024" rust-version = "1.88" diff --git a/README.md b/README.md index f114e91..2fcc2bf 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ grainx shows live computer and process activity in a terminal and can share thos [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) > [!NOTE] -> **Status:** Experimental CLI tool (v0.1.2, pre-1.0); CI checks Linux and macOS. +> **Status:** Experimental CLI tool (v0.1.3, pre-1.0); CI checks Linux and macOS. ![The grainx dashboard with the process table hidden](docs/demo/dashboard.gif) @@ -19,6 +19,22 @@ grainx shows live computer and process activity in a terminal and can share thos ## Quick start +### Try the released CLI (macOS or Linux) + +The prebuilt CLI does not require Rust. The installer downloads a release, checks its SHA-256 checksum, and installs to `~/.local/bin`: + +~~~bash +curl -fsSL https://raw.githubusercontent.com/rustfuture/grainx/main/install.sh | sh +export PATH="$HOME/.local/bin:$PATH" +grainx --version +grainx # dashboard; press q to quit +grainx export --json /tmp/grainx-stats.json --csv /tmp/grainx-stats.csv +~~~ + +Prebuilt Linux binaries require glibc; unsupported platforms can build from source. On Windows, use the [PowerShell installer](install.ps1). + +### Build from source + You need Git and stable Rust 1.88 or newer with Cargo ([rustup](https://rustup.rs/)), plus an interactive terminal for the dashboard. The first build downloads Cargo dependencies. The crate uses Rust edition 2024; the minimum version is checked against the committed dependency lockfile. ~~~bash diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..771f9df --- /dev/null +++ b/install.ps1 @@ -0,0 +1,129 @@ +# Installer for the `grainx` CLI (https://github.com/rustfuture/grainx). +# +# irm https://raw.githubusercontent.com/rustfuture/grainx/main/install.ps1 | iex +# +# Downloads the prebuilt Windows release archive, checks its SHA-256 checksum and +# copies grainx.exe into an install directory. It needs no administrator rights. +# It does NOT change your PATH: if the install directory is not on it, the script +# prints the command to add it. +# +# Environment variables (all optional): +# GRAINX_VERSION Version to install, e.g. 0.3.0 or v0.3.0 (default: latest release) +# GRAINX_INSTALL_DIR Where to put grainx.exe (default: $env:LOCALAPPDATA\grainx\bin) +# GRAINX_DOWNLOAD_BASE Base URL, local directory or file:// URL holding the archive +# and its .sha256 file, instead of the GitHub release. Needs +# GRAINX_VERSION. Meant for mirrors and testing. +# GRAINX_REPO GitHub repository (default: rustfuture/grainx) +# +# Everything runs inside a function and reports problems with `throw`, never `exit`, +# so piping this script to `iex` cannot close your terminal. + +function Install-Tool { + Set-StrictMode -Version 2 + $ErrorActionPreference = 'Stop' + $ProgressPreference = 'SilentlyContinue' # the progress bar makes Windows PowerShell 5.1 downloads very slow + + $repo = if ($env:GRAINX_REPO) { $env:GRAINX_REPO } else { 'rustfuture/grainx' } + $fromSource = "cargo install --git https://github.com/$repo grainx" + + # Windows PowerShell 5.1 does not enable TLS 1.2 by default. + try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch { } + + # --- platform ------------------------------------------------------------- + if ($PSVersionTable.PSVersion.Major -ge 6 -and -not $IsWindows) { + throw "This installer is for Windows. On macOS or Linux use: curl -fsSL https://raw.githubusercontent.com/$repo/main/install.sh | sh" + } + $osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() + switch ($osArch) { + 'X64' { } + 'Arm64' { Write-Host 'Windows on ARM: installing the x64 build, which runs under emulation.' } + default { throw "Unsupported CPU architecture: $osArch. No prebuilt binary is available; if you have Rust (1.88 or newer), build from source: $fromSource" } + } + $target = 'x86_64-pc-windows-msvc' + + # --- version -------------------------------------------------------------- + if ($env:GRAINX_VERSION) { + $version = $env:GRAINX_VERSION.TrimStart('v') + } elseif ($env:GRAINX_DOWNLOAD_BASE) { + throw 'GRAINX_DOWNLOAD_BASE is set, so also set GRAINX_VERSION (for example $env:GRAINX_VERSION = ''0.3.0'').' + } else { + Write-Host 'Looking up the latest release...' + try { + $release = Invoke-RestMethod -UseBasicParsing -Uri "https://api.github.com/repos/$repo/releases/latest" -Headers @{ 'User-Agent' = 'grainx-installer' } + } catch { + throw 'Could not look up the latest release (no release published yet, no network, or the GitHub API rate limit). Set $env:GRAINX_VERSION, e.g. $env:GRAINX_VERSION = ''0.3.0''' + } + $version = ([string]$release.tag_name).TrimStart('v') + } + if ($version -notmatch '^[0-9A-Za-z.+-]+$') { throw "Invalid version: $version" } + + $installDir = if ($env:GRAINX_INSTALL_DIR) { $env:GRAINX_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA 'grainx\bin' } + $name = "grainx-$version-$target" + $archive = "$name.zip" + $base = if ($env:GRAINX_DOWNLOAD_BASE) { $env:GRAINX_DOWNLOAD_BASE } else { "https://github.com/$repo/releases/download/v$version" } + $base = $base.TrimEnd('/', '\') + + $tmp = Join-Path ([IO.Path]::GetTempPath()) ("grainx-install-" + [Guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Path $tmp | Out-Null + try { + Write-Host "Installing grainx $version for $target" + + # --- download --------------------------------------------------------- + foreach ($file in @($archive, "$archive.sha256")) { + $dest = Join-Path $tmp $file + try { + if ($base -match '^https?://') { + Invoke-WebRequest -UseBasicParsing -Uri "$base/$file" -OutFile $dest + } else { + $localBase = if ($base -match '^file://') { ([Uri]$base).LocalPath } else { $base } + Copy-Item -LiteralPath (Join-Path $localBase $file) -Destination $dest + } + } catch { + throw "Could not download $base/$file (is $version a published version for $target?)" + } + } + + # --- verify ----------------------------------------------------------- + $sumLine = (Get-Content -LiteralPath (Join-Path $tmp "$archive.sha256") -TotalCount 1) + $expected = ($sumLine -split '\s+')[0].ToLower() + if ($expected -notmatch '^[0-9a-f]{64}$') { throw "The checksum file $archive.sha256 is not valid." } + $actual = (Get-FileHash -LiteralPath (Join-Path $tmp $archive) -Algorithm SHA256).Hash.ToLower() + if ($expected -ne $actual) { + Write-Error "Checksum mismatch for $archive`n expected: $expected`n actual: $actual" -ErrorAction Continue + throw 'Refusing to install a file that does not match its checksum.' + } + Write-Host 'Checksum OK' + + # --- unpack and install ---------------------------------------------- + Expand-Archive -LiteralPath (Join-Path $tmp $archive) -DestinationPath $tmp -Force + $exe = Join-Path $tmp "$name\grainx.exe" + if (-not (Test-Path -LiteralPath $exe)) { throw "The archive does not contain $name\grainx.exe" } + + New-Item -ItemType Directory -Force -Path $installDir | Out-Null + $installed = Join-Path $installDir 'grainx.exe' + try { + Copy-Item -LiteralPath $exe -Destination $installed -Force + } catch { + throw "Could not write $installed. If grainx is running (for example as an agent hook), close it and try again; or set `$env:GRAINX_INSTALL_DIR to another directory." + } + + Write-Host "Installed: $installed" + & $installed --version + if ($LASTEXITCODE -ne 0) { throw "The installed binary did not run. Build from source instead: $fromSource" } + + # --- PATH hint (never edited automatically) -------------------------- + $dirs = @($env:PATH -split ';' | Where-Object { $_ } | ForEach-Object { $_.TrimEnd('\') }) + if ($dirs -notcontains $installDir.TrimEnd('\')) { + Write-Host '' + Write-Host "$installDir is not on your PATH. To add it for your user account, run:" + Write-Host " [Environment]::SetEnvironmentVariable('Path', [Environment]::GetEnvironmentVariable('Path','User') + ';$installDir', 'User')" + Write-Host 'then open a new terminal. (This installer does not change your PATH.)' + } + Write-Host '' + Write-Host "Next: run 'grainx --help' to get started." + } finally { + Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue + } +} + +Install-Tool diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..9637787 --- /dev/null +++ b/install.sh @@ -0,0 +1,186 @@ +#!/bin/sh +# Installer for the `grainx` CLI (https://github.com/rustfuture/grainx). +# +# curl -fsSL https://raw.githubusercontent.com/rustfuture/grainx/main/install.sh | sh +# +# Downloads the prebuilt release archive for this machine, checks its SHA-256 +# checksum and copies `grainx` into an install directory. It never uses sudo +# and never edits shell startup files. +# +# Environment variables (all optional): +# GRAINX_VERSION Version to install, e.g. 0.3.0 or v0.3.0 (default: latest release) +# GRAINX_INSTALL_DIR Where to put the binary (default: $HOME/.local/bin) +# GRAINX_DOWNLOAD_BASE Base URL (or file:// URL) holding the archive and its +# .sha256 file, instead of the GitHub release. Needs +# GRAINX_VERSION. Meant for mirrors and testing. +# GRAINX_REPO GitHub repository (default: rustfuture/grainx) + +set -eu + +REPO="${GRAINX_REPO:-rustfuture/grainx}" +FROM_SOURCE="cargo install --git https://github.com/${REPO} grainx" + +say() { printf '%s\n' "$*"; } +err() { printf 'grainx-install: error: %s\n' "$*" >&2; } +die() { err "$*"; exit 1; } + +have() { command -v "$1" >/dev/null 2>&1; } + +unsupported() { + err "$1" + err "No prebuilt binary is available for this platform. If you have Rust (1.88 or newer), build from source:" + err " ${FROM_SOURCE}" + exit 1 +} + +detect_target() { + os="$(uname -s)" + arch="$(uname -m)" + + case "$arch" in + x86_64 | amd64) arch="x86_64" ;; + aarch64 | arm64) arch="aarch64" ;; + *) unsupported "unsupported CPU architecture: ${arch}" ;; + esac + + case "$os" in + Linux) + # The Linux binaries are linked against glibc; musl (e.g. Alpine) cannot run them. + if have ldd && ldd --version 2>&1 | grep -qi musl; then + unsupported "this Linux uses musl libc, but the prebuilt binaries need glibc" + fi + TARGET="${arch}-unknown-linux-gnu" + ;; + Darwin) + # A shell running under Rosetta reports x86_64 on an Apple Silicon Mac. + if [ "$arch" = "x86_64" ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = "1" ]; then + arch="aarch64" + fi + TARGET="${arch}-apple-darwin" + ;; + MINGW* | MSYS* | CYGWIN*) + die "this looks like Windows. Use the PowerShell installer instead: irm https://raw.githubusercontent.com/${REPO}/main/install.ps1 | iex" + ;; + *) + unsupported "unsupported operating system: ${os}" + ;; + esac +} + +# fetch URL DEST +fetch() { + if have curl; then + curl -fsSL --retry 3 -o "$2" "$1" || return 1 + elif have wget; then + wget -q -O "$2" "$1" || return 1 + else + die "need curl or wget to download files" + fi +} + +# fetch_stdout URL +fetch_stdout() { + if have curl; then + curl -fsSL --retry 3 "$1" + elif have wget; then + wget -q -O - "$1" + else + die "need curl or wget to download files" + fi +} + +resolve_version() { + if [ -n "${GRAINX_VERSION:-}" ]; then + VERSION="${GRAINX_VERSION#v}" + elif [ -n "${GRAINX_DOWNLOAD_BASE:-}" ]; then + die "GRAINX_DOWNLOAD_BASE is set, so also set GRAINX_VERSION (for example GRAINX_VERSION=0.3.0)" + else + say "Looking up the latest release..." + json="$(fetch_stdout "https://api.github.com/repos/${REPO}/releases/latest")" || + die "could not look up the latest release (no release published yet, no network, or the GitHub API rate limit). Set GRAINX_VERSION, e.g. GRAINX_VERSION=0.3.0" + tag="$(printf '%s\n' "$json" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)" + [ -n "$tag" ] || die "could not read the latest release tag. Set GRAINX_VERSION, e.g. GRAINX_VERSION=0.3.0" + VERSION="${tag#v}" + fi + case "$VERSION" in + '' | *[!0-9A-Za-z.+-]*) die "invalid version: ${VERSION}" ;; + esac +} + +sha256_of() { + if have sha256sum; then + sha256sum "$1" | awk '{print $1}' + elif have shasum; then + shasum -a 256 "$1" | awk '{print $1}' + else + die "need sha256sum or shasum to verify the download" + fi +} + +main() { + have tar || die "need tar to unpack the download" + have curl || have wget || die "need curl or wget to download files" + + detect_target + resolve_version + + INSTALL_DIR="${GRAINX_INSTALL_DIR:-${HOME:?HOME is not set}/.local/bin}" + NAME="grainx-${VERSION}-${TARGET}" + ARCHIVE="${NAME}.tar.gz" + BASE="${GRAINX_DOWNLOAD_BASE:-https://github.com/${REPO}/releases/download/v${VERSION}}" + BASE="${BASE%/}" + + TMP="$(mktemp -d 2>/dev/null || mktemp -d -t grainx-install)" + trap 'rm -rf "$TMP"' EXIT INT TERM HUP + + say "Installing grainx ${VERSION} for ${TARGET}" + fetch "${BASE}/${ARCHIVE}" "${TMP}/${ARCHIVE}" || + die "could not download ${BASE}/${ARCHIVE} (is ${VERSION} a published version for ${TARGET}?)" + fetch "${BASE}/${ARCHIVE}.sha256" "${TMP}/${ARCHIVE}.sha256" || + die "could not download ${BASE}/${ARCHIVE}.sha256" + + expected="$(awk 'NR == 1 {print tolower($1)}' "${TMP}/${ARCHIVE}.sha256")" + actual="$(sha256_of "${TMP}/${ARCHIVE}")" + case "$expected" in + '' | *[!0-9a-f]*) die "the checksum file ${ARCHIVE}.sha256 is not valid" ;; + esac + if [ "${#expected}" -ne 64 ]; then + die "the checksum file ${ARCHIVE}.sha256 is not valid" + fi + if [ "$expected" != "$actual" ]; then + err "checksum mismatch for ${ARCHIVE}" + err " expected: ${expected}" + err " actual: ${actual}" + die "refusing to install a file that does not match its checksum" + fi + say "Checksum OK" + + tar -xzf "${TMP}/${ARCHIVE}" -C "$TMP" || die "could not unpack ${ARCHIVE}" + [ -f "${TMP}/${NAME}/grainx" ] || die "the archive does not contain ${NAME}/grainx" + + mkdir -p "$INSTALL_DIR" || die "could not create ${INSTALL_DIR}; set GRAINX_INSTALL_DIR to a writable directory" + # Copy under a temporary name and rename, so a running grainx is replaced safely. + cp "${TMP}/${NAME}/grainx" "${INSTALL_DIR}/.grainx.new.$$" || + die "could not write to ${INSTALL_DIR}; set GRAINX_INSTALL_DIR to a writable directory" + chmod 755 "${INSTALL_DIR}/.grainx.new.$$" + mv -f "${INSTALL_DIR}/.grainx.new.$$" "${INSTALL_DIR}/grainx" + + say "Installed: ${INSTALL_DIR}/grainx" + "${INSTALL_DIR}/grainx" --version || + die "the installed binary did not run; it may not suit this system. Build from source instead: ${FROM_SOURCE}" + + case ":${PATH}:" in + *":${INSTALL_DIR}:"*) ;; + *) + say "" + say "${INSTALL_DIR} is not on your PATH. Add it, for example:" + say " export PATH=\"${INSTALL_DIR}:\$PATH\"" + say "(put that line in your shell startup file to keep it; this installer does not edit it)" + ;; + esac + + say "" + say "Next: run 'grainx --help' to get started." +} + +main "$@" diff --git a/packaging/README.txt b/packaging/README.txt new file mode 100644 index 0000000..4a2d889 --- /dev/null +++ b/packaging/README.txt @@ -0,0 +1,27 @@ +grainx (grainx) +=============== + +Shows live computer and process activity in a terminal. This archive holds the `grainx` command-line tool, built from the tagged release. + +Install +------- +Put the `grainx` binary (`grainx.exe` on Windows) in a directory on your PATH, +or use the installer, which does that for you and checks the checksum: + + macOS / Linux: curl -fsSL https://raw.githubusercontent.com/rustfuture/grainx/main/install.sh | sh + Windows: irm https://raw.githubusercontent.com/rustfuture/grainx/main/install.ps1 | iex + +Then: + + grainx --version + grainx --help + +Verify this download +-------------------- +Each archive has a matching .sha256 file on the release page: + + sha256sum -c grainx--.tar.gz.sha256 (Linux) + shasum -a 256 -c grainx--.tar.gz.sha256 (macOS) + +Docs and source: https://github.com/rustfuture/grainx +License: see LICENSE