返回 CodeWhale
install.sh
根目录 / scripts / release / install.sh
1 #!/usr/bin/env bash
2 set -euo pipefail
3 # CodeWhale Unix installer
4 # Copies codewhale and codew to ~/.local/bin (or $PREFIX/bin)
5
6 PREFIX="${PREFIX:-$HOME/.local}"
7 BIN_DIR="${PREFIX}/bin"
8 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
10 version_code() {
11 local version="$1"
12 local major minor patch
13 IFS=. read -r major minor patch <<< "$version"
14 printf '%d%03d%03d\n' "${major:-0}" "${minor:-0}" "${patch:-0}"
15 }
16
17 detect_host_glibc() {
18 local out
19 if out="$(getconf GNU_LIBC_VERSION 2>/dev/null)"; then
20 printf '%s\n' "$out" | awk '{print $NF; exit}'
21 return 0
22 fi
23 if out="$(ldd --version 2>&1 | head -n 1)"; then
24 printf '%s\n' "$out" | grep -Eo '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n 1
25 return 0
26 fi
27 return 1
28 }
29
30 required_glibc_for_binary() {
31 local bin="$1"
32 local versions
33 versions="$(grep -aoE 'GLIBC_[0-9]+\.[0-9]+(\.[0-9]+)?' "$bin" 2>/dev/null | sed 's/^GLIBC_//' || true)"
34 if [[ -z "$versions" ]]; then
35 return 1
36 fi
37 printf '%s\n' "$versions" | awk -F. '
38 {
39 patch = ($3 == "" ? 0 : $3)
40 code = ($1 * 1000000) + ($2 * 1000) + patch
41 if (code > best) {
42 best = code
43 value = $0
44 }
45 }
46 END {
47 if (value != "") print value
48 }
49 '
50 }
51
52 preflight_glibc() {
53 local bin="$1"
54 if [[ "$(uname -s)" != "Linux" ]]; then
55 return 0
56 fi
57 if [[ "${CODEWHALE_SKIP_GLIBC_CHECK:-}" == "1" || "${DEEPSEEK_TUI_SKIP_GLIBC_CHECK:-}" == "1" || "${DEEPSEEK_SKIP_GLIBC_CHECK:-}" == "1" ]]; then
58 return 0
59 fi
60
61 local required
62 if ! required="$(required_glibc_for_binary "$bin")" || [[ -z "$required" ]]; then
63 return 0
64 fi
65
66 local host
67 if ! host="$(detect_host_glibc)" || [[ -z "$host" ]]; then
68 echo "ERROR: $(basename "$bin") requires GLIBC_$required, but no GNU libc was detected." >&2
69 echo "Build from source instead: cargo install codewhale-cli --locked" >&2
70 echo "Set CODEWHALE_SKIP_GLIBC_CHECK=1 to bypass this check at your own risk." >&2
71 return 1
72 fi
73
74 if [[ "$(version_code "$host")" -lt "$(version_code "$required")" ]]; then
75 echo "ERROR: $(basename "$bin") requires GLIBC_$required, but this system has glibc $host." >&2
76 echo "Ubuntu 22.04 ships glibc 2.35 and cannot run assets built against Ubuntu 24.04/glibc 2.39." >&2
77 echo "Build from source instead: cargo install codewhale-cli --locked" >&2
78 echo "Release follow-up: build Linux GNU assets against an older glibc baseline or add a musl/static asset." >&2
79 echo "Set CODEWHALE_SKIP_GLIBC_CHECK=1 to bypass this check at your own risk." >&2
80 return 1
81 fi
82 }
83
84 # This script installs an already checksum-verified archive. Existing different
85 # binaries go through `codewhale update`, the sole version-aware updater.
86 case "$BIN_DIR" in
87 /*) ;;
88 *) echo "ERROR: PREFIX must be an absolute user path" >&2; exit 1 ;;
89 esac
90 [[ ! -L "$BIN_DIR" ]] || { echo "ERROR: $BIN_DIR is a symlink; choose a fresh PREFIX" >&2; exit 1; }
91 mkdir -p "$BIN_DIR"
92 BIN_DIR="$(cd -P "$BIN_DIR" && pwd)"
93 case "$BIN_DIR/" in
94 /bin/*|/sbin/*|/usr/bin/*|/usr/sbin/*|/nix/store/*|/gnu/store/*|*/node_modules/*|*/Cellar/*|*/.linuxbrew/*|*/linuxbrew/*|*/.cargo/bin/*)
95 echo "ERROR: refusing managed/system directory $BIN_DIR; choose a fresh user PREFIX" >&2
96 exit 1
97 ;;
98 esac
99 [[ -w "$BIN_DIR" ]] || { echo "ERROR: $BIN_DIR is not writable; choose a user PREFIX (no sudo)" >&2; exit 1; }
100
101 check_destination() {
102 local src="$1" dst="$2"
103 destination_exists=0
104 if [[ -e "$dst" || -L "$dst" ]]; then
105 if [[ ! -L "$dst" && -f "$dst" && -x "$dst" ]] && cmp -s "$src" "$dst"; then
106 destination_exists=1
107 return 0
108 fi
109 echo "ERROR: refusing to replace existing $dst; no existing file was changed." >&2
110 echo "For an existing direct Codewhale install, run its full path with 'update'." >&2
111 echo "To migrate, install this verified archive into a fresh user prefix:" >&2
112 echo ' mkdir -p "$HOME/.local"' >&2
113 echo ' codewhale_prefix="$(mktemp -d "$HOME/.local/codewhale-release.XXXXXX")"' >&2
114 echo ' PREFIX="$codewhale_prefix" ./install.sh' >&2
115 echo ' "$codewhale_prefix/bin/codewhale" --version' >&2
116 echo "Put the selected prefix/bin first on PATH after verifying it; see docs/INSTALL.md." >&2
117 return 1
118 fi
119 }
120
121 # Validate both sources and every destination before the first write.
122 for bin in codewhale codew; do
123 src="$SCRIPT_DIR/$bin"
124 [[ -f "$src" ]] || { echo "ERROR: $src not found in archive" >&2; exit 1; }
125 preflight_glibc "$src"
126 check_destination "$src" "$BIN_DIR/$bin"
127 done
128 legacy_tui="$BIN_DIR/codewhale-tui"
129 if [[ -e "$legacy_tui" || -L "$legacy_tui" ]]; then
130 check_destination "$SCRIPT_DIR/codewhale" "$legacy_tui"
131 fi
132
133 stage=""
134 stage_dir=""
135 trap 'if [[ -n "$stage" ]]; then rm -f "$stage"; fi; if [[ -n "$stage_dir" ]]; then rmdir "$stage_dir"; fi' EXIT
136 install_binary() {
137 local src="$1" dst="$2"
138 check_destination "$src" "$dst"
139 if [[ "$destination_exists" == 1 ]]; then
140 echo " $dst (already installed)"
141 return
142 fi
143 stage_dir="$(mktemp -d "$BIN_DIR/.codewhale-install.XXXXXX")"
144 stage="$stage_dir/$(basename "$dst")"
145 cp "$src" "$stage"
146 chmod 0755 "$stage"
147 # Same-directory hard-link publication is atomic and never overwrites a
148 # destination created between preflight and this operation.
149 # The explicit parent operand avoids treating a raced-in destination
150 # directory (or directory symlink) as an alternate publication location.
151 ln "$stage" "$BIN_DIR/"
152 [[ ! -L "$dst" && -f "$dst" ]] && cmp -s "$stage" "$dst" || {
153 echo "ERROR: installed path changed during publication: $dst" >&2
154 return 1
155 }
156 rm -f "$stage"
157 rmdir "$stage_dir"
158 stage=""
159 stage_dir=""
160 echo " $dst"
161 }
162
163 echo "Installing codewhale to $BIN_DIR ..."
164 for bin in codewhale codew; do
165 install_binary "$SCRIPT_DIR/$bin" "$BIN_DIR/$bin"
166 done
167
168 echo ""
169 echo "Done. Commands installed to $BIN_DIR."
170 echo "Future updates: \"$BIN_DIR/codewhale\" update"
171 for bin in codewhale codew; do
172 resolved="$(command -v "$bin" || true)"
173 if [[ "$resolved" != "$BIN_DIR/$bin" ]]; then
174 echo "PATH selects ${resolved:-no $bin command}; this install is $BIN_DIR/$bin"
175 fi
176 done
177 echo "To select this installation in the current shell:"
178 echo " export PATH=\"$BIN_DIR:\$PATH\""
179 echo " hash -r"
180 echo " command -v codewhale codew"
181 echo "Keep the directory first in your shell profile after verifying it."
182 if ! command -v node >/dev/null 2>&1; then
183 echo "Computer Use is included and needs Node.js 20 or newer on PATH."
184 echo "Install Node.js from https://nodejs.org/, then restart Codewhale to enable Computer Use."
185 fi
186
186 lines BASH