Skip to content

Commit 2bf863e

Browse files
Install script (#543)
* feature: make a script that is easier to do one-line install across OS's * Fix download URL handling for Windows and Linux in install script * Improve error handling for download failures in install script * refactor: simplify Windows script execution in install tests * fix: correct file extension from .yaml to .yml in workflow paths * feat: enable manual triggering of install script tests * fix: improve error handling in install script * feat: add debug mode and improve version detection in install script * fix: improve OS and architecture detection in install script * Tidy up description of steps in the CI job * fix: correct typo in install script step and enable debug mode * fix: enhance GitHub API response handling for latest version detection * fix: improve retry mechanism for fetching latest version from GitHub API * fix: add GitHub token support to install script for authenticated API requests * fix: update installation instructions for Kosli CLI to include script and Docker usage * fix: update wording in installation instructions for clarity
1 parent 5c1b6a4 commit 2bf863e

File tree

3 files changed

+273
-13
lines changed

3 files changed

+273
-13
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Install Script Tests
2+
3+
on:
4+
push:
5+
paths:
6+
- 'install-cli.sh'
7+
- '.github/workflows/install-script-tests.yml'
8+
pull_request:
9+
paths:
10+
- 'install-cli.sh'
11+
- '.github/workflows/install-script-tests.yml'
12+
workflow_dispatch:
13+
14+
jobs:
15+
test-script:
16+
name: Test Bash Script on ${{ matrix.os }}
17+
runs-on: ${{ matrix.os }}
18+
strategy:
19+
matrix:
20+
os: [ubuntu-latest, macos-latest, windows-latest]
21+
22+
steps:
23+
- name: Checkout repository
24+
uses: actions/checkout@v5
25+
26+
- name: Run install script
27+
shell: bash
28+
run: bash install-cli.sh --debug --token ${{ secrets.GITHUB_TOKEN }}
29+
30+
- name: Verify installation
31+
shell: bash
32+
run: kosli version

docs.kosli.com/content/getting_started/install.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,28 @@ Kosli CLI can be installed from package managers,
1111
by Curling pre-built binaries, or can be used from the distributed Docker images.
1212
{{< tabs "installKosli" >}}
1313

14+
{{< tab "Script" >}}
15+
You can download the correct Kosli CLI for your platform, given that you can run shell scripts on it, by invoking this one-line script:
16+
17+
```shell {.command}
18+
curl -fL https://raw.githubusercontent.com/kosli-dev/cli/refs/heads/main/install-cli.sh | sh
19+
```
20+
{{< /tab >}}
21+
22+
{{< tab "Docker" >}}
23+
You can run the Kosli CLI with docker:
24+
```shell {.command}
25+
docker run --rm ghcr.io/kosli-dev/cli:v{{< cli-version >}}
26+
```
27+
The `entrypoint` for this container is the kosli command.
28+
29+
To run any kosli command you append it to the `docker run` command above –
30+
without the `kosli` keyword. For example to run `kosli version`:
31+
```shell {.command}
32+
docker run --rm ghcr.io/kosli-dev/cli:v{{< cli-version >}} version
33+
```
34+
{{< /tab >}}
35+
1436
{{< tab "Homebrew" >}}
1537
If you have [Homebrew](https://brew.sh/) (available on MacOS, Linux or Windows Subsystem for Linux),
1638
you can install the Kosli CLI by running:
@@ -64,20 +86,7 @@ For example, on Mac with AMD:
6486
curl -L https://github.com/kosli-dev/cli/releases/download/v{{< cli-version >}}/kosli_{{< cli-version >}}_darwin_amd64.tar.gz | tar zx
6587
sudo mv kosli /usr/local/bin/kosli
6688
```
67-
{{< /tab >}}
68-
69-
{{< tab "Docker" >}}
70-
You can run the Kosli CLI with docker:
71-
```shell {.command}
72-
docker run --rm ghcr.io/kosli-dev/cli:v{{< cli-version >}}
73-
```
74-
The `entrypoint` for this container is the kosli command.
7589

76-
To run any kosli command you append it to the `docker run` command above –
77-
without the `kosli` keyword. For example to run `kosli version`:
78-
```shell {.command}
79-
docker run --rm ghcr.io/kosli-dev/cli:v{{< cli-version >}} version
80-
```
8190
{{< /tab >}}
8291

8392
{{< tab "From source" >}}

install-cli.sh

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
# This script downloads the OS- and architecture-specific Kosli CLI binary,
5+
# extracts it, and moves the executable to a directory in your PATH.
6+
7+
# --- Configuration ---
8+
CLI_OS="unknown"
9+
ARCH="unknown"
10+
VERSION=""
11+
FILE_NAME="kosli"
12+
DEBUG=false
13+
GITHUB_TOKEN=""
14+
15+
# --- Debug function ---
16+
debug_print() {
17+
if [ "$DEBUG" = true ]; then
18+
echo "DEBUG: $1" >&2
19+
fi
20+
}
21+
22+
# --- Parse arguments ---
23+
while [ $# -gt 0 ]; do
24+
case $1 in
25+
--debug)
26+
DEBUG=true
27+
debug_print "Debug mode enabled"
28+
shift
29+
;;
30+
--token)
31+
if [ -n "${2:-}" ]; then
32+
GITHUB_TOKEN="$2"
33+
debug_print "GitHub token provided"
34+
shift 2
35+
else
36+
echo "Error: --token requires a value"
37+
exit 1
38+
fi
39+
;;
40+
*)
41+
VERSION=$1
42+
debug_print "Version specified: $VERSION"
43+
shift
44+
;;
45+
esac
46+
done
47+
48+
# --- Version Selection ---
49+
if [ -n "$VERSION" ]; then
50+
echo "Downloading specified version $VERSION of Kosli CLI..."
51+
debug_print "Using specified version: $VERSION"
52+
else
53+
echo "Detecting the latest version of Kosli CLI..."
54+
debug_print "Fetching latest version from GitHub API"
55+
56+
# Retry mechanism for fetching the latest version
57+
RETRY_COUNT=0
58+
MAX_RETRIES=5
59+
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
60+
if [ -n "$GITHUB_TOKEN" ]; then
61+
debug_print "Using GitHub token for API request"
62+
METADATA=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/kosli-dev/cli/releases/latest")
63+
else
64+
debug_print "Using unauthenticated API request"
65+
METADATA=$(curl -s "https://api.github.com/repos/kosli-dev/cli/releases/latest")
66+
fi
67+
debug_print "GitHub API response: $METADATA"
68+
69+
# Check if the response contains the expected tag_name
70+
if echo "$METADATA" | grep -q '"tag_name":'; then
71+
TAG_NAME=$(echo "$METADATA" | grep '"tag_name":')
72+
debug_print "GitHub API response tag: $TAG_NAME"
73+
LATEST_TAG=$(echo "$TAG_NAME" | sed -E 's/.*"([^"]+)".*/\1/')
74+
debug_print "GitHub API response tag: $LATEST_TAG"
75+
if [ -z "$LATEST_TAG" ]; then
76+
echo "Error: Could not fetch the latest version tag from GitHub."
77+
exit 1
78+
fi
79+
VERSION=$LATEST_TAG
80+
echo "Latest version is $VERSION. Downloading..."
81+
debug_print "Set VERSION to: $VERSION"
82+
break
83+
else
84+
echo "Warning: GitHub API response did not contain a valid tag_name. Retrying in 5 seconds..."
85+
sleep 5
86+
RETRY_COUNT=$((RETRY_COUNT + 1))
87+
if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
88+
echo "Error: GitHub rate limit exceeded too many times."
89+
exit 1
90+
fi
91+
fi
92+
done
93+
fi
94+
echo ""
95+
96+
# Strip the 'v' prefix for use in the filename, e.g., v2.11.22 -> 2.11.22
97+
VERSION_FILENAME=$(echo "$VERSION" | sed 's/^v//')
98+
debug_print "VERSION_FILENAME after stripping 'v': $VERSION_FILENAME"
99+
100+
# --- OS and Architecture Detection ---
101+
debug_print "Detecting OS and architecture"
102+
debug_print "uname -s output: $(uname -s)"
103+
debug_print "uname -m output: $(uname -m)"
104+
105+
UNAME_S=$(uname -s)
106+
if echo "$UNAME_S" | grep -q -E -i "(cygwin|mingw|msys|windows)"; then
107+
CLI_OS="windows"
108+
ARCH="amd64"
109+
FILE_NAME="${FILE_NAME}.exe"
110+
debug_print "Detected Windows OS"
111+
elif echo "$UNAME_S" | grep -q -i "darwin"; then
112+
CLI_OS="darwin"
113+
debug_print "Detected Darwin/macOS"
114+
UNAME_M=$(uname -m)
115+
if [ "$UNAME_M" = "arm64" ]; then
116+
ARCH="arm64"
117+
debug_print "Detected ARM64 architecture"
118+
else
119+
ARCH="amd64"
120+
debug_print "Detected AMD64 architecture"
121+
fi
122+
else
123+
CLI_OS="linux"
124+
debug_print "Detected Linux OS"
125+
MACHINE_TYPE="$(uname -m)"
126+
debug_print "Machine type: $MACHINE_TYPE"
127+
case $MACHINE_TYPE in
128+
amd64 | x86_64 | x64)
129+
ARCH="amd64"
130+
debug_print "Mapped to AMD64 architecture"
131+
;;
132+
aarch64 | arm64)
133+
ARCH="arm64"
134+
debug_print "Mapped to ARM64 architecture"
135+
;;
136+
*)
137+
echo "Error: Unsupported Linux architecture: $MACHINE_TYPE"
138+
echo "Kosli CLI is only available for amd64 and arm64 on Linux."
139+
exit 1
140+
;;
141+
esac
142+
fi
143+
144+
debug_print "Final values - CLI_OS: $CLI_OS, ARCH: $ARCH, FILE_NAME: $FILE_NAME"
145+
146+
# --- Download and Extract ---
147+
# The download is a .tar.gz or .zip file which needs to be extracted
148+
if [ "$CLI_OS" = "windows" ]; then
149+
URL="https://github.com/kosli-dev/cli/releases/download/${VERSION}/kosli_${VERSION_FILENAME}_${CLI_OS}_${ARCH}.zip"
150+
debug_print "Windows URL constructed: $URL"
151+
echo "Downloading from: $URL"
152+
# Download and extract for Windows
153+
debug_print "Starting Windows download and extraction"
154+
if ! curl -L --fail "$URL" -o kosli.zip; then
155+
echo "Error: Download failed. Please check the URL and your network connection."
156+
exit 1
157+
fi
158+
debug_print "Download completed, extracting zip file"
159+
unzip -o kosli.zip
160+
debug_print "Extraction completed"
161+
else
162+
URL="https://github.com/kosli-dev/cli/releases/download/${VERSION}/kosli_${VERSION_FILENAME}_${CLI_OS}_${ARCH}.tar.gz"
163+
debug_print "Unix URL constructed: $URL"
164+
echo "Downloading from: $URL"
165+
# Download and extract for Linux and Darwin
166+
debug_print "Starting Unix download and extraction"
167+
if ! curl -L --fail "$URL" | tar zx; then
168+
echo "Error: Download or extraction failed. Please check the URL and your network connection."
169+
exit 1
170+
fi
171+
debug_print "Download and extraction completed"
172+
fi
173+
174+
# --- Installation ---
175+
# Move the extracted binary to a directory in the user's PATH
176+
echo "Installing Kosli CLI..."
177+
debug_print "Starting installation process"
178+
debug_print "Current PATH: $PATH"
179+
180+
# Check directories one by one instead of using set --
181+
for dir in "/usr/local/bin" "/usr/bin" "/opt/bin"; do
182+
debug_print "Checking directory: $dir"
183+
# Check if destination directory exists and is in the PATH
184+
if [ -d "$dir" ] && echo "$PATH" | grep -q "$dir"; then
185+
debug_print "Directory $dir exists and is in PATH"
186+
debug_print "Attempting to move $FILE_NAME to $dir"
187+
if mv "$FILE_NAME" "$dir/"; then
188+
echo ""
189+
echo "✅ Kosli CLI was successfully installed in $dir"
190+
echo "Running 'kosli version' to verify:"
191+
debug_print "Installation successful, running version check"
192+
kosli version
193+
exit 0
194+
else
195+
echo ""
196+
echo "Attempting to install with sudo..."
197+
echo "We'd like to install the Kosli CLI executable in '$dir'. Please enter your password if prompted."
198+
debug_print "Regular move failed, trying with sudo"
199+
if sudo mv "$FILE_NAME" "$dir/"; then
200+
echo ""
201+
echo "✅ Kosli CLI was successfully installed in $dir"
202+
echo "Running 'kosli version' to verify:"
203+
debug_print "Sudo installation successful, running version check"
204+
kosli version
205+
exit 0
206+
fi
207+
debug_print "Sudo move also failed for $dir"
208+
fi
209+
else
210+
debug_print "Directory $dir either doesn't exist or is not in PATH"
211+
fi
212+
done
213+
214+
debug_print "All installation attempts failed"
215+
echo ""
216+
echo "Error: Could not install Kosli CLI."
217+
echo "Please move the '$FILE_NAME' executable manually to a directory in your \$PATH."
218+
echo "For example, you can run: sudo mv \"$FILE_NAME\" /usr/local/bin/"
219+
exit 1

0 commit comments

Comments
 (0)