#!/bin/sh # Longbridge desktop installer for Linux. # # Usage: # curl -fsSL https://longbridge.com/desktop/install.sh | sh # (the longbridge.com URL redirects to this script's real home on the CDN: # https://assets.lbkrs.com/github/release/longbridge-desktop/install.sh) # curl -fsSL .../install.sh | sh -s -- --channel preview # curl -fsSL .../install.sh | sh -s -- --channel nightly # curl -fsSL .../install.sh | sh -s -- --version 0.19.0 # curl -fsSL .../install.sh | sh -s -- --uninstall # # Environment variables (flags take precedence): # LONGBRIDGE_CHANNEL stable (default) | preview | nightly # LONGBRIDGE_VERSION version to install, default: latest # LONGBRIDGE_BUNDLE_PATH install from a local tar.gz instead of downloading (CI/offline) # # Everything is installed under $HOME (no sudo required): # ~/.local/longbridge{,-preview,-nightly}.app the application # ~/.local/bin/longbridge-desktop{,-preview,-nightly} command symlink # ~/.local/share/applications/*.desktop launcher entry set -eu CDN="https://assets.lbkrs.com/github/release/longbridge-desktop" # Minimum glibc of the published binaries; keep in sync with the CI build image. MIN_GLIBC_MAJOR=2 MIN_GLIBC_MINOR=35 # --- output helpers --------------------------------------------------------- # Colors only on a TTY, honoring NO_COLOR; CI logs degrade to plain text. if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-}" != "dumb" ]; then c_bold="$(printf '\033[1m')" c_dim="$(printf '\033[2m')" c_red="$(printf '\033[31m')" c_green="$(printf '\033[32m')" c_yellow="$(printf '\033[33m')" c_cyan="$(printf '\033[36m')" c_reset="$(printf '\033[0m')" else c_bold="" c_dim="" c_red="" c_green="" c_yellow="" c_cyan="" c_reset="" fi step() { printf '%s==>%s %s%s%s\n' "$c_cyan" "$c_reset" "$c_bold" "$1" "$c_reset"; } ok() { printf ' %s✓%s %s\n' "$c_green" "$c_reset" "$1"; } note() { printf ' %s·%s %s%s%s\n' "$c_dim" "$c_reset" "$c_dim" "$1" "$c_reset"; } warn() { printf ' %s!%s %s\n' "$c_yellow" "$c_reset" "$1" >&2; } # die "message" ["extra line" ...] — first line gets the ✗, the rest indent under it die() { printf ' %s✗%s %s\n' "$c_red" "$c_reset" "$1" >&2 shift for _line in "$@"; do printf ' %s\n' "$_line" >&2 done exit 1 } # ~-relative display for paths under $HOME home_rel() { printf '~%s' "${1#"$HOME"}"; } channel="${LONGBRIDGE_CHANNEL:-stable}" version="${LONGBRIDGE_VERSION:-latest}" bundle_path="${LONGBRIDGE_BUNDLE_PATH:-}" uninstall=0 while [ $# -gt 0 ]; do case "$1" in --channel) channel="${2:?missing value for --channel}"; shift 2 ;; --version) version="${2:?missing value for --version}"; shift 2 ;; --uninstall) uninstall=1; shift ;; *) die "Unknown option: $1" ;; esac done case "$channel" in stable | preview | nightly) ;; *) die "Invalid channel '$channel' (expected stable, preview or nightly)" ;; esac suffix="" if [ "$channel" != "stable" ]; then suffix="-$channel" fi app_dir="$HOME/.local/longbridge$suffix.app" bin_link="$HOME/.local/bin/longbridge-desktop$suffix" desktop_dir="$HOME/.local/share/applications" desktop_file="$desktop_dir/longbridge-desktop$suffix.desktop" if [ "$uninstall" = 1 ]; then step "Uninstalling Longbridge ($channel)" if [ -d "$app_dir" ]; then rm -rf "$app_dir" ok "Removed $(home_rel "$app_dir")" note "User data and settings are kept; remove them manually if no longer needed." else warn "Longbridge ($channel) is not installed ($(home_rel "$app_dir") does not exist)." fi rm -f "$bin_link" "$desktop_file" # 卸载按渠道定向;列出其它渠道的安装,避免「删了个寂寞还以为卸干净了」 for other in "$HOME"/.local/longbridge*.app; do [ -d "$other" ] || continue other_channel=$(basename "$other" .app) other_channel=${other_channel#longbridge} other_channel=${other_channel#-} if [ -z "$other_channel" ]; then other_channel=stable fi warn "Found another install: $(home_rel "$other"), remove it with '--uninstall --channel $other_channel'" done exit 0 fi printf '%s\n' "${c_bold}Longbridge Desktop Installer${c_reset}" step "Checking system" platform="$(uname -s)" arch="$(uname -m)" if [ "$platform" != "Linux" ]; then die "This installer only supports Linux." \ "Download the macOS or Windows app from https://longbridge.com/desktop" fi case "$arch" in x86_64) target="linux-x86_64" ;; *) die "Unsupported architecture: $arch (only x86_64 packages are published)" ;; esac if command -v curl > /dev/null 2>&1; then fetch() { command curl -fsSL "$@"; } # The tarball is large; keep a progress bar for it. Progress goes to # stderr, so stdout redirected into a file stays clean. download() { command curl -fL --progress-bar "$@"; } elif command -v wget > /dev/null 2>&1; then fetch() { wget -qO- "$@"; } download() { wget -O- "$@"; } else die "Could not find 'curl' or 'wget' in your PATH" fi glibc_version="" check_glibc() { command -v ldd > /dev/null 2>&1 || return 0 ldd_banner="$(ldd --version 2>&1 | head -n 1 || true)" case "$ldd_banner" in *musl*) die "Longbridge requires glibc; musl-based systems are not supported." ;; esac glibc_version="$(printf '%s\n' "$ldd_banner" | grep -o '[0-9][0-9]*\.[0-9][0-9]*$' || true)" [ -n "$glibc_version" ] || return 0 major="${glibc_version%%.*}" minor="${glibc_version#*.}" if [ "$major" -lt "$MIN_GLIBC_MAJOR" ] || { [ "$major" -eq "$MIN_GLIBC_MAJOR" ] && [ "$minor" -lt "$MIN_GLIBC_MINOR" ]; }; then die "Longbridge requires glibc >= $MIN_GLIBC_MAJOR.$MIN_GLIBC_MINOR, but this system has $glibc_version." fi } check_glibc if [ -n "$glibc_version" ]; then ok "$target · glibc $glibc_version" else ok "$target" fi temp="$(mktemp -d "${TMPDIR:-/tmp}/longbridge-install-XXXXXX")" trap 'rm -rf "$temp"' EXIT if [ -n "$bundle_path" ]; then step "Using local bundle" tarball="$bundle_path" version="(local bundle)" ok "$bundle_path" else if [ "$version" = "latest" ]; then step "Resolving latest $channel release" manifest_url="$CDN/$channel/latest.json" else step "Resolving $channel release v${version#v}" manifest_url="$CDN/$channel/v${version#v}.json" fi manifest="$temp/manifest.json" if ! fetch "$manifest_url" > "$manifest"; then die "Failed to fetch $manifest_url" \ "(is there a $channel release for this version?)" fi version="$(sed -n 's/.*"version":"\([^"]*\)".*/\1/p' "$manifest" | head -n 1)" if [ -z "$version" ]; then die "Could not parse the release manifest at $manifest_url" fi asset_name="longbridge-v$version-$target.tar.gz" if ! grep -q "\"name\":\"$asset_name\"" "$manifest"; then if [ "$channel" = "nightly" ]; then die "Release v$version on channel '$channel' has no Linux tar.gz package." \ "Nightly builds are currently not published for Linux; try '--channel preview'." fi die "Release v$version on channel '$channel' has no Linux tar.gz package." fi ok "Version $version" # sha256 is published in the manifest for releases newer than this script sha256="$(grep -o "\"name\":\"$asset_name\"[^}]*" "$manifest" | sed -n 's/.*"sha256":"\([a-f0-9]\{64\}\)".*/\1/p' | head -n 1 || true)" tarball="$temp/$asset_name" step "Downloading $asset_name" download "$CDN/$channel/$asset_name" > "$tarball" if [ -n "$sha256" ] && command -v sha256sum > /dev/null 2>&1; then if ! printf '%s %s\n' "$sha256" "$tarball" | sha256sum -c - > /dev/null 2>&1; then die "Checksum mismatch for $asset_name, aborting." fi ok "Checksum verified" else note "Checksum not available for this release; skipping verification" fi fi step "Installing to $(home_rel "$app_dir")" staging="$temp/extracted" mkdir -p "$staging" tar -xzf "$tarball" -C "$staging" src_app="$(find "$staging" -mindepth 1 -maxdepth 1 -type d -name '*.app' | head -n 1)" if [ -z "$src_app" ]; then die "Unexpected package layout in the downloaded tar.gz" fi rm -rf "$app_dir" mkdir -p "$HOME/.local" "$HOME/.local/bin" "$desktop_dir" mv "$src_app" "$app_dir" ok "Application installed" cp "$app_dir/share/applications/longbridge-desktop$suffix.desktop" "$desktop_file" sed -i "s|Exec=longbridge|Exec=$app_dir/bin/longbridge|g" "$desktop_file" sed -i "s|Icon=longbridge|Icon=$app_dir/share/icons/hicolor/512x512/apps/longbridge.png|g" "$desktop_file" if command -v update-desktop-database > /dev/null 2>&1; then update-desktop-database -q "$desktop_dir" || true fi ok "Desktop entry added to your app launcher" ln -sf "$app_dir/bin/longbridge" "$bin_link" ok "Command installed: longbridge-desktop$suffix" printf '\n%s\n\n' "${c_green}${c_bold}Longbridge $version ($channel) installed successfully!${c_reset}" printf ' %sLaunch:%s from your app launcher, or run %s\n' "$c_bold" "$c_reset" "'longbridge-desktop$suffix'" printf ' %sUpdate:%s re-run this installer\n' "$c_bold" "$c_reset" printf ' %sUninstall:%s re-run with --uninstall%s\n' "$c_bold" "$c_reset" "$([ -n "$suffix" ] && printf ' %s' "--channel $channel")" case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) printf '\n' warn "$(home_rel "$HOME/.local/bin") is not on your PATH; the 'longbridge-desktop$suffix' command won't work yet." case "${SHELL:-}" in *zsh) note "Add it with: echo 'export PATH=\$HOME/.local/bin:\$PATH' >> ~/.zshrc" ;; *fish) note "Add it with: fish_add_path -U \$HOME/.local/bin" ;; *) note "Add it with: echo 'export PATH=\$HOME/.local/bin:\$PATH' >> ~/.bashrc" ;; esac ;; esac