summaryrefslogtreecommitdiff
path: root/extra/install-kimi.sh
blob: d1013ca9c73c8794d46632af1d84065c93215118 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env bash
#
# kimi-code installer for macOS and Linux.
#
# Usage:
#   curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
#   KIMI_VERSION=0.5.0 curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
#   KIMI_INSTALL_DIR=/usr/local curl -fsSL https://code.kimi.com/kimi-code/install.sh | sudo -E bash
#
# Optional env:
#   KIMI_VERSION         Explicit version; if unset, fetch from https://code.kimi.com/kimi-code/latest
#   KIMI_INSTALL_DIR     Installation directory, default $HOME/.kimi-code

set -euo pipefail

KIMI_DOWNLOAD_BASE="https://code.kimi.com/kimi-code"

KIMI_VERSION="${KIMI_VERSION:-}"
KIMI_INSTALL_DIR="${KIMI_INSTALL_DIR:-$HOME/.local/bin}"

# ---------- helpers ----------

_have() { command -v "$1" >/dev/null 2>&1; }

_log() {
  if [ -t 1 ]; then
    printf '\033[1;36m==>\033[0m %s\n' "$*"
  else
    printf '==> %s\n' "$*"
  fi
}

_err() {
  printf 'error: %s\n' "$*" >&2
  exit 1
}

_detect_target() {
  local os arch
  case "$(uname -s)" in
    Darwin) os="darwin" ;;
    Linux)  os="linux"  ;;
    MINGW*|MSYS*|CYGWIN*)
      _err "Windows is not supported by install.sh — use install.ps1 (PowerShell)"
      ;;
    *) _err "unsupported OS: $(uname -s)" ;;
  esac
  case "$(uname -m)" in
    x86_64|amd64) arch="x64" ;;
    arm64|aarch64) arch="arm64" ;;
    *) _err "unsupported architecture: $(uname -m)" ;;
  esac

  # Rosetta 2: when running under an x64 shell on an ARM Mac, switch back to the native arm64 binary
  if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
    if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then
      arch="arm64"
    fi
  fi

  # musl detection (Alpine, etc.) — we currently only ship glibc binaries, so failing early is better than a runtime dlopen error
  if [ "$os" = "linux" ]; then
    if [ -f "/lib/libc.musl-x86_64.so.1" ] || \
       [ -f "/lib/libc.musl-aarch64.so.1" ] || \
       ldd /bin/ls 2>&1 | grep -q musl; then
      _err "Alpine / musl Linux is not currently supported. Use Node.js + 'npm install -g @moonshot-ai/kimi-code' instead."
    fi
  fi

  echo "${os}-${arch}"
}

_download() {
  local url="$1" dest="${2:-}"
  if _have curl; then
    if [ -n "$dest" ]; then
      if [ -t 1 ]; then
        curl --fail --location --progress-bar -o "$dest" "$url"
      else
        curl --fail --location --silent -o "$dest" "$url"
      fi
    else
      curl --fail --location --silent "$url"
    fi
  elif _have wget; then
    if [ -n "$dest" ]; then
      wget -q -O "$dest" "$url"
    else
      wget -q -O - "$url"
    fi
  else
    _err "curl or wget is required"
  fi
}

# Prefer jq; otherwise parse a single manifest field using pure bash regex
_manifest_field() {
  local manifest_json="$1" target="$2" field="$3"
  if _have jq; then
    printf '%s' "$manifest_json" | jq -er ".platforms[\"$target\"].$field // empty"
  else
    local one_line
    one_line="$(printf '%s' "$manifest_json" | tr -d '\n\r\t' | sed 's/ \+/ /g')"
    if [[ $one_line =~ \"$target\"[^}]*\"$field\"[[:space:]]*:[[:space:]]*\"([^\"]+)\" ]]; then
      printf '%s' "${BASH_REMATCH[1]}"
    fi
  fi
}

_sha256_check() {
  local file="$1" expected="$2"
  local actual
  if _have shasum; then
    actual="$(shasum -a 256 "$file" | cut -d' ' -f1)"
  elif _have sha256sum; then
    actual="$(sha256sum "$file" | cut -d' ' -f1)"
  else
    _err "shasum or sha256sum required to verify download"
  fi
  if [ "$actual" != "$expected" ]; then
    _err "checksum mismatch: expected $expected, got $actual"
  fi
}

# ---------- main ----------

TMPDIR_INSTALL=""
_cleanup() {
  if [ -n "$TMPDIR_INSTALL" ] && [ -d "$TMPDIR_INSTALL" ]; then
    rm -rf "$TMPDIR_INSTALL"
  fi
}
trap _cleanup EXIT

main() {
  local target version manifest_url manifest filename checksum binary_url tmpdir

  target="$(_detect_target)"
  _log "Detected target: $target"

  # 1. Resolve version
  if [ -n "$KIMI_VERSION" ]; then
    version="$KIMI_VERSION"
    _log "Using pinned version $version"
  else
    _log "Resolving latest version from ${KIMI_DOWNLOAD_BASE}/latest"
    version="$(_download "${KIMI_DOWNLOAD_BASE}/latest" | tr -d '[:space:]')"
    [ -n "$version" ] || _err "could not resolve latest version"
    _log "Latest version: $version"
  fi

  # 2. Fetch manifest
  manifest_url="${KIMI_DOWNLOAD_BASE}/${version}/manifest.json"
  _log "Fetching manifest ${manifest_url}"
  manifest="$(_download "$manifest_url")"
  [ -n "$manifest" ] || _err "manifest is empty or unreachable: $manifest_url"

  # 3. Find current platform entry
  filename="$(_manifest_field "$manifest" "$target" "filename")"
  checksum="$(_manifest_field "$manifest" "$target" "checksum")"
  [ -n "$filename" ] || _err "platform $target not found in manifest"
  [[ "$checksum" =~ ^[a-f0-9]{64}$ ]] || _err "invalid checksum for $target: $checksum"

  # 4. Download binary
  TMPDIR_INSTALL="$(mktemp -d 2>/dev/null || mktemp -d -t kimi-install)"
  tmpdir="$TMPDIR_INSTALL"
  binary_url="${KIMI_DOWNLOAD_BASE}/${version}/${filename}"
  _log "Downloading ${binary_url}"
  _download "$binary_url" "${tmpdir}/${filename}"

  # 5. Verify checksum
  _log "Verifying checksum"
  _sha256_check "${tmpdir}/${filename}" "$checksum"

  # 6. Install
  chmod +x "${tmpdir}/${filename}"
  mkdir -p "${KIMI_INSTALL_DIR}"
  install -m 0755 "${tmpdir}/${filename}" "${KIMI_INSTALL_DIR}/kimi"
  _log "Installed to ${KIMI_INSTALL_DIR}/kimi"


  _log "Done. Run: kimi --version"
}

main "$@"