Skip to content
Merged
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
214 changes: 158 additions & 56 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# REQUESTY_HOME Data directory (default: $HOME/.requesty)
# REQUESTY_INSTALL_DIR Directory for the binary (default: $REQUESTY_HOME/bin)
# REQUESTY_VERSION Release tag to install (default: latest)
# NO_COLOR Disable colored output
#
# A marker file is created in the temporary directory while installing and
# removed once the installation completes, so an interrupted run leaves a trace.
Expand All @@ -30,15 +31,70 @@ BINARY="requesty"
MARKER_BEGIN="# >>> requesty cli installer >>>"
MARKER_END="# <<< requesty cli installer <<<"

# Colors are used when stdout is a terminal and NO_COLOR is unset. The script
# is normally piped into sh, but stdout still points at the terminal.
setup_style() {
BOLD="" DIM="" RED="" GREEN="" YELLOW="" CYAN="" RESET=""
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-dumb}" != "dumb" ]; then
BOLD="$(printf '\033[1m')"
DIM="$(printf '\033[2m')"
RED="$(printf '\033[31m')"
GREEN="$(printf '\033[32m')"
YELLOW="$(printf '\033[33m')"
CYAN="$(printf '\033[36m')"
RESET="$(printf '\033[0m')"
fi

TICK="*" CROSS="x" BAR="|"
case "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" in
*UTF-8* | *utf8* | *UTF8* | *utf-8*)
TICK="✓" CROSS="✗" BAR="│"
;;
esac
}

banner() {
printf '%s' "$CYAN"
cat <<'EOF'

____ _
| _ \ ___ __ _ _ _ ___ ___| |_ _ _
| |_) / _ \/ _` | | | |/ _ \/ __| __| | | |
| _ < __/ (_| | |_| | __/\__ \ |_| |_| |
|_| \_\___|\__, |\__,_|\___||___/\__|\__, |
|_| |___/
EOF
printf '%s\n' "$RESET"
printf ' %sRequesty CLI installer%s\n\n' "$BOLD" "$RESET"
}

# step prints a completed installation step.
step() {
printf ' %s%s%s %s\n' "$GREEN" "$TICK" "$RESET" "$1"
}

warn() {
printf ' %s!%s %s\n' "$YELLOW" "$RESET" "$1"
}

info() {
printf '%s\n' "$1"
printf ' %s\n' "$1"
}

# action prints one line inside the highlighted "next steps" block.
action() {
printf ' %s%s%s %s\n' "$CYAN" "$BAR" "$RESET" "$1"
}

command_hint() {
printf '%s%s%s' "$BOLD" "$1" "$RESET"
}

fail() {
printf 'error: %s\n' "$1" >&2
printf '\n %s%s error:%s %s\n' "$RED" "$CROSS" "$RESET" "$1" >&2

if [ -f "${MARKER_FILE:-}" ]; then
printf 'the installation did not complete, %s was left behind\n' "$MARKER_FILE" >&2
printf ' %sthe installation did not complete, %s was left behind%s\n' "$DIM" "$MARKER_FILE" "$RESET" >&2
fi

exit 1
Expand Down Expand Up @@ -103,13 +159,14 @@ verify_checksum() {
elif command -v shasum >/dev/null 2>&1; then
checksum="$(shasum -a 256 "$dir/$file" | cut -d ' ' -f 1)"
else
info "Neither sha256sum nor shasum is available, skipping checksum verification"
warn "Neither sha256sum nor shasum is available, skipping checksum verification"
return 0
fi

expected="$(grep " $file\$" "$dir/checksums.txt" | cut -d ' ' -f 1)"
[ -n "$expected" ] || fail "$file is missing from checksums.txt"
[ "$checksum" = "$expected" ] || fail "checksum mismatch for $file: expected $expected, got $checksum"
step "Verified checksum"
}

# latest_version resolves the tag the "latest" release points at, so the
Expand All @@ -134,9 +191,9 @@ install_release() {
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT INT TERM

info "Downloading $archive"
download "$base/$archive" "$tmp/$archive"
download "$base/checksums.txt" "$tmp/checksums.txt"
step "Downloaded $archive"
verify_checksum "$tmp" "$archive"

tar -xzf "$tmp/$archive" -C "$tmp"
Expand All @@ -147,74 +204,117 @@ install_release() {

# Replace via rename so a running binary is never written into.
mv -f "$tmp/$BINARY" "$INSTALL_DIR/$BINARY"
step "Installed $BINARY to $INSTALL_DIR/$BINARY"

mkdir -p "$REQUESTY_HOME"
}

# configure_path adds the install directory to PATH in the configuration file
# of the current shell, inside a marker block so upgrades stay idempotent.
configure_path() {
[ "$MODIFY_PATH" = true ] || return 0

case ":$PATH:" in
*":$INSTALL_DIR:"*) return 0 ;;
esac

shell_name="$(basename "${SHELL:-sh}")"
case "$shell_name" in
fish)
profile="${XDG_CONFIG_HOME:-$HOME/.config}/fish/config.fish"
line="fish_add_path \"$INSTALL_DIR\""
;;
zsh)
profile="${ZDOTDIR:-$HOME}/.zshrc"
line="export PATH=\"$INSTALL_DIR:\$PATH\""
;;
# shell_profile prints the configuration file of the current shell.
shell_profile() {
case "$1" in
fish) echo "${XDG_CONFIG_HOME:-$HOME/.config}/fish/config.fish" ;;
zsh) echo "${ZDOTDIR:-$HOME}/.zshrc" ;;
bash)
profile="$HOME/.bashrc"
[ -f "$profile" ] || [ ! -f "$HOME/.bash_profile" ] || profile="$HOME/.bash_profile"
line="export PATH=\"$INSTALL_DIR:\$PATH\""
;;
*)
profile="$HOME/.profile"
line="export PATH=\"$INSTALL_DIR:\$PATH\""
if [ ! -f "$HOME/.bashrc" ] && [ -f "$HOME/.bash_profile" ]; then
echo "$HOME/.bash_profile"
else
echo "$HOME/.bashrc"
fi
;;
*) echo "$HOME/.profile" ;;
esac
}

if [ ! -f "$profile" ]; then
info "Could not configure PATH because $profile does not exist. Add this line to your shell configuration:"
info " $line"
return 0
fi
# path_line prints the line that puts the install directory on PATH.
path_line() {
case "$1" in
fish) echo "fish_add_path \"$INSTALL_DIR\"" ;;
*) echo "export PATH=\"$INSTALL_DIR:\$PATH\"" ;;
esac
}

if grep -F "$MARKER_BEGIN" "$profile" >/dev/null 2>&1; then
PROFILE="$profile"
return 0
fi
# configure_path appends the PATH line to the profile inside a marker block so
# upgrades stay idempotent. It returns 0 only when the profile was modified.
configure_path() {
profile="$1"
line="$2"

[ -f "$profile" ] || return 1
grep -F "$MARKER_BEGIN" "$profile" >/dev/null 2>&1 && return 1

{
echo ""
echo "$MARKER_BEGIN"
echo "$line"
echo "$MARKER_END"
} >>"$profile"
}

PROFILE="$profile"
PROFILE_UPDATED=true
# short_path replaces a leading $HOME with ~ so commands are easier to read.
short_path() {
case "$1" in
"$HOME"/*) printf '~%s\n' "${1#"$HOME"}" ;;
*) printf '%s\n' "$1" ;;
esac
}

# summary prints the final report. The optional pair of arguments is a PATH
# related instruction and the command that goes with it.
summary() {
info "Installed $BINARY $VERSION to $INSTALL_DIR/$BINARY"
path_instruction="${1:-}"
path_command="${2:-}"

printf '\n %s%s%s %s%s %s is ready%s\n\n' "$GREEN" "$TICK" "$RESET" "$BOLD" "$BINARY" "$VERSION" "$RESET"

if [ -f "$REQUESTY_HOME/config.json" ]; then
info "Kept your configuration in $REQUESTY_HOME/config.json"
info "${DIM}Kept your configuration in $REQUESTY_HOME/config.json${RESET}"
printf '\n'
fi

printf ' %s%s%s %sNext steps%s\n' "$CYAN" "$BAR" "$RESET" "$BOLD" "$RESET"
action ""

n=1
if [ -n "$path_instruction" ]; then
action "$n. $path_instruction"
action " $(command_hint "$path_command")"
n=$((n + 1))
fi

action "$n. Start the CLI:"
action " $(command_hint "$BINARY")"
action ""
printf '\n'
}

# finish handles PATH configuration and prints the summary.
finish() {
rm -f "$MARKER_FILE"

if [ "$MODIFY_PATH" != true ]; then
summary
return 0
fi

if [ "${PROFILE_UPDATED:-false}" = true ]; then
info "Added $INSTALL_DIR to PATH in $PROFILE"
info "Run 'source $PROFILE' or open a new terminal, then run '$BINARY'"
case ":$PATH:" in
*":$INSTALL_DIR:"*)
summary
return 0
;;
esac

shell_name="$(basename "${SHELL:-sh}")"
profile="$(shell_profile "$shell_name")"
line="$(path_line "$shell_name")"

if [ ! -f "$profile" ]; then
warn "Could not configure PATH because $profile does not exist"
summary "Add this line to your shell configuration:" "$line"
elif configure_path "$profile" "$line"; then
step "Added $INSTALL_DIR to PATH in $profile"
summary "Reload your shell (or open a new terminal):" "source $(short_path "$profile")"
else
info "Run '$BINARY' to get started"
summary "Reload your shell (or open a new terminal):" "source $(short_path "$profile")"
fi
}

Expand Down Expand Up @@ -257,12 +357,16 @@ main() {
esac
done

setup_style
banner

require_command uname
require_command tar

RECEIPT="$REQUESTY_HOME/version"
OS="$(detect_os)"
ARCH="$(detect_arch)"
step "Detected $OS/$ARCH"

# A leftover marker file is how a user, or we, can tell that a previous run
# died halfway through.
Expand All @@ -271,28 +375,26 @@ main() {

if [ "$VERSION" = "latest" ]; then
VERSION="$(latest_version)"
step "Resolved latest release: $VERSION"
fi

installed="$(installed_version)"
if [ "$FORCE" = false ] && [ "$installed" = "$VERSION" ] && [ -x "$INSTALL_DIR/$BINARY" ]; then
info "$BINARY $VERSION is already installed in $INSTALL_DIR"
configure_path
rm -f "$MARKER_FILE"
step "$BINARY $VERSION is already installed in $INSTALL_DIR"
finish
exit 0
fi

if [ -n "$installed" ]; then
info "Upgrading $BINARY from $installed to $VERSION"
info "${DIM}Upgrading $BINARY from $installed to $VERSION${RESET}"
else
info "Installing $BINARY $VERSION"
info "${DIM}Installing $BINARY $VERSION${RESET}"
fi

install_release
printf '%s\n' "$VERSION" >"$RECEIPT"

configure_path
rm -f "$MARKER_FILE"
summary
finish
}

main "$@"
Expand Down
Loading