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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ brew install livekit-cli
curl -sSL https://get.livekit.io/cli | bash
```

The script installs `lk` to `/usr/local/bin`. Set `LK_INSTALL_PATH` to install somewhere else.

Or download a precompiled binary for the [latest release](https://github.com/livekit/livekit-cli/releases/latest)

## Windows
Expand Down
11 changes: 7 additions & 4 deletions cmd/lk/banner.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"charm.land/lipgloss/v2"
"github.com/Masterminds/semver/v3"
"github.com/urfave/cli/v3"

livekitcli "github.com/livekit/livekit-cli/v2"
"github.com/livekit/livekit-cli/v2/pkg/util"
Expand Down Expand Up @@ -133,10 +134,12 @@ func bannerMessages(raw []byte, version string) []string {

// showBanner prints the notices before the command runs. It blocks so the
// banner owns the terminal first: prompts and spinners write to the terminal
// directly and cannot be held behind it. Non-interactive runs (scripts, pipes)
// skip the fetch entirely.
func showBanner(ctx context.Context) {
if !out.Interactive() {
// directly and cannot be held behind it. Runs that a script or tool drives
// (stderr or stdin isn't a terminal, or --yes) skip the fetch entirely: the
// install script and coding agents invoke lk that way, and a notice there is
// noise. A person at a terminal sees it on their next command.
func showBanner(ctx context.Context, cmd *cli.Command) {
if !out.Interactive() || SkipPrompts(cmd) {
return
}
printBanner(loadBanner(ctx), out.Err)
Expand Down
2 changes: 1 addition & 1 deletion cmd/lk/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func initLogger(ctx context.Context, cmd *cli.Command) (context.Context, error)
}
util.DetectBackground()

showBanner(ctx)
showBanner(ctx, cmd)

// Nudge users still on API-key auth toward the new account-based flow.
maybeShowUpgradeNotice(cmd, conf)
Expand Down
57 changes: 46 additions & 11 deletions install-cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
# limitations under the License.

# LiveKit CLI installer for Linux. Downloads the latest GitHub release archive
# and installs the binary + completions system-wide.
# and installs the binary + completions system-wide. On macOS, use Homebrew.
#
# Environment:
# LK_INSTALL_PATH where to put lk (default /usr/local/bin)
# LK_INSTALL_MAX_ATTEMPTS download attempts (default 5)
# LK_INSTALL_RETRY_DELAY seconds between attempts (default 2)

set -u
set -o errtrace
Expand All @@ -23,12 +28,14 @@ set -o pipefail

REPO="livekit-cli"
BIN_NAME="lk"
INSTALL_PATH="/usr/local/bin"
INSTALL_PATH="${LK_INSTALL_PATH:-/usr/local/bin}"
INSTALL_PATH="${INSTALL_PATH%/}"
BASH_COMPLETION_PATH="/usr/share/bash-completion/completions"
ZSH_COMPLETION_PATH="/usr/share/zsh/site-functions"
FISH_COMPLETION_PATH="/usr/share/fish/vendor_completions.d"

log() { printf "%b\n" "$*"; }
warn() { printf "%b\n" "$*" >&2; }
abort() { printf "%s\n" "$@" >&2; exit 1; }


Expand All @@ -41,16 +48,14 @@ RETRY_DELAY="${LK_INSTALL_RETRY_DELAY:-2}"
[[ "$RETRY_DELAY" =~ ^[0-9]+$ ]] && [ "$RETRY_DELAY" -ge 1 ] || abort "LK_INSTALL_RETRY_DELAY must be a positive integer (seconds)"

[ -n "${BASH_VERSION:-}" ] || abort "This script requires bash"
[ -d "$INSTALL_PATH" ] || abort "Could not install, $INSTALL_PATH doesn't exist"
command -v curl >/dev/null || abort "cURL is required and is not found"
command -v sha256sum >/dev/null || abort "sha256sum is required and is not found"
command -v jq >/dev/null || abort "jq is required and is not found"

OS="$(uname)"
case "$OS" in
Darwin) abort "Installer not supported on MacOS, please install using Homebrew." ;;
Darwin) abort "Installer not supported on macOS. Install with Homebrew: brew install livekit-cli" ;;
Linux) ;;
*) abort "Installer is only supported on Linux." ;;
*) abort "Installer is only supported on Linux. On Windows, run: winget install LiveKit.LiveKitCLI" ;;
esac

case "$(uname -m)" in
Expand All @@ -59,14 +64,33 @@ case "$(uname -m)" in
*) abort "Unsupported architecture: $(uname -m)" ;;
esac

# Use sudo only for a directory this user can't write to (or create).
SUDO_PREFIX=""
if [ ! -w "$INSTALL_PATH" ]; then
SUDO_PREFIX="sudo"
if [ -d "$INSTALL_PATH" ]; then
[ -w "$INSTALL_PATH" ] || SUDO_PREFIX="sudo"
elif [ -n "${LK_INSTALL_PATH:-}" ]; then
parent="$(dirname "$INSTALL_PATH")"
while [ ! -d "$parent" ]; do parent="$(dirname "$parent")"; done
[ -w "$parent" ] || SUDO_PREFIX="sudo"
else
abort "Could not install, $INSTALL_PATH doesn't exist"
fi
if [ -n "$SUDO_PREFIX" ]; then
log "sudo is required to install to $INSTALL_PATH"
fi

VERSION=$(curl -fsSL --retry "$MAX_ATTEMPTS" --retry-delay "$RETRY_DELAY" https://api.github.com/repos/livekit/$REPO/releases/latest \
| jq -r '.tag_name' | sed 's/^v//')
# This script never makes lk itself a symlink (only livekit-cli), so one here is
# another package manager's, such as Homebrew's. Replacing it would leave that
# manager thinking its copy is still installed.
if [ -L "$INSTALL_PATH/$BIN_NAME" ]; then
abort "$INSTALL_PATH/$BIN_NAME is a symlink, probably Homebrew's. Run \`brew uninstall livekit-cli\` first, or set LK_INSTALL_PATH to install elsewhere."
fi

# The latest release's tag, from where /releases/latest redirects. This needs
# no API token or jq.
LATEST_URL=$(curl -fsSLI -o /dev/null -w '%{url_effective}' --retry "$MAX_ATTEMPTS" --retry-delay "$RETRY_DELAY" \
"https://github.com/livekit/$REPO/releases/latest")
VERSION="${LATEST_URL##*/v}"

[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || abort "Invalid version: $VERSION"

Expand Down Expand Up @@ -96,12 +120,14 @@ actual_sum=$(sha256sum "$TEMP_DIR/$ARCHIVE_NAME" | awk '{print $1}')

tar xzf "$TEMP_DIR/$ARCHIVE_NAME" -C "$TEMP_DIR"

$SUDO_PREFIX mkdir -p "$INSTALL_PATH"
$SUDO_PREFIX mv "$TEMP_DIR/$BIN_NAME" "$INSTALL_PATH/$BIN_NAME"
$SUDO_PREFIX ln -sf "$INSTALL_PATH/$BIN_NAME" "$INSTALL_PATH/livekit-cli"

# Install completions if the corresponding system directories exist. The fish
# completion ships in the archive (no need to invoke lk to regenerate it).
if [ -d "$TEMP_DIR/autocomplete" ]; then
# Skipped for a custom LK_INSTALL_PATH, which is usually a user-level install.
if [ -d "$TEMP_DIR/autocomplete" ] && [ -z "${LK_INSTALL_PATH:-}" ]; then
[ -d "$BASH_COMPLETION_PATH" ] && \
$SUDO_PREFIX install -m 0644 "$TEMP_DIR/autocomplete/bash_autocomplete" "$BASH_COMPLETION_PATH/livekit-cli" || true
[ -d "$ZSH_COMPLETION_PATH" ] && \
Expand All @@ -111,3 +137,12 @@ if [ -d "$TEMP_DIR/autocomplete" ]; then
fi

log "\n$BIN_NAME is installed to $INSTALL_PATH\n"

# Another lk earlier on PATH (often Homebrew's) would shadow this one.
found="$(command -v "$BIN_NAME" 2>/dev/null || true)"
if [ -z "$found" ]; then
warn "Note: $INSTALL_PATH isn't on your PATH. Add it to run \`$BIN_NAME\` by name.\n"
elif [ "$found" != "$INSTALL_PATH/$BIN_NAME" ]; then
warn "Note: $found comes first on your PATH, so \`$BIN_NAME\` runs that copy, not this one."
warn "Remove it (for Homebrew: brew uninstall livekit-cli) or put $INSTALL_PATH earlier on PATH.\n"
fi
Loading