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
15 changes: 15 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,18 @@ jobs:
run: gh release edit "$GITHUB_REF_NAME" --prerelease=false --latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

install-script:
name: install.sh (${{ matrix.os }})
needs: goreleaser
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: sh install.sh --version "$GITHUB_REF_NAME" --bin-dir "$RUNNER_TEMP/bin"
- run: flagsmith --version
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ repos:
- id: check-added-large-files
- id: check-merge-conflict

- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1
hooks:
- id: shellcheck

- repo: https://github.com/scop/pre-commit-shfmt
rev: v3.13.1-1
hooks:
- id: shfmt

- repo: https://github.com/golangci/golangci-lint
rev: v2.11.4
hooks:
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

The next-generation Flagsmith command-line interface (work in progress).

## Install

```sh
curl -fsSL https://raw.githubusercontent.com/Flagsmith/flagsmith-cli/main/install.sh | sh
```

Installs to `$HOME/.local/bin` and adds it to your `PATH`. Options:

```sh
curl -fsSL https://raw.githubusercontent.com/Flagsmith/flagsmith-cli/main/install.sh | sh -s -- --version v2.0.0 --bin-dir /usr/local/bin --no-modify-path
curl -fsSL https://raw.githubusercontent.com/Flagsmith/flagsmith-cli/main/install.sh | sh -s -- --help
```

`FLAGSMITH_CLI_VERSION`, `FLAGSMITH_INSTALL_DIR` and `FLAGSMITH_NO_MODIFY_PATH` do the same if exported first.

To pin the installer itself, fetch it at a commit you trust: `raw.githubusercontent.com/Flagsmith/flagsmith-cli/<sha>/install.sh`.

Alternatively, `go install github.com/Flagsmith/flagsmith-cli@latest`, or grab an archive from [Releases](https://github.com/Flagsmith/flagsmith-cli/releases).

## Build

```sh
Expand Down
270 changes: 270 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
#!/bin/sh
# Install the Flagsmith CLI.
#
# curl -fsSL https://get.flagsmith.com | sh
# curl -fsSL https://get.flagsmith.com | sh -s -- --version <tag>
#
# A variable assignment in front of `curl` applies to curl, not to sh, so pass
# the version as a flag or export it first.
set -eu

DEFAULT_VERSION="v2.0.0-beta.1" # x-release-please-version

REPO="Flagsmith/flagsmith-cli"
BIN_NAME="flagsmith"
BASE_URL="${FLAGSMITH_CLI_BASE_URL:-https://github.com/${REPO}/releases/download}"

usage() {
cat <<EOF
Install the Flagsmith CLI.

Usage: install.sh [options]

Options:
-v, --version <tag> Version to install (default: ${DEFAULT_VERSION})
--bin-dir <dir> Where to install (default: \$HOME/.local/bin)
--no-modify-path Leave shell startup files alone
--dry-run Report what would be installed, then stop
-h, --help Show this message

Environment:
FLAGSMITH_CLI_VERSION Same as --version
FLAGSMITH_INSTALL_DIR Same as --bin-dir
FLAGSMITH_NO_MODIFY_PATH Set to 1 for --no-modify-path
FLAGSMITH_CLI_BASE_URL Release download base URL
EOF
}

say() { printf '%s\n' "$*"; }
err() {
printf '%s\n' "install.sh: $*" >&2
exit 1
}

need_cmd() {
command -v "$1" >/dev/null 2>&1 || err "need '$1' (command not found)"
}

download() {
if [ "$DOWNLOADER" = curl ]; then
case "$1" in
# Refuse a downgrade to http on our own URLs; a base URL someone set
# themselves is their call.
https://*) curl --proto '=https' --tlsv1.2 -fsSL --retry 3 -o "$2" "$1" ;;
*) curl -fsSL --retry 3 -o "$2" "$1" ;;
esac
else
wget --quiet --output-document="$2" "$1"
fi
}

parse_args() {
VERSION="${FLAGSMITH_CLI_VERSION:-$DEFAULT_VERSION}"
INSTALL_DIR="${FLAGSMITH_INSTALL_DIR:-}"
NO_MODIFY_PATH="${FLAGSMITH_NO_MODIFY_PATH:-0}"
DRY_RUN=0

while [ $# -gt 0 ]; do
case "$1" in
-v | --version)
[ $# -ge 2 ] || err "--version needs a value, e.g. --version ${DEFAULT_VERSION}"
VERSION="$2"
shift 2
;;
--bin-dir)
[ $# -ge 2 ] || err "--bin-dir needs a value"
INSTALL_DIR="$2"
shift 2
;;
--no-modify-path)
NO_MODIFY_PATH=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
-h | --help)
usage
exit 0
;;
*) err "unknown option '$1' (try --help)" ;;
esac
done

case "$VERSION" in
v*) ;;
*) VERSION="v${VERSION}" ;;
esac
: "${INSTALL_DIR:=${HOME}/.local/bin}"
}

# detect_platform sets OS and ARCH to the halves of a release archive name.
detect_platform() {
OS=$(uname -s)
ARCH=$(uname -m)

case "$OS" in
Linux) OS=linux ;;
Darwin) OS=darwin ;;
MINGW* | MSYS* | CYGWIN* | Windows_NT)
err "Windows is not supported by this script — download the .zip from https://github.com/${REPO}/releases"
;;
*) err "unsupported operating system '${OS}'" ;;
esac

case "$ARCH" in
x86_64 | amd64) ARCH=amd64 ;;
aarch64 | arm64) ARCH=arm64 ;;
*) err "unsupported architecture '${ARCH}' — 'go install github.com/${REPO}@${VERSION}' builds from source" ;;
Comment thread
Zaimwa9 marked this conversation as resolved.
esac

# uname reports x86_64 under Rosetta.
if [ "$OS" = darwin ] && [ "$ARCH" = amd64 ] &&
[ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = 1 ]; then
ARCH=arm64
fi
}

verify_checksum() {
_archive="$1"
_name=$(basename "$_archive")
_matches=$(awk -v name="$_name" '$2 == name || $2 == "*" name {print $1}' "$2")
[ "$(printf '%s' "$_matches" | grep -c .)" = 1 ] ||
err "expected exactly one checksum for ${_name} in checksums.txt"

if command -v sha256sum >/dev/null 2>&1; then
_actual=$(sha256sum "$_archive" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
_actual=$(shasum -a 256 "$_archive" | awk '{print $1}')
elif command -v openssl >/dev/null 2>&1; then
_actual=$(openssl dgst -sha256 "$_archive" | awk '{print $NF}')
else
err "need 'sha256sum', 'shasum' or 'openssl' to verify the download"
fi

[ "$_actual" = "$_matches" ] ||
err "checksum mismatch for ${_name}: expected ${_matches}, got ${_actual}"
}

# write_env_scripts writes the snippets the startup files source. They live
# under our own directory: $HOME/.local/bin/env belongs to cargo-dist.
write_env_scripts() {
mkdir -p "$(dirname "$ENV_SCRIPT")"
cat >"$ENV_SCRIPT" <<EOF
# Added by the Flagsmith CLI installer.
case ":\${PATH}:" in
*:"${INSTALL_DIR}":*) ;;
*) export PATH="${INSTALL_DIR}:\${PATH}" ;;
esac
EOF
cat >"${ENV_SCRIPT}.fish" <<EOF
# Added by the Flagsmith CLI installer.
if not contains "${INSTALL_DIR}" \$PATH
set -gx PATH "${INSTALL_DIR}" \$PATH
end
EOF
}

# add_ci_path makes the CLI available to later steps of a GitHub Actions job.
# GITHUB_PATH does not expand variables, so write the resolved directory.
add_ci_path() {
[ -n "${GITHUB_PATH:-}" ] || return 0
printf '%s\n' "$INSTALL_DIR" >>"$GITHUB_PATH"
say " added ${INSTALL_DIR} to \$GITHUB_PATH"
}

# add_source_line appends to a startup file, once.
add_source_line() {
grep -qF "$2" "$1" && return 0
printf '\n%s\n' "$2" >>"$1"
say " updated $1"
}

modify_path() {
case ":${PATH}:" in
*:"${INSTALL_DIR}":*) return 0 ;;
esac

write_env_scripts
_line=". \"${ENV_SCRIPT}\""
_edited=0
for _rc in .profile .bashrc .bash_profile .bash_login .zshrc .zshenv; do
if [ -f "${HOME}/${_rc}" ]; then
add_source_line "${HOME}/${_rc}" "$_line"
_edited=1
fi
done
if [ "$_edited" = 0 ]; then
printf '%s\n' "$_line" >>"${HOME}/.profile"
say " created ${HOME}/.profile"
fi
if [ -d "${HOME}/.config/fish" ]; then
mkdir -p "${HOME}/.config/fish/conf.d"
printf 'source "%s"\n' "${ENV_SCRIPT}.fish" >"${HOME}/.config/fish/conf.d/flagsmith.fish"
say " updated ${HOME}/.config/fish/conf.d/flagsmith.fish"
fi
PATH_MODIFIED=1
}

main() {
parse_args "$@"

need_cmd uname
need_cmd tar
if command -v curl >/dev/null 2>&1; then
DOWNLOADER=curl
elif command -v wget >/dev/null 2>&1; then
DOWNLOADER=wget
else
err "need 'curl' or 'wget'"
fi

detect_platform
ENV_SCRIPT="${XDG_DATA_HOME:-${HOME}/.local/share}/flagsmith/env"
PATH_MODIFIED=0

archive_name="${BIN_NAME}_${VERSION#v}_${OS}_${ARCH}.tar.gz"
archive_url="${BASE_URL}/${VERSION}/${archive_name}"
sums_url="${BASE_URL}/${VERSION}/checksums.txt"

if [ "$DRY_RUN" = 1 ]; then
say "would install ${BIN_NAME} ${VERSION} (${OS}/${ARCH}) to ${INSTALL_DIR}"
say " archive: ${archive_url}"
say " checksums: ${sums_url}"
return 0
fi

tmp=$(mktemp -d 2>/dev/null || mktemp -d -t flagsmith)
trap 'rm -rf "$tmp"' EXIT INT TERM

say "downloading ${BIN_NAME} ${VERSION} (${OS}/${ARCH})"
download "$archive_url" "${tmp}/${archive_name}" ||
err "cannot download ${archive_url}
If ${VERSION} was released moments ago its archives may still be uploading — retry shortly, or choose a version with --version."
download "$sums_url" "${tmp}/checksums.txt" || err "cannot download ${sums_url}"
verify_checksum "${tmp}/${archive_name}" "${tmp}/checksums.txt"

tar -xzf "${tmp}/${archive_name}" -C "$tmp" "$BIN_NAME"
mkdir -p "$INSTALL_DIR"
chmod 755 "${tmp}/${BIN_NAME}"
mv -f "${tmp}/${BIN_NAME}" "${INSTALL_DIR}/${BIN_NAME}"

installed=$("${INSTALL_DIR}/${BIN_NAME}" --version 2>/dev/null) ||
err "${INSTALL_DIR}/${BIN_NAME} was installed but will not run — wrong platform?"
say "installed ${installed} to ${INSTALL_DIR}/${BIN_NAME}"

if [ "$NO_MODIFY_PATH" != 1 ]; then
modify_path
add_ci_path
fi

say ""
if [ "$PATH_MODIFIED" = 1 ]; then
say "Run '. \"${ENV_SCRIPT}\"' or open a new shell, then '${BIN_NAME} init' to get started."
else
say "Run '${BIN_NAME} init' to get started."
fi
}

main "$@"
5 changes: 4 additions & 1 deletion release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
"prerelease": true,
"prerelease-type": "beta",
"draft": false,
"include-component-in-tag": false
"include-component-in-tag": false,
"extra-files": [
"install.sh"
]
}
},
"changelog-sections": [
Expand Down