From 3f362d35f7b00ec88e9c324dc75b6486191bf596 Mon Sep 17 00:00:00 2001 From: kctl-env Date: Sat, 7 Feb 2026 01:15:31 +0530 Subject: [PATCH 01/30] feat: add bootstrap installer - Add install.sh for one-command setup - Make scripts derive KCTL_ENV_ROOT from install path - Fix list-remote via GitHub tags API - Document quick install in README --- CHANGELOG.md | 7 ++ README.md | 14 ++++ bin/kctl-env | 5 +- bin/kubectl | 5 +- install.sh | 133 +++++++++++++++++++++++++++++++++++ libexec/kctl-env-list-remote | 40 +++++++++-- 6 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 install.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 5662a14..cf7284a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to Semantic Versioning. +## [Unreleased] +### Added +- `install.sh` bootstrap installer (installs kctl-env into `~/.kctl-env` or `$KCTL_ENV_ROOT`) +- README “Quick install” section + ## [v0.1.0] - 2026-01-07 ### Added - Initial scaffold: bin/kubectl shim with env/.kubectl-version/global/auto resolution and TTL cache @@ -15,3 +20,5 @@ All notable changes to this project will be documented in this file. This projec - Git repository initialized, branch protections configured (squash/rebase, linear history, delete branch on merge) [v0.1.0]: https://github.com/senet/kctl-env/releases/tag/v0.1.0 + +[Unreleased]: https://github.com/senet/kctl-env/compare/v0.1.0...HEAD diff --git a/README.md b/README.md index 7a57762..0c34892 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,20 @@ Pure Bash, zero-deps kubectl version manager with fast shims and tfenv-style UX. ## Installation +### Quick install (recommended) + +Install into `~/.kctl-env`: + +```sh +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash +``` + +Pin a specific version (recommended for reproducibility): + +```sh +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- v0.1.1 +``` + 1. Clone or extract kctl-env to a directory (e.g., `~/.kctl-env`). 2. Add the `bin/` directory to your `PATH`: diff --git a/bin/kctl-env b/bin/kctl-env index 70e72d0..ff02268 100755 --- a/bin/kctl-env +++ b/bin/kctl-env @@ -1,7 +1,10 @@ #!/usr/bin/env bash set -euo pipefail -KCTL_ENV_ROOT="${KCTL_ENV_ROOT:-$HOME/.kctl-env}" +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +default_root="$(cd -- "$script_dir/.." && pwd)" + +KCTL_ENV_ROOT="${KCTL_ENV_ROOT:-$default_root}" LIBEXEC="$KCTL_ENV_ROOT/libexec" usage() { diff --git a/bin/kubectl b/bin/kubectl index 47caede..7204bef 100755 --- a/bin/kubectl +++ b/bin/kubectl @@ -5,7 +5,10 @@ set -euo pipefail # --- Config --- -KCTL_ENV_ROOT="${KCTL_ENV_ROOT:-$HOME/.kctl-env}" +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +default_root="$(cd -- "$script_dir/.." && pwd)" + +KCTL_ENV_ROOT="${KCTL_ENV_ROOT:-$default_root}" KCTL_CACHE="$KCTL_ENV_ROOT/cache" KCTL_VERSIONS="$KCTL_ENV_ROOT/versions" KCTL_GLOBAL_VERSION_FILE="$KCTL_ENV_ROOT/version" diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..c679a86 --- /dev/null +++ b/install.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# kctl-env bootstrap installer +# - Zero deps beyond: bash, curl, tar, coreutils +# - Installs into $KCTL_ENV_ROOT (default: ~/.kctl-env) +# - Preserves existing runtime dirs (versions/, cache/) + +set -euo pipefail + +REPO_OWNER="senet" +REPO_NAME="kctl-env" + +KCTL_ENV_ROOT="${KCTL_ENV_ROOT:-$HOME/.kctl-env}" +KCTL_ENV_REF="${KCTL_ENV_REF:-}" + +usage() { + cat </dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; } +} + +require_cmd install +require_cmd mktemp +require_cmd curl +require_cmd find +require_cmd head +require_cmd tar + +tmpdir="$(mktemp -d)" +trap 'rm -rf "$tmpdir"' EXIT + +archive="$tmpdir/src.tar.gz" + +echo "Downloading $archive_url" +curl -fsSL "$archive_url" -o "$archive" + +# Extract +mkdir -p "$tmpdir/src" +tar -xzf "$archive" -C "$tmpdir/src" + +# Determine extracted directory +src_root="$(find "$tmpdir/src" -mindepth 1 -maxdepth 1 -type d | head -n 1)" +if [[ -z "${src_root:-}" || ! -d "$src_root" ]]; then + echo "Failed to locate extracted source directory" >&2 + exit 1 +fi + +# Create target directories (preserve versions/cache if already exist) +mkdir -p "$KCTL_ENV_ROOT" \ + "$KCTL_ENV_ROOT/bin" \ + "$KCTL_ENV_ROOT/libexec" \ + "$KCTL_ENV_ROOT/etc" \ + "$KCTL_ENV_ROOT/packaging" \ + "$KCTL_ENV_ROOT/scripts" \ + "$KCTL_ENV_ROOT/versions" \ + "$KCTL_ENV_ROOT/cache" + +# Install core scripts +install -m 0755 "$src_root/bin/kctl-env" "$KCTL_ENV_ROOT/bin/kctl-env" +install -m 0755 "$src_root/bin/kubectl" "$KCTL_ENV_ROOT/bin/kubectl" + +# libexec scripts +for f in "$src_root"/libexec/*; do + [[ -f "$f" ]] || continue + install -m 0755 "$f" "$KCTL_ENV_ROOT/libexec/$(basename "$f")" +done + +# Completion scripts (optional) +if [[ -f "$src_root/etc/kctl-env-completion.bash" ]]; then + install -m 0644 "$src_root/etc/kctl-env-completion.bash" "$KCTL_ENV_ROOT/etc/kctl-env-completion.bash" +fi +if [[ -f "$src_root/etc/kctl-env-completion.zsh" ]]; then + install -m 0644 "$src_root/etc/kctl-env-completion.zsh" "$KCTL_ENV_ROOT/etc/kctl-env-completion.zsh" +fi + +# Scripts (optional) +if [[ -f "$src_root/scripts/release.sh" ]]; then + install -m 0755 "$src_root/scripts/release.sh" "$KCTL_ENV_ROOT/scripts/release.sh" +fi + +# Docs + metadata (best-effort) +for f in README.md CHANGELOG.md VERSION; do + if [[ -f "$src_root/$f" ]]; then + install -m 0644 "$src_root/$f" "$KCTL_ENV_ROOT/$f" + fi +done + +echo +echo "Installed kctl-env into: $KCTL_ENV_ROOT" +echo +echo "Next steps:" +echo " 1) Add to PATH:" +echo " export PATH=\"$KCTL_ENV_ROOT/bin:\$PATH\"" +echo " 2) Install kubectl:" +echo " kctl-env install latest" +echo " 3) Select version:" +echo " kctl-env use latest" +echo diff --git a/libexec/kctl-env-list-remote b/libexec/kctl-env-list-remote index a2d0a3e..5b1a3fe 100755 --- a/libexec/kctl-env-list-remote +++ b/libexec/kctl-env-list-remote @@ -1,10 +1,42 @@ #!/usr/bin/env bash set -euo pipefail -# List available kubectl releases from dl.k8s.io -# Zero-deps: parse HTML index +# List available kubectl releases. +# +# Kubernetes' dl.k8s.io no longer exposes a browsable release index, so we use +# the GitHub tags API and parse it with POSIX tools (no jq). +# +# Notes: +# - Unauthenticated GitHub API requests are rate-limited. +# - Increase limits by exporting GITHUB_TOKEN. -curl -fsSL https://dl.k8s.io/release/ | - grep -Eo 'v[0-9]+\.[0-9]+\.[0-9]+' | +MAX_PAGES="${KCTL_LIST_REMOTE_MAX_PAGES:-5}" +PER_PAGE=100 + +api_base="https://api.github.com/repos/kubernetes/kubernetes/tags" + +curl_args=(-sSL) +if [[ -n "${GITHUB_TOKEN:-}" ]]; then + curl_args+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") +fi + +page=1 +while [[ "$page" -le "$MAX_PAGES" ]]; do + json="$(curl "${curl_args[@]}" "${api_base}?per_page=${PER_PAGE}&page=${page}")" || { + echo "kctl-env: failed to query GitHub tags API (page ${page})." >&2 + echo "Hint: export GITHUB_TOKEN to avoid rate limits, or set KCTL_LIST_REMOTE_MAX_PAGES lower." >&2 + exit 1 + } + + # Extract stable semver tags like v1.29.0 + tags="$(printf '%s' "$json" | + grep -Eo '"name"[[:space:]]*:[[:space:]]*"v[0-9]+\.[0-9]+\.[0-9]+"' | + sed -E 's/.*"(v[0-9]+\.[0-9]+\.[0-9]+)".*/\1/' + )" + + [[ -z "$tags" ]] && break + printf '%s\n' "$tags" + page=$((page + 1)) +done | sort -Vr | uniq From 18744073f6faccf383adf280cf0d55d9a799d1ec Mon Sep 17 00:00:00 2001 From: kctl-env Date: Sat, 7 Feb 2026 01:16:42 +0530 Subject: [PATCH 02/30] docs: clarify quick install branch ref --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0c34892..6e26529 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ Install into `~/.kctl-env`: curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash ``` +If you’re testing from a feature branch, replace `main` with the branch name (e.g. `feat/easy-install`). + Pin a specific version (recommended for reproducibility): ```sh From 2fe39da091de6831d2e9cacd5c9a0a71b002a350 Mon Sep 17 00:00:00 2001 From: kctl-env Date: Sat, 7 Feb 2026 01:17:09 +0530 Subject: [PATCH 03/30] docs: fix quick install branch example --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e26529..7879e21 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,11 @@ Install into `~/.kctl-env`: curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash ``` -If you’re testing from a feature branch, replace `main` with the branch name (e.g. `feat/easy-install`). +If you’re testing from a feature branch, replace `main` in the URL and pass the branch name as the ref: + +```sh +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/feat/easy-install/install.sh | bash -s -- feat/easy-install +``` Pin a specific version (recommended for reproducibility): From fd3c7af48e97592a7014282775a811a905e07ad9 Mon Sep 17 00:00:00 2001 From: kctl-env Date: Sat, 7 Feb 2026 01:21:35 +0530 Subject: [PATCH 04/30] chore: update maintainer email Replace with sen.net@hotmail.com in packaging metadata and release script. --- packaging/debian/changelog | 2 +- packaging/debian/control | 2 +- packaging/rpm/kctl-env.spec | 2 +- scripts/release.sh | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packaging/debian/changelog b/packaging/debian/changelog index 8e3e570..f928769 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -2,4 +2,4 @@ kctl-env (0.1.0) unstable; urgency=medium * Initial release. - -- kctl-env Maintainers Wed, 07 Jan 2026 22:00:00 +0000 + -- kctl-env Maintainers Wed, 07 Jan 2026 22:00:00 +0000 diff --git a/packaging/debian/control b/packaging/debian/control index d5cada9..f400e08 100644 --- a/packaging/debian/control +++ b/packaging/debian/control @@ -1,7 +1,7 @@ Source: kctl-env Section: utils Priority: optional -Maintainer: kctl-env Maintainers +Maintainer: kctl-env Maintainers Standards-Version: 4.6.2 Build-Depends: debhelper-compat (= 13) diff --git a/packaging/rpm/kctl-env.spec b/packaging/rpm/kctl-env.spec index 4d5ef57..d929307 100644 --- a/packaging/rpm/kctl-env.spec +++ b/packaging/rpm/kctl-env.spec @@ -36,5 +36,5 @@ ln -sf /usr/lib/kctl-env/bin/kubectl %{buildroot}/usr/bin/kubectl /usr/bin/kubectl %changelog -* Wed Jan 07 2026 kctl-env Maintainers - 0.1.0-1 +* Wed Jan 07 2026 kctl-env Maintainers - 0.1.0-1 - Initial release diff --git a/scripts/release.sh b/scripts/release.sh index 76261f9..e311b0f 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -32,7 +32,7 @@ if [[ -f "$changelog_file" ]]; then echo echo " * Release $new_version" echo - echo " -- kctl-env Maintainers $change_date" + echo " -- kctl-env Maintainers $change_date" echo cat "$changelog_file" } > "$tmp" @@ -44,7 +44,7 @@ spec_file="packaging/rpm/kctl-env.spec" if [[ -f "$spec_file" ]]; then sed -i "s/^Version:\s*.*/Version: $new_version/" "$spec_file" # Update changelog - sed -i "1 a\\* $(date +"%a %b %d %Y") kctl-env Maintainers - $new_version-1\\n- Release $new_version" "$spec_file" + sed -i "1 a\\* $(date +"%a %b %d %Y") kctl-env Maintainers - $new_version-1\\n- Release $new_version" "$spec_file" fi # Commit changes From dfd308f601c4ad03cfea1599934c3df8788be256 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 01:49:30 +0530 Subject: [PATCH 05/30] Initial plan (#5) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> From cca8a1d7e589850b9fbdf66f2347d60c49ed1a1f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:39:58 +0530 Subject: [PATCH 06/30] Fix curl error handling and revert maintainer emails to project address (#6) * Initial plan * fix: add curl -f flag and revert maintainer emails to project address Co-authored-by: senet <4061835+senet@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: senet <4061835+senet@users.noreply.github.com> --- libexec/kctl-env-list-remote | 2 +- packaging/debian/control | 2 +- scripts/release.sh | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libexec/kctl-env-list-remote b/libexec/kctl-env-list-remote index 5b1a3fe..26464b8 100755 --- a/libexec/kctl-env-list-remote +++ b/libexec/kctl-env-list-remote @@ -15,7 +15,7 @@ PER_PAGE=100 api_base="https://api.github.com/repos/kubernetes/kubernetes/tags" -curl_args=(-sSL) +curl_args=(-sSLf) if [[ -n "${GITHUB_TOKEN:-}" ]]; then curl_args+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") fi diff --git a/packaging/debian/control b/packaging/debian/control index f400e08..d5cada9 100644 --- a/packaging/debian/control +++ b/packaging/debian/control @@ -1,7 +1,7 @@ Source: kctl-env Section: utils Priority: optional -Maintainer: kctl-env Maintainers +Maintainer: kctl-env Maintainers Standards-Version: 4.6.2 Build-Depends: debhelper-compat (= 13) diff --git a/scripts/release.sh b/scripts/release.sh index e311b0f..76261f9 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -32,7 +32,7 @@ if [[ -f "$changelog_file" ]]; then echo echo " * Release $new_version" echo - echo " -- kctl-env Maintainers $change_date" + echo " -- kctl-env Maintainers $change_date" echo cat "$changelog_file" } > "$tmp" @@ -44,7 +44,7 @@ spec_file="packaging/rpm/kctl-env.spec" if [[ -f "$spec_file" ]]; then sed -i "s/^Version:\s*.*/Version: $new_version/" "$spec_file" # Update changelog - sed -i "1 a\\* $(date +"%a %b %d %Y") kctl-env Maintainers - $new_version-1\\n- Release $new_version" "$spec_file" + sed -i "1 a\\* $(date +"%a %b %d %Y") kctl-env Maintainers - $new_version-1\\n- Release $new_version" "$spec_file" fi # Commit changes From bd62efa016bb8102f085e11d84112f6d18d32f67 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:47:22 +0530 Subject: [PATCH 07/30] Initial plan (#7) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> From 20e7869d1fc16ab1990f226ec51913a25553665b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:58:22 +0530 Subject: [PATCH 08/30] Initial plan (#8) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> From 3c7547e2f48556506e52f61bed9d552a67b1acc4 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:14:24 +0530 Subject: [PATCH 09/30] docs: prioritize pinned version install for supply-chain security (#10) * Initial plan * docs: make pinned version install the recommended default for security Co-authored-by: senet <4061835+senet@users.noreply.github.com> * docs: pin install script to same tag and add manual install header Co-authored-by: senet <4061835+senet@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: senet <4061835+senet@users.noreply.github.com> --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7879e21..eddb4b2 100644 --- a/README.md +++ b/README.md @@ -14,24 +14,28 @@ Pure Bash, zero-deps kubectl version manager with fast shims and tfenv-style UX. ### Quick install (recommended) -Install into `~/.kctl-env`: +Pin a specific version for security and reproducibility: ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/v0.1.1/install.sh | bash -s -- v0.1.1 ``` -If you’re testing from a feature branch, replace `main` in the URL and pass the branch name as the ref: +### Development/unstable install + +Install the latest development version from `main` (not recommended for production): ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/feat/easy-install/install.sh | bash -s -- feat/easy-install +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash ``` -Pin a specific version (recommended for reproducibility): +For testing from a feature branch, replace `main` in the URL and pass the branch name as the ref: ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- v0.1.1 +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/feat/easy-install/install.sh | bash -s -- feat/easy-install ``` +### Manual installation + 1. Clone or extract kctl-env to a directory (e.g., `~/.kctl-env`). 2. Add the `bin/` directory to your `PATH`: From 06b639f4499743ddaf0409e4215bd43a9d785b8d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:16:20 +0530 Subject: [PATCH 10/30] Require explicit ref in install.sh for supply-chain security (#11) * Initial plan * Require explicit ref for supply-chain security Co-authored-by: senet <4061835+senet@users.noreply.github.com> --------- Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: senet <4061835+senet@users.noreply.github.com> --- README.md | 13 +++++-------- install.sh | 13 ++++++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index eddb4b2..f48aac1 100644 --- a/README.md +++ b/README.md @@ -14,27 +14,24 @@ Pure Bash, zero-deps kubectl version manager with fast shims and tfenv-style UX. ### Quick install (recommended) -Pin a specific version for security and reproducibility: +Pin a specific version (recommended for reproducibility and supply-chain security): ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/v0.1.1/install.sh | bash -s -- v0.1.1 +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- v0.1.1 ``` -### Development/unstable install - -Install the latest development version from `main` (not recommended for production): +Install from the main branch (for development/testing): ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- main ``` -For testing from a feature branch, replace `main` in the URL and pass the branch name as the ref: +If you're testing from a feature branch, replace `main` in the URL and pass the branch name as the ref: ```sh curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/feat/easy-install/install.sh | bash -s -- feat/easy-install ``` -### Manual installation 1. Clone or extract kctl-env to a directory (e.g., `~/.kctl-env`). 2. Add the `bin/` directory to your `PATH`: diff --git a/install.sh b/install.sh index c679a86..c2c139a 100644 --- a/install.sh +++ b/install.sh @@ -17,18 +17,19 @@ usage() { kctl-env installer Usage: - ./install.sh [ref] + ./install.sh Arguments: - ref Git ref to install (tag like v0.1.1, or branch like main) + ref Git ref to install (required; tag like v0.1.1, or branch like main) Environment: KCTL_ENV_ROOT Install root (default: ~/.kctl-env) - KCTL_ENV_REF Same as [ref] + KCTL_ENV_REF Alternative to passing ref as argument (required if ref not provided) Examples: ./install.sh v0.1.1 KCTL_ENV_ROOT="$HOME/.kctl-env" ./install.sh main + KCTL_ENV_REF=v0.1.1 ./install.sh EOF } @@ -40,8 +41,10 @@ fi ref="${ref_from_input:-$KCTL_ENV_REF}" if [[ -z "$ref" ]]; then - # Default to main for safety (predictable). Users can pin a tag. - ref="main" + echo "Error: No ref specified. For supply-chain security, you must explicitly specify a version tag or branch." >&2 + echo "Usage: $0 (e.g., v0.1.1 or main)" >&2 + echo "See: curl -fsSL https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/main/install.sh | bash -s -- " >&2 + exit 1 fi archive_url="" From 0098c8ec2b9758e15449e73d5de0f9699491f795 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:17:54 +0530 Subject: [PATCH 11/30] Add SHA256 verification to bootstrap installer (#12) * Initial plan * Add SHA256 verification to installer for tagged releases Co-authored-by: senet <4061835+senet@users.noreply.github.com> * Fix checksum verification filename handling Co-authored-by: senet <4061835+senet@users.noreply.github.com> * Make checksum verification mandatory for tagged releases with bypass option Co-authored-by: senet <4061835+senet@users.noreply.github.com> * Add validation for malformed checksum files and clarify release docs Co-authored-by: senet <4061835+senet@users.noreply.github.com> --------- Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: senet <4061835+senet@users.noreply.github.com> --- CONTRIBUTING.md | 15 +++++++++++++++ README.md | 2 ++ install.sh | 42 ++++++++++++++++++++++++++++++++++++++++++ scripts/release.sh | 8 ++++++++ 4 files changed, 67 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 425b137..dce6b4d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,21 @@ git tag -a v0.1.1 -m "v0.1.1 release" git push origin v0.1.1 ``` +5. Generate and publish checksums for the release: + ```sh + # Generate SHA256 checksum for the source tarball + curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v0.1.1.tar.gz | sha256sum > v0.1.1.tar.gz.sha256 + + # Create GitHub release and upload checksum as asset + # Note: GitHub automatically provides the source tarball at: + # https://github.com/senet/kctl-env/archive/refs/tags/v0.1.1.tar.gz + # We only need to upload the checksum file as an additional asset. + gh release create v0.1.1 --title "v0.1.1" --notes "Release v0.1.1" v0.1.1.tar.gz.sha256 + + # Or if release already exists, just upload the checksum: + # gh release upload v0.1.1 v0.1.1.tar.gz.sha256 + ``` + This enables secure installation with SHA256 verification. ## Branch protections (recommended) diff --git a/README.md b/README.md index f48aac1..741382d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/feat/easy-install/in ``` +**Security note**: Tagged releases (v*) include SHA256 checksum verification when checksums are available as release assets. Branch installations skip verification as GitHub does not provide checksums for auto-generated archives. + 1. Clone or extract kctl-env to a directory (e.g., `~/.kctl-env`). 2. Add the `bin/` directory to your `PATH`: diff --git a/install.sh b/install.sh index c2c139a..42b3ec0 100644 --- a/install.sh +++ b/install.sh @@ -11,6 +11,7 @@ REPO_NAME="kctl-env" KCTL_ENV_ROOT="${KCTL_ENV_ROOT:-$HOME/.kctl-env}" KCTL_ENV_REF="${KCTL_ENV_REF:-}" +KCTL_ENV_SKIP_VERIFY="${KCTL_ENV_SKIP_VERIFY:-}" usage() { cat <&2 + else + checksum_url="https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/${ref}/${ref}.tar.gz.sha256" + echo "Verifying checksum..." + if curl -fsSL "$checksum_url" -o "$tmpdir/checksum.sha256" 2>/dev/null; then + # Extract just the hash (first field) and verify manually + expected_hash="$(awk '{print $1}' "$tmpdir/checksum.sha256")" + actual_hash="$(sha256sum "$archive" | awk '{print $1}')" + + # Validate that hashes were extracted successfully + if [[ -z "$expected_hash" || -z "$actual_hash" ]]; then + echo "Error: Failed to extract checksum values" >&2 + echo "The checksum file may be empty or malformed" >&2 + exit 1 + fi + + if [[ "$expected_hash" != "$actual_hash" ]]; then + echo "Checksum verification failed for $ref" >&2 + echo "Expected: $expected_hash" >&2 + echo "Actual: $actual_hash" >&2 + echo "This may indicate a compromised download or release." >&2 + exit 1 + fi + echo "Checksum verified successfully" + else + echo "Error: No checksum found for $ref at $checksum_url" >&2 + echo "For security, this installer requires SHA256 verification for tagged releases." >&2 + echo "To install an older release without checksums, use: KCTL_ENV_SKIP_VERIFY=1 $0 $ref" >&2 + exit 1 + fi + fi +else + echo "Note: Checksum verification skipped for branch '$ref' (not available for auto-generated archives)" +fi + # Extract mkdir -p "$tmpdir/src" tar -xzf "$archive" -C "$tmpdir/src" diff --git a/scripts/release.sh b/scripts/release.sh index 76261f9..085aba9 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -52,3 +52,11 @@ git add VERSION "$changelog_file" "$spec_file" git commit -m "release: bump to v$new_version" echo "Release files updated and committed. Open a PR if on a release branch." +echo +echo "After merging and creating the tag, remember to:" +echo " 1. Create a GitHub release for v$new_version" +echo " 2. Generate and attach checksum for the source tarball:" +echo " curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v$new_version.tar.gz | sha256sum > v$new_version.tar.gz.sha256" +echo " 3. Upload the checksum file as a release asset" +echo " This enables secure installation with: curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- v$new_version" + From 410c628d415fd0b7bf723faa57f02ef0e620ba10 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:32:47 +0530 Subject: [PATCH 12/30] Revert maintainer emails to project address in packaging metadata (#13) * Initial plan * fix: revert maintainer emails to project address in packaging files Co-authored-by: senet <4061835+senet@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: senet <4061835+senet@users.noreply.github.com> --- packaging/debian/changelog | 2 +- packaging/rpm/kctl-env.spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/debian/changelog b/packaging/debian/changelog index f928769..8e3e570 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -2,4 +2,4 @@ kctl-env (0.1.0) unstable; urgency=medium * Initial release. - -- kctl-env Maintainers Wed, 07 Jan 2026 22:00:00 +0000 + -- kctl-env Maintainers Wed, 07 Jan 2026 22:00:00 +0000 diff --git a/packaging/rpm/kctl-env.spec b/packaging/rpm/kctl-env.spec index d929307..4d5ef57 100644 --- a/packaging/rpm/kctl-env.spec +++ b/packaging/rpm/kctl-env.spec @@ -36,5 +36,5 @@ ln -sf /usr/lib/kctl-env/bin/kubectl %{buildroot}/usr/bin/kubectl /usr/bin/kubectl %changelog -* Wed Jan 07 2026 kctl-env Maintainers - 0.1.0-1 +* Wed Jan 07 2026 kctl-env Maintainers - 0.1.0-1 - Initial release From 50960a34637977032c7fd92792dd7e463747eb2e Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:42:50 +0530 Subject: [PATCH 13/30] Update install.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/install.sh b/install.sh index 42b3ec0..878180d 100644 --- a/install.sh +++ b/install.sh @@ -65,6 +65,8 @@ require_cmd find require_cmd head require_cmd tar require_cmd sha256sum +require_cmd awk +require_cmd basename tmpdir="$(mktemp -d)" trap 'rm -rf "$tmpdir"' EXIT From 667304799b6c3acdf9c85dc1d5c5114c6b26241a Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:43:14 +0530 Subject: [PATCH 14/30] Update install.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 878180d..625bd37 100644 --- a/install.sh +++ b/install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # kctl-env bootstrap installer -# - Zero deps beyond: bash, curl, tar, coreutils +# - Requires standard Unix tools: bash, curl, tar, coreutils (incl. install, mktemp, head, basename, sha256sum), find, awk # - Installs into $KCTL_ENV_ROOT (default: ~/.kctl-env) # - Preserves existing runtime dirs (versions/, cache/) From 78d4ca75b6592a7fce8b7803e56c07081fb0c80d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Feb 2026 17:53:52 +0530 Subject: [PATCH 15/30] Add macOS compatibility for SHA256 verification in installer (#14) * Initial plan * Add macOS compatibility for SHA256 verification with fallback support Co-authored-by: senet <4061835+senet@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: senet <4061835+senet@users.noreply.github.com> --- install.sh | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/install.sh b/install.sh index 625bd37..c3ea633 100644 --- a/install.sh +++ b/install.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # kctl-env bootstrap installer -# - Requires standard Unix tools: bash, curl, tar, coreutils (incl. install, mktemp, head, basename, sha256sum), find, awk +# - Requires standard Unix tools: bash, curl, tar, coreutils (incl. install, mktemp, head, basename), find, awk +# - SHA256 verification uses sha256sum (Linux), shasum (macOS), or openssl (fallback) # - Installs into $KCTL_ENV_ROOT (default: ~/.kctl-env) # - Preserves existing runtime dirs (versions/, cache/) @@ -58,13 +59,28 @@ require_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; } } +# Compute SHA256 hash using available tools +# Returns hash on stdout, exits non-zero on error +compute_sha256() { + local file="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$file" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$file" | awk '{print $1}' + elif command -v openssl >/dev/null 2>&1; then + openssl dgst -sha256 "$file" | awk '{print $NF}' + else + echo "Error: No SHA256 tool found (tried: sha256sum, shasum, openssl)" >&2 + exit 1 + fi +} + require_cmd install require_cmd mktemp require_cmd curl require_cmd find require_cmd head require_cmd tar -require_cmd sha256sum require_cmd awk require_cmd basename @@ -88,7 +104,7 @@ if [[ "$ref" == v* ]]; then if curl -fsSL "$checksum_url" -o "$tmpdir/checksum.sha256" 2>/dev/null; then # Extract just the hash (first field) and verify manually expected_hash="$(awk '{print $1}' "$tmpdir/checksum.sha256")" - actual_hash="$(sha256sum "$archive" | awk '{print $1}')" + actual_hash="$(compute_sha256 "$archive")" # Validate that hashes were extracted successfully if [[ -z "$expected_hash" || -z "$actual_hash" ]]; then From 934cceef97989d0591ab942213ac0974c693f549 Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:05:13 +0530 Subject: [PATCH 16/30] Update scripts/release.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- scripts/release.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/release.sh b/scripts/release.sh index 085aba9..d6dcd91 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -56,7 +56,10 @@ echo echo "After merging and creating the tag, remember to:" echo " 1. Create a GitHub release for v$new_version" echo " 2. Generate and attach checksum for the source tarball:" +echo " # On Linux / systems with GNU coreutils:" echo " curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v$new_version.tar.gz | sha256sum > v$new_version.tar.gz.sha256" +echo " # On macOS (no sha256sum by default):" +echo " curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v$new_version.tar.gz | shasum -a 256 > v$new_version.tar.gz.sha256" echo " 3. Upload the checksum file as a release asset" echo " This enables secure installation with: curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- v$new_version" From e49f322ba01e86dd20d7b46aec660deb5b2738ba Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:05:32 +0530 Subject: [PATCH 17/30] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 741382d..74f8135 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/feat/easy-install/in ``` -**Security note**: Tagged releases (v*) include SHA256 checksum verification when checksums are available as release assets. Branch installations skip verification as GitHub does not provide checksums for auto-generated archives. +**Security note**: Tagged releases (v*) require SHA256 checksum verification by default and will fail if the checksum asset is missing, unless you explicitly set `KCTL_ENV_SKIP_VERIFY=1` to skip verification. Branch installations skip verification as GitHub does not provide checksums for auto-generated archives. 1. Clone or extract kctl-env to a directory (e.g., `~/.kctl-env`). 2. Add the `bin/` directory to your `PATH`: From 24f837389c83f4c6775ab81f7b3f2044bdf89b59 Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:05:52 +0530 Subject: [PATCH 18/30] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 74f8135..7ddaadb 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ Install from the main branch (for development/testing): curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- main ``` -If you're testing from a feature branch, replace `main` in the URL and pass the branch name as the ref: +If you're testing from a feature branch, replace `main` in the URL (URL-encoding the branch name if it contains `/`) and pass the branch name as the ref: ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/feat/easy-install/install.sh | bash -s -- feat/easy-install +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/feat%2Feasy-install/install.sh | bash -s -- feat/easy-install ``` From eeb232272b37d17994aa7bdf43459f0999771c21 Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:06:11 +0530 Subject: [PATCH 19/30] Update CONTRIBUTING.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dce6b4d..4c4661b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,8 +40,10 @@ ``` 5. Generate and publish checksums for the release: ```sh - # Generate SHA256 checksum for the source tarball + # Generate SHA256 checksum for the source tarball (Linux / GNU coreutils) curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v0.1.1.tar.gz | sha256sum > v0.1.1.tar.gz.sha256 + # On macOS (BSD userland), use shasum instead: + # curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v0.1.1.tar.gz | shasum -a 256 > v0.1.1.tar.gz.sha256 # Create GitHub release and upload checksum as asset # Note: GitHub automatically provides the source tarball at: From ddc9def604b82f6b052a8cfb78896d168b68d4b1 Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:06:24 +0530 Subject: [PATCH 20/30] Update install.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- install.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index c3ea633..79fdcad 100644 --- a/install.sh +++ b/install.sh @@ -124,7 +124,9 @@ if [[ "$ref" == v* ]]; then else echo "Error: No checksum found for $ref at $checksum_url" >&2 echo "For security, this installer requires SHA256 verification for tagged releases." >&2 - echo "To install an older release without checksums, use: KCTL_ENV_SKIP_VERIFY=1 $0 $ref" >&2 + echo "If you still want to install this older release without checksums, you can re-run with:" >&2 + echo " Local file: KCTL_ENV_SKIP_VERIFY=1 ./install.sh $ref" >&2 + echo " Via curl: KCTL_ENV_SKIP_VERIFY=1 bash -s -- $ref" >&2 exit 1 fi fi From ff40a183ee3b117e101979d019ad28b6ed7df894 Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:06:38 +0530 Subject: [PATCH 21/30] Update install.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 79fdcad..72ad8d4 100644 --- a/install.sh +++ b/install.sh @@ -84,7 +84,7 @@ require_cmd tar require_cmd awk require_cmd basename -tmpdir="$(mktemp -d)" +tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/kctl-env.XXXXXX")" trap 'rm -rf "$tmpdir"' EXIT archive="$tmpdir/src.tar.gz" From c966ba6c56b0c7e99d80d52452c8231451a7c526 Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:06:55 +0530 Subject: [PATCH 22/30] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ddaadb..719a218 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Pure Bash, zero-deps kubectl version manager with fast shims and tfenv-style UX. Pin a specific version (recommended for reproducibility and supply-chain security): ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- v0.1.1 +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- v0.1.0 ``` Install from the main branch (for development/testing): From 8fb34e747cfbebb6be4d679df5ed1469335fd791 Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:20:39 +0530 Subject: [PATCH 23/30] Update libexec/kctl-env-list-remote Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- libexec/kctl-env-list-remote | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/libexec/kctl-env-list-remote b/libexec/kctl-env-list-remote index 26464b8..91c34b3 100755 --- a/libexec/kctl-env-list-remote +++ b/libexec/kctl-env-list-remote @@ -38,5 +38,20 @@ while [[ "$page" -le "$MAX_PAGES" ]]; do printf '%s\n' "$tags" page=$((page + 1)) done | - sort -Vr | - uniq + if sort -V /dev/null 2>&1; then + # Prefer GNU sort's version sort when available. + sort -Vr | uniq + else + # POSIX fallback: sort semantic versions vMAJOR.MINOR.PATCH numerically. + awk -F. ' + { + ver = $0 + # Strip leading "v" from major component. + sub(/^v/, "", $1) + # Print: original major minor patch + printf "%s %d %d %d\n", ver, $1, $2, $3 + } + ' | + sort -k2,2nr -k3,3nr -k4,4nr | + awk '!seen[$1]++ { print $1 }' + fi From 518570deee91503a9b435cba3c3aab8103e825d4 Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:20:56 +0530 Subject: [PATCH 24/30] Update scripts/release.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- scripts/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release.sh b/scripts/release.sh index d6dcd91..3ae19e9 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -61,5 +61,5 @@ echo " curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v$new_ echo " # On macOS (no sha256sum by default):" echo " curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v$new_version.tar.gz | shasum -a 256 > v$new_version.tar.gz.sha256" echo " 3. Upload the checksum file as a release asset" -echo " This enables secure installation with: curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- v$new_version" +echo " This enables secure installation with: curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/v$new_version/install.sh | bash -s -- v$new_version" From 17d92804d97654ca02a136e8ccbfcae5a46ab136 Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:21:14 +0530 Subject: [PATCH 25/30] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 719a218..144ba2d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Pure Bash, zero-deps kubectl version manager with fast shims and tfenv-style UX. Pin a specific version (recommended for reproducibility and supply-chain security): ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- v0.1.0 +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/v0.1.0/install.sh | bash -s -- v0.1.0 ``` Install from the main branch (for development/testing): From af8be08f266050de21a5bda2ea678aa28ccc3e8c Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:21:30 +0530 Subject: [PATCH 26/30] Update install.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 72ad8d4..05e2f14 100644 --- a/install.sh +++ b/install.sh @@ -138,8 +138,8 @@ fi mkdir -p "$tmpdir/src" tar -xzf "$archive" -C "$tmpdir/src" -# Determine extracted directory -src_root="$(find "$tmpdir/src" -mindepth 1 -maxdepth 1 -type d | head -n 1)" +# Determine extracted directory (portable: avoid non-POSIX -mindepth/-maxdepth) +src_root="$(find "$tmpdir/src" -type d ! -path "$tmpdir/src" | head -n 1)" if [[ -z "${src_root:-}" || ! -d "$src_root" ]]; then echo "Failed to locate extracted source directory" >&2 exit 1 From 517c844af248ae720eb379b9ac73b78720507c1f Mon Sep 17 00:00:00 2001 From: kctl-env Date: Sun, 8 Feb 2026 18:26:16 +0530 Subject: [PATCH 27/30] fix: robust checksum parsing in installer Read only first hash from checksum file, normalize case, validate SHA256, and require tr. --- install.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 05e2f14..7e38839 100644 --- a/install.sh +++ b/install.sh @@ -83,6 +83,7 @@ require_cmd head require_cmd tar require_cmd awk require_cmd basename +require_cmd tr tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/kctl-env.XXXXXX")" trap 'rm -rf "$tmpdir"' EXIT @@ -103,8 +104,8 @@ if [[ "$ref" == v* ]]; then echo "Verifying checksum..." if curl -fsSL "$checksum_url" -o "$tmpdir/checksum.sha256" 2>/dev/null; then # Extract just the hash (first field) and verify manually - expected_hash="$(awk '{print $1}' "$tmpdir/checksum.sha256")" - actual_hash="$(compute_sha256 "$archive")" + expected_hash="$(awk 'NR==1{print $1; exit}' "$tmpdir/checksum.sha256" | tr '[:upper:]' '[:lower:]')" + actual_hash="$(compute_sha256 "$archive" | tr '[:upper:]' '[:lower:]')" # Validate that hashes were extracted successfully if [[ -z "$expected_hash" || -z "$actual_hash" ]]; then @@ -112,6 +113,12 @@ if [[ "$ref" == v* ]]; then echo "The checksum file may be empty or malformed" >&2 exit 1 fi + + if [[ ! "$expected_hash" =~ ^[0-9a-f]{64}$ ]]; then + echo "Error: Checksum file is malformed (expected SHA256 hex)." >&2 + echo "Got: $expected_hash" >&2 + exit 1 + fi if [[ "$expected_hash" != "$actual_hash" ]]; then echo "Checksum verification failed for $ref" >&2 From 3642d1fd14418679ba9533610d1bcd7e4b8feb12 Mon Sep 17 00:00:00 2001 From: kctl-env Date: Sun, 8 Feb 2026 18:40:00 +0530 Subject: [PATCH 28/30] chore: make install.sh executable --- install.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 install.sh diff --git a/install.sh b/install.sh old mode 100644 new mode 100755 From 0d4aa37fa51bb845dfd25b61aaa355ccbd413bc4 Mon Sep 17 00:00:00 2001 From: kctl-env Date: Sun, 8 Feb 2026 18:41:10 +0530 Subject: [PATCH 29/30] docs: adjust installer and release guidance - Avoid referencing old tags without install.sh - Document immutable-release checksum attachment --- CONTRIBUTING.md | 7 +++---- README.md | 8 ++++---- scripts/release.sh | 3 ++- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c4661b..f9af004 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,14 +45,13 @@ # On macOS (BSD userland), use shasum instead: # curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v0.1.1.tar.gz | shasum -a 256 > v0.1.1.tar.gz.sha256 - # Create GitHub release and upload checksum as asset + # Create the GitHub release and attach the checksum asset + # Note: this repo uses immutable releases, so you must attach assets at + # creation time (you can't upload them later). # Note: GitHub automatically provides the source tarball at: # https://github.com/senet/kctl-env/archive/refs/tags/v0.1.1.tar.gz # We only need to upload the checksum file as an additional asset. gh release create v0.1.1 --title "v0.1.1" --notes "Release v0.1.1" v0.1.1.tar.gz.sha256 - - # Or if release already exists, just upload the checksum: - # gh release upload v0.1.1 v0.1.1.tar.gz.sha256 ``` This enables secure installation with SHA256 verification. diff --git a/README.md b/README.md index 144ba2d..b731312 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,16 @@ Pure Bash, zero-deps kubectl version manager with fast shims and tfenv-style UX. ### Quick install (recommended) -Pin a specific version (recommended for reproducibility and supply-chain security): +Install from the `main` branch (recommended until the next tagged release that includes `install.sh`): ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/v0.1.0/install.sh | bash -s -- v0.1.0 +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- main ``` -Install from the main branch (for development/testing): +Pin a specific version (recommended for reproducibility and supply-chain security after the next release): ```sh -curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/main/install.sh | bash -s -- main +curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/vX.Y.Z/install.sh | bash -s -- vX.Y.Z ``` If you're testing from a feature branch, replace `main` in the URL (URL-encoding the branch name if it contains `/`) and pass the branch name as the ref: diff --git a/scripts/release.sh b/scripts/release.sh index 3ae19e9..32c0467 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -60,6 +60,7 @@ echo " # On Linux / systems with GNU coreutils:" echo " curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v$new_version.tar.gz | sha256sum > v$new_version.tar.gz.sha256" echo " # On macOS (no sha256sum by default):" echo " curl -fsSL https://github.com/senet/kctl-env/archive/refs/tags/v$new_version.tar.gz | shasum -a 256 > v$new_version.tar.gz.sha256" -echo " 3. Upload the checksum file as a release asset" +echo " 3. Create the release and attach the checksum asset (immutable releases):" +echo " gh release create v$new_version --title \"v$new_version\" --notes \"Release v$new_version\" v$new_version.tar.gz.sha256" echo " This enables secure installation with: curl -fsSL https://raw.githubusercontent.com/senet/kctl-env/v$new_version/install.sh | bash -s -- v$new_version" From a4ae27b5be34c14be31f9e1e1be3c2bfa2ee301f Mon Sep 17 00:00:00 2001 From: Prosenjit Sen <4061835+senet@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:54:48 +0530 Subject: [PATCH 30/30] Update install.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Prosenjit Sen <4061835+senet@users.noreply.github.com> --- install.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 7e38839..4a5a5af 100755 --- a/install.sh +++ b/install.sh @@ -145,8 +145,14 @@ fi mkdir -p "$tmpdir/src" tar -xzf "$archive" -C "$tmpdir/src" -# Determine extracted directory (portable: avoid non-POSIX -mindepth/-maxdepth) -src_root="$(find "$tmpdir/src" -type d ! -path "$tmpdir/src" | head -n 1)" +# Determine extracted directory (choose first top-level subdirectory deterministically) +src_root="" +for d in "$tmpdir/src"/*; do + if [[ -d "$d" ]]; then + src_root="$d" + break + fi +done if [[ -z "${src_root:-}" || ! -d "$src_root" ]]; then echo "Failed to locate extracted source directory" >&2 exit 1