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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
version or target.
- SHA-256 verification, archive safety checks, atomic installation, and
concurrent-install locking.
- Preflight checks for archive and checksum tools before release archive
downloads.
- Stable machine-readable output for CI/CD consumers.
- Cross-platform test, security scanning, release, and integration-vendoring
workflows.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,12 @@ requested. Concurrent installations are serialized with a lock.

| 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.sh` | Linux or macOS | `curl`, or GNU `wget` with `--https-only`; `tar` and `gzip` for `.tar.gz` archives or `unzip` for `.zip` archives; 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+ |

After resolving the release manifest, the shell installer checks the required
archive and checksum tools before downloading the release archive.

## Configuration

Command-line options take precedence over environment variables.
Expand Down
22 changes: 20 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ sha256_file() {
fi
}

check_required_tools() {
case "$ARCHIVE_NAME" in
*.tar.gz|*.tgz)
command -v tar >/dev/null 2>&1 || die "tar is required"
command -v gzip >/dev/null 2>&1 || die "gzip is required"
;;
*.zip)
command -v unzip >/dev/null 2>&1 || die "unzip is required"
;;
*) die "unsupported archive format: $ARCHIVE_NAME" ;;
esac

if ! command -v sha256sum >/dev/null 2>&1 &&
! command -v shasum >/dev/null 2>&1 &&
! command -v openssl >/dev/null 2>&1; then
die "sha256sum, shasum, or openssl is required"
fi
}

manifest_value() {
awk -F= -v wanted="$1" '
$0 !~ /^[[:space:]]*#/ && $1 == wanted {
Expand Down Expand Up @@ -289,13 +308,11 @@ validate_archive_listing() {
validate_archive_entries() {
case "$ARCHIVE_FILE" in
*.tar.gz|*.tgz)
command -v tar >/dev/null 2>&1 || die "tar is required"
tar -tvzf "$ARCHIVE_FILE" > "$WORK_DIR/archive-listing.txt"
validate_archive_listing "$WORK_DIR/archive-listing.txt"
tar -tzf "$ARCHIVE_FILE" > "$WORK_DIR/archive-entries.txt"
;;
*.zip)
command -v unzip >/dev/null 2>&1 || die "unzip is required"
unzip -Z -l "$ARCHIVE_FILE" > "$WORK_DIR/archive-listing.txt"
validate_archive_listing "$WORK_DIR/archive-listing.txt"
unzip -Z1 "$ARCHIVE_FILE" > "$WORK_DIR/archive-entries.txt"
Expand Down Expand Up @@ -383,6 +400,7 @@ main() {
return 0
fi

check_required_tools
download_archive
stage_archive
activate_staged_install
Expand Down
70 changes: 70 additions & 0 deletions tests/install.bats
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,36 @@ make_path_without_curl() {
printf '%s\n' "$bin"
}

make_path_without_tar() {
local bin="$BATS_TEST_TMPDIR/path-without-tar"
mkdir -p "$bin"

local cmd path
for cmd in sh mkdir mktemp rm rmdir awk tr gzip gunzip cat chmod mv dirname grep cp sha256sum shasum openssl sleep curl wget; do
path="$(command -v "$cmd" 2>/dev/null || true)"
if [ -n "$path" ] && [ ! -e "$bin/$cmd" ]; then
ln -s "$path" "$bin/$cmd"
fi
done

printf '%s\n' "$bin"
}

make_path_without_checksum_tool() {
local bin="$BATS_TEST_TMPDIR/path-without-checksum-tool"
mkdir -p "$bin"

local cmd path
for cmd in sh mkdir mktemp rm rmdir awk tr tar gzip gunzip cat chmod mv dirname grep cp sleep curl wget; do
path="$(command -v "$cmd" 2>/dev/null || true)"
if [ -n "$path" ] && [ ! -e "$bin/$cmd" ]; then
ln -s "$path" "$bin/$cmd"
fi
done

printf '%s\n' "$bin"
}

write_busybox_wget_stub() {
local bin="$1"
cat > "$bin/wget" <<'EOF'
Expand Down Expand Up @@ -468,6 +498,46 @@ executable=$bin_dir/cloudsmith"
[ ! -e "$INSTALL_ROOT/1.19.0/$TEST_TARGET/cloudsmith/cloudsmith" ]
}

@test "missing tar fails before the release archive is downloaded" {
build_fixture "$FIXTURE_DIR" "1.19.0" "$TEST_TARGET"
local restricted_path archive_name
restricted_path="$(make_path_without_tar)"
archive_name="$(fixture_archive_name "1.19.0" "$TEST_TARGET")"

run --separate-stderr env PATH="$restricted_path" \
CLOUDSMITH_CLI_ALLOW_INSECURE_URLS=true \
"$INSTALL_SH" \
--version 1.19.0 \
--target "$TEST_TARGET" \
--install-root "$INSTALL_ROOT" \
--manifest-url "$FIXTURE_URL/manifest.txt"

[ "$status" -ne 0 ]
[[ "$stderr" == *"tar is required"* ]]
[[ "$(cat "$FIXTURE_SERVER_LOG")" == *"GET /manifest.txt"* ]]
[[ "$(cat "$FIXTURE_SERVER_LOG")" != *"GET /$archive_name"* ]]
}

@test "missing checksum tools fail before the release archive is downloaded" {
build_fixture "$FIXTURE_DIR" "1.19.0" "$TEST_TARGET"
local restricted_path archive_name
restricted_path="$(make_path_without_checksum_tool)"
archive_name="$(fixture_archive_name "1.19.0" "$TEST_TARGET")"

run --separate-stderr env PATH="$restricted_path" \
CLOUDSMITH_CLI_ALLOW_INSECURE_URLS=true \
"$INSTALL_SH" \
--version 1.19.0 \
--target "$TEST_TARGET" \
--install-root "$INSTALL_ROOT" \
--manifest-url "$FIXTURE_URL/manifest.txt"

[ "$status" -ne 0 ]
[[ "$stderr" == *"sha256sum, shasum, or openssl is required"* ]]
[[ "$(cat "$FIXTURE_SERVER_LOG")" == *"GET /manifest.txt"* ]]
[[ "$(cat "$FIXTURE_SERVER_LOG")" != *"GET /$archive_name"* ]]
}

@test "BusyBox-style wget is rejected in secure mode because it cannot enforce HTTPS redirects" {
local no_curl_path="$BATS_TEST_TMPDIR/no-curl-secure"
no_curl_path="$(make_path_without_curl)"
Expand Down
Loading