diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a500a4b..bbbc11c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1 @@ * @cloudsmith-io/engineering -BartoszBlizniak \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d24edce..3ecde82 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -27,5 +27,13 @@ labels: bug - **OS**: - **Script**: +- **CLI version requested**: - **Shell**: - **CI system (if any)**: + +## Additional Context + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index cf4c706..e2edbff 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -7,5 +7,5 @@ contact_links: url: https://docs.cloudsmith.com/developer-tools/cli about: Check the official Cloudsmith CLI documentation - name: Cloudsmith Support - url: https://cloudsmith.io/support/ + url: https://support.cloudsmith.com/hc/en-us/requests/new about: Contact Cloudsmith support for account-specific issues diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index f68a52b..e852847 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,8 +1,8 @@ -# Description +## Summary - + -## Type of Change +## Type of change - [ ] Bug fix - [ ] New feature @@ -11,6 +11,21 @@ - [ ] Refactoring - [ ] Other (please describe) -## Additional Notes +## Validation - + + +## Compatibility + + + +## Checklist + +- [ ] I have added or updated tests where appropriate. +- [ ] I have updated documentation and `CHANGELOG.md` where appropriate. +- [ ] I have not included credentials, tokens, or other sensitive data. +- [ ] I have called out any breaking change explicitly. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 724c37e..fcb4f57 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -108,13 +108,12 @@ jobs: fail-fast: false matrix: include: - # TODO confirm vendor path with each integration's maintainers. - repo: cloudsmith-io/cloudsmith-cli-action - vendor_path: scripts/vendor/ + integration: github-action - repo: cloudsmith-io/orb - vendor_path: scripts/vendor/ + integration: circleci-orb - repo: cloudsmith-io/cloudsmith-ado-integration - vendor_path: scripts/vendor/ + integration: azure-devops steps: - name: Checkout installer repo uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -129,33 +128,75 @@ jobs: set -eu git clone --depth 1 "https://x-access-token:${VENDOR_TOKEN}@github.com/${TARGET_REPO}.git" target-repo - - name: Vendor scripts with provenance header + - name: Vendor release assets env: + INTEGRATION: ${{ matrix.integration }} TAG: ${{ github.ref_name }} - SOURCE_REPO: ${{ github.repository }} - VENDOR_PATH: ${{ matrix.vendor_path }} run: | set -eu - mkdir -p "target-repo/${VENDOR_PATH}" - for f in install.sh install.ps1; do - sha="$(sha256sum "$f" | awk '{print $1}')" - first_line="$(head -n1 "$f")" - dest="target-repo/${VENDOR_PATH}${f}" - { - case "$first_line" in - '#!'*) printf '%s\n' "$first_line" ;; - esac - printf '# Vendored from %s\n' "$SOURCE_REPO" - printf '# Source tag: %s\n' "$TAG" - printf '# Source commit: %s\n' "$GITHUB_SHA" - printf '# SHA-256 (pre-vendoring): %s\n' "$sha" - printf '# Do not edit directly; update via the source repository and re-vendor.\n' - case "$first_line" in - '#!'*) tail -n +2 "$f" ;; - *) cat "$f" ;; - esac - } > "$dest" - done + case "$INTEGRATION" in + github-action) + destination="target-repo/installer" + mkdir -p "$destination" + cp install.sh install.ps1 "$destination/" + printf '%s\n' "$TAG" > "$destination/VERSION" + ( + cd "$destination" + sha256sum install.sh install.ps1 > SHA256SUMS + ) + ;; + circleci-orb) + destination="target-repo/src/scripts" + mkdir -p "$destination" + cp install.sh "$destination/install.sh" + sha="$(sha256sum install.sh | awk '{print $1}')" + printf 'version=%s\nsha256=%s\n' "$TAG" "$sha" \ + > "$destination/install.sh.version" + ;; + azure-devops) + destination="target-repo/cloudsmith-task/installer" + mkdir -p "$destination" + cp install.sh install.ps1 "$destination/" + printf '%s\n' "$TAG" > "$destination/VERSION" + ;; + *) + echo "unsupported integration: $INTEGRATION" >&2 + exit 1 + ;; + esac + + - name: Verify vendored files + env: + INTEGRATION: ${{ matrix.integration }} + run: | + set -eu + case "$INTEGRATION" in + github-action) + destination="target-repo/installer" + cmp install.sh "$destination/install.sh" + cmp install.ps1 "$destination/install.ps1" + test -s "$destination/VERSION" + (cd "$destination" && sha256sum -c SHA256SUMS) + ;; + circleci-orb) + destination="target-repo/src/scripts" + cmp install.sh "$destination/install.sh" + expected="$(awk -F= '$1 == "sha256" {print $2}' "$destination/install.sh.version")" + actual="$(sha256sum install.sh | awk '{print $1}')" + test -n "$(awk -F= '$1 == "version" {print $2}' "$destination/install.sh.version")" + test "$expected" = "$actual" + ;; + azure-devops) + destination="target-repo/cloudsmith-task/installer" + cmp install.sh "$destination/install.sh" + cmp install.ps1 "$destination/install.ps1" + test -s "$destination/VERSION" + ;; + *) + echo "unsupported integration: $INTEGRATION" >&2 + exit 1 + ;; + esac - name: Commit and open vendoring PR env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 033d47d..ae9f001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Initial `install.sh` and `install.ps1`. -- CI: lint, cross-platform tests, zizmor scanning, and a tag-triggered - release workflow. +- Cross-platform `install.sh` and `install.ps1` installers for the standalone + Cloudsmith CLI. +- Automatic platform detection and support for selecting a specific CLI + version or target. +- SHA-256 verification, archive safety checks, atomic installation, and + concurrent-install locking. +- Stable machine-readable output for CI/CD consumers. +- Cross-platform test, security scanning, release, and integration-vendoring + workflows. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4312c89 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# Contributing + +Thank you for helping improve the Cloudsmith CLI installers. Bug reports, +documentation improvements, tests, and focused code changes are welcome. + +## Before you start + +- Search existing issues and pull requests before opening a duplicate. +- Use the issue templates for bugs, feature requests, and usage questions. +- Do not open a public issue for a suspected vulnerability. Follow + [SECURITY.md](SECURITY.md) instead. +- Keep changes focused. The installers are vendored by multiple CI/CD + integrations, so compatibility and reviewability matter. + +## Development setup + +Fork and clone the repository, then create a branch from `main`. + +The shell suite requires bats-core 1.5 or later and Python 3: + +```sh +bats tests/ +``` + +Shell changes should also pass the checks used in CI: + +```sh +sh -n install.sh +shellcheck --shell=sh --severity=style install.sh +``` + +The Windows suite requires Pester 5.5 through 5.99: + +```powershell +Install-Module Pester -MinimumVersion 5.5 -MaximumVersion 5.99 -Scope CurrentUser +Import-Module Pester -MinimumVersion 5.5 -MaximumVersion 5.99 +Invoke-Pester -Path tests/Install.Tests.ps1 +``` + +See [tests/README.md](tests/README.md) for more detail. Tests use local fixtures +and do not require access to the public package repository. + +## Compatibility expectations + +Treat these interfaces as public contracts: + +- command-line options and PowerShell parameters +- `CLOUDSMITH_CLI_*` environment variables +- supported target identifiers +- versioned installation layout +- the four `key=value` output lines + +Changes to a public contract need explicit release notes, integration impact +analysis, and an appropriate semantic-versioning decision. Preserve POSIX `sh` +compatibility in `install.sh` and Windows PowerShell 5.1 compatibility in +`install.ps1`. + +## Pull requests + +- Explain the problem and the chosen solution. +- Add or update tests for behavior changes. +- Update `README.md` and `CHANGELOG.md` when user-visible behavior changes. +- List the validation commands you ran. +- Keep commits free of credentials, private URLs, generated test artifacts, and + unrelated changes. + +Maintainers may request additional integration validation before merging a +change that affects installer behavior or release distribution. diff --git a/README.md b/README.md index fe3c1a2..7b00ce7 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,192 @@ -# cloudsmith-cli-install-script +
-Install scripts for the [Cloudsmith CLI](https://github.com/cloudsmith-io/cloudsmith-cli) -standalone binary: `install.sh` (POSIX sh) and `install.ps1` (PowerShell). +# Cloudsmith CLI installer -Each script detects the platform, downloads the matching release archive, -verifies its SHA-256, and installs it into a versioned directory. It prints -where the binary was installed; it does not modify `PATH` or authenticate. +Secure, cross-platform installation scripts for the standalone Cloudsmith CLI. -These scripts are vendored into the Cloudsmith CI integrations (GitHub -Action, CircleCI orb, Azure DevOps task). They are also published as assets -on this repository's GitHub releases, alongside a `SHA256SUMS` file. +[![Test](https://github.com/cloudsmith-io/cloudsmith-cli-install-script/actions/workflows/test.yml/badge.svg)](https://github.com/cloudsmith-io/cloudsmith-cli-install-script/actions/workflows/test.yml) +[![Zizmor](https://github.com/cloudsmith-io/cloudsmith-cli-install-script/actions/workflows/zizmor.yml/badge.svg)](https://github.com/cloudsmith-io/cloudsmith-cli-install-script/actions/workflows/zizmor.yml) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) -## Usage +[Quick start](#quick-start) · [Configuration](#configuration) · [Supported targets](#supported-targets) · [Security](#security) · [Support](#support) -Linux/macOS: +
+ +This repository is the canonical source for the scripts used to install the +[Cloudsmith CLI](https://github.com/cloudsmith-io/cloudsmith-cli) standalone +binary: + +- `install.sh` for Linux and macOS using POSIX `sh` +- `install.ps1` for Windows using PowerShell + +The scripts detect the host platform, download the matching release archive, +verify its SHA-256 checksum, and install the CLI into a versioned directory. +They do not modify `PATH` or authenticate with Cloudsmith. + +> [!IMPORTANT] +> For reproducible CI builds, use a specific CLI version instead of `latest`. + +## At a glance + +| Capability | Behaviour | +| --- | --- | +| Platforms | Linux, macOS, and Windows on x86-64 and Arm where supported | +| Integrity | Verifies the release archive against its SHA-256 manifest | +| Installation | Uses versioned directories and atomic activation | +| Output | Emits four stable `key=value` lines for CI consumers | +| Side effects | Does not modify `PATH` or configure authentication | +| Distribution | Published as release assets and vendored into Cloudsmith CI/CD integrations | + +## Quick start + +Download the appropriate script from a tagged +[GitHub release](https://github.com/cloudsmith-io/cloudsmith-cli-install-script/releases) +and verify it against the release's `SHA256SUMS` file before running it. + +### Linux or macOS ```sh -sh install.sh --version latest +sh ./install.sh --version latest ``` -Windows: +### Windows ```powershell ./install.ps1 -Version latest ``` -### Options +On success, use the reported `bin_dir` to update `PATH`, or invoke the reported +`executable` directly. -`install.sh` flag / `install.ps1` parameter, matching environment variable, default: +## How installation works -| sh | PowerShell | Env var | Default | +```mermaid +flowchart LR + A[Detect host] --> B[Resolve CLI release] + B --> C[Download archive] + C --> D[Verify SHA-256] + D --> E[Validate contents] + E --> F[Install atomically] +``` + +An existing verified installation is reused unless a forced reinstall is +requested. Concurrent installations are serialized with a lock. + +## Requirements + +| Installer | Supported host | Required tools | +| --- | --- | --- | +| `install.sh` | Linux or macOS | `curl`, or GNU `wget` with `--https-only`; `tar`; `gzip`; and one of `sha256sum`, `shasum`, or `openssl` | +| `install.ps1` | Windows x86-64, or Windows Arm64 with x86-64 emulation | Windows PowerShell 5.1 or PowerShell 7+ | + +## Configuration + +Command-line options take precedence over environment variables. + +| Shell option | PowerShell parameter | Environment variable | Default | | --- | --- | --- | --- | | `--version` | `-Version` | `CLOUDSMITH_CLI_VERSION` | `latest` | -| `--install-root` | `-InstallRoot` | `CLOUDSMITH_CLI_INSTALL_ROOT` | `~/.local/share/cloudsmith-cli` (sh), `%LOCALAPPDATA%\Cloudsmith\CLI` (ps1) | -| `--target` | `-Target` | `CLOUDSMITH_CLI_TARGET` | auto-detected | -| `--output-file` | `-OutputFile` | `CLOUDSMITH_CLI_OUTPUT_FILE` | stdout | -| `--repository` | `-Repository` | `CLOUDSMITH_CLI_REPOSITORY` | `cloudsmith/cli-standalone` | -| `--manifest-url` | `-ManifestUrl` | `CLOUDSMITH_CLI_MANIFEST_URL` | derived from repository/version/target | -| `--force` | `-Force` | — | off | +| `--install-root` | `-InstallRoot` | `CLOUDSMITH_CLI_INSTALL_ROOT` | Shell: `$XDG_DATA_HOME/cloudsmith-cli` or `~/.local/share/cloudsmith-cli`
PowerShell: `%LOCALAPPDATA%\Cloudsmith\CLI` | +| `--target` | `-Target` | `CLOUDSMITH_CLI_TARGET` | Automatically detected | +| `--output-file` | `-OutputFile` | `CLOUDSMITH_CLI_OUTPUT_FILE` | Standard output | +| `--repository` | `-Repository` | `CLOUDSMITH_CLI_REPOSITORY` | `cloudsmith/cli` | +| `--manifest-url` | `-ManifestUrl` | `CLOUDSMITH_CLI_MANIFEST_URL` | Derived from the repository, version, and target | +| `--force` | `-Force` | Not applicable | Disabled | -## Output +Run `sh ./install.sh --help` for shell usage. PowerShell parameters support +standard `Get-Help` and tab completion. -On success the script prints exactly four `key=value` lines (to stdout, or -to the output file if set). Everything else goes to stderr. +
+Advanced overrides -``` +The repository, manifest URL, and target overrides are intended for testing or +unusual environments. Normal installations should use the defaults. + +If libc detection fails on an unusual Linux host, set the target explicitly +with `--target` or `CLOUDSMITH_CLI_TARGET`. + +
+ +## Output contract + +Each installer emits exactly four `key=value` lines to standard output, or to +the requested output file. Diagnostic messages are written to standard error. + +```text version=1.19.0 target=linux-x86_64-gnu bin_dir=/home/runner/.local/share/cloudsmith-cli/1.19.0/linux-x86_64-gnu/cloudsmith executable=/home/runner/.local/share/cloudsmith-cli/1.19.0/linux-x86_64-gnu/cloudsmith/cloudsmith ``` -## Supported targets +> [!NOTE] +> The four keys and their order form a public contract for CI/CD integrations. -| Target | Notes | -| --- | --- | -| `linux-x86_64-gnu` | glibc 2.28+ | -| `linux-aarch64-gnu` | glibc 2.28+ | -| `linux-x86_64-musl` | Alpine and other musl distros | -| `linux-aarch64-musl` | Alpine and other musl distros | -| `macos-x86_64` | Intel | -| `macos-arm64` | Apple Silicon | -| `windows-x86_64` | Also used on Windows ARM64 (x86_64 emulation) | +## Supported targets -If libc detection fails on an unusual host, pass `--target` explicitly. +| Operating system | Architecture | Target | Runtime notes | +| --- | --- | --- | --- | +| Linux | x86-64 | `linux-x86_64-gnu` | glibc 2.28+ | +| Linux | Arm64 | `linux-aarch64-gnu` | glibc 2.28+ | +| Linux | x86-64 | `linux-x86_64-musl` | Alpine and other musl distributions | +| Linux | Arm64 | `linux-aarch64-musl` | Alpine and other musl distributions | +| macOS | x86-64 | `macos-x86_64` | Intel | +| macOS | Arm64 | `macos-arm64` | Apple silicon | +| Windows | x86-64 | `windows-x86_64` | Also used on Windows Arm64 through x86-64 emulation | ## Security -- HTTPS-only downloads. -- Archive SHA-256 verified against the release manifest before extraction. -- Archive entries validated before extraction (no absolute paths or `..`). -- Binary is staged, checked with `cloudsmith --version`, then activated - atomically; concurrent installs are serialized with a lock. -- Already-installed, verified versions are reused unless `--force` is given. +The installers apply the following safeguards: + +- HTTPS-only downloads +- SHA-256 verification before extraction +- archive validation to reject absolute paths and parent-directory traversal +- executable validation with `cloudsmith --version` before activation +- atomic activation and serialized concurrent installs +- reuse only after validating an existing installation + +The shell installer uses `curl` or `wget`; the PowerShell installer uses the +.NET web request stack. Standard proxy configuration and operating-system +certificate trust stores apply. + +To report a potential vulnerability, follow the [security policy](SECURITY.md) +instead of opening a public issue. + +## CI/CD integrations -Proxies and custom CAs: downloads use `curl`/`wget` (sh) or -`Invoke-WebRequest` (PowerShell), so standard proxy environment variables -and OS trust stores apply. +Tagged releases publish `install.sh`, `install.ps1`, and `SHA256SUMS`. The +approved installer bytes are also vendored into: + +| Integration | Installer consumer | +| --- | --- | +| GitHub Actions | [cloudsmith-cli-action](https://github.com/cloudsmith-io/cloudsmith-cli-action) | +| CircleCI | [Cloudsmith orb](https://github.com/cloudsmith-io/orb) | +| Azure DevOps | Cloudsmith CLI task for Azure Pipelines | ## Development -```sh -bats tests/ # shell suite (needs bats-core and python3) -Invoke-Pester # PowerShell suite (Pester 5.5+, Windows) -``` +See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow and +[tests/README.md](tests/README.md) for test-suite requirements. + +## Releases and compatibility + +Releases use `vX.Y.Z` tags, and published release assets are immutable. +Breaking changes to command-line options, environment variables, installation +layout, supported targets, or the output contract require a major version bump. + +## Support + +| Need help with | Where to go | +| --- | --- | +| Installer bug | [Open a bug report](https://github.com/cloudsmith-io/cloudsmith-cli-install-script/issues/new?template=bug_report.md) | +| Installer usage | [Ask a question](https://github.com/cloudsmith-io/cloudsmith-cli-install-script/issues/new?template=question.md) | +| Cloudsmith CLI behaviour | [Cloudsmith CLI repository](https://github.com/cloudsmith-io/cloudsmith-cli) | +| Account or private support | [Submit a Cloudsmith support request](https://support.cloudsmith.com/hc/en-us/requests/new) | +| Security concern | [Follow the private reporting process](SECURITY.md) | + +See [SUPPORT.md](SUPPORT.md) for the information to include when requesting +help. -## Releases +## License -Tagged `vX.Y.Z`. Release assets are immutable; breaking changes to flags or -output are a major version bump. +Licensed under the [Apache License 2.0](LICENSE). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2f859bb --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security policy + +## Supported versions + +Security fixes are applied to the latest released installer version. Users +should upgrade to the latest release before reporting an issue that may already +have been addressed. + +## Reporting a vulnerability + +Do not disclose suspected vulnerabilities in a public GitHub issue, discussion, +or pull request. + +Report them privately through +[Cloudsmith Support](https://support.cloudsmith.com/hc/en-us/requests/new). Include: + +- the installer script and release version +- the operating system and architecture +- clear reproduction steps or a proof of concept +- the potential impact +- any suggested remediation + +Remove API keys, tokens, credentials, private repository URLs, and other +sensitive data from logs and examples. Cloudsmith will coordinate disclosure +and remediation with the reporter. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..a4e3f27 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,18 @@ +# Support + +Use the channel that best matches the problem: + +- For reproducible installer bugs, open a + [bug report](https://github.com/cloudsmith-io/cloudsmith-cli-install-script/issues/new?template=bug_report.md). +- For installer usage questions, open a + [question](https://github.com/cloudsmith-io/cloudsmith-cli-install-script/issues/new?template=question.md). +- For problems with the installed CLI rather than its installation, use the + [Cloudsmith CLI repository](https://github.com/cloudsmith-io/cloudsmith-cli). +- For account-specific or private help, contact + [Cloudsmith Support](https://support.cloudsmith.com/hc/en-us/requests/new). +- For suspected vulnerabilities, follow [SECURITY.md](SECURITY.md) and do not + open a public issue. + +When reporting an installer problem, include the script release, requested CLI +version, operating system, architecture, shell, exact command, and stderr +output. Remove all credentials, tokens, private URLs, and other sensitive data. diff --git a/tests/README.md b/tests/README.md index ccc1cb2..2558ba6 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,10 +1,34 @@ # Tests -Bash suite (`install.bats`, `detect_target.bats`): requires [bats-core](https://github.com/bats-core/bats-core) >= 1.5.0 and python3. -Run: `bats tests/` +Both test suites are network-free. Fixtures are served over loopback only +(`127.0.0.1` or `localhost`). -PowerShell suite (`Install.Tests.ps1`), Windows only: requires Pester >= 5.5.0 -(`Install-Module Pester -MinimumVersion 5.5.0 -Scope CurrentUser`). -Run: `Invoke-Pester -Path tests`. Self-skips cleanly on non-Windows hosts. +## Shell suite -Both suites are network-free: all fixtures are served over loopback only (127.0.0.1 / localhost). +The shell suite covers `install.bats` and `detect_target.bats`. It requires +[bats-core](https://github.com/bats-core/bats-core) 1.5.0 or later and +Python 3. + +Run it from the repository root: + +```sh +bats tests/ +``` + +## PowerShell suite + +The Windows-only PowerShell suite (`Install.Tests.ps1`) requires Pester 5.5 or +later in the 5.x release line. The suite skips cleanly on non-Windows hosts. + +Install the supported Pester version: + +```powershell +Install-Module Pester -MinimumVersion 5.5 -MaximumVersion 5.99 -Scope CurrentUser +``` + +Run the suite from the repository root: + +```powershell +Import-Module Pester -MinimumVersion 5.5 -MaximumVersion 5.99 +Invoke-Pester -Path tests/Install.Tests.ps1 +```