freecode-sh 0.1.0-alpha.42 → 0.1.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -34
- package/check-platform.mjs +2 -20
- package/freecode.mjs +102 -182
- package/install-lock.mjs +153 -0
- package/package.json +19 -30
- package/release-trust.mjs +24 -0
- package/release-version.cjs +17 -0
- package/release.mjs +64 -0
- package/terminal-release.json +33 -0
- package/terminal-release.json.sig +0 -0
- package/install.ps1 +0 -683
- package/install.sh +0 -687
- package/npm-bootstrap-environment.mjs +0 -7
package/install.sh
DELETED
|
@@ -1,687 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
|
|
3
|
-
# Free Code native installer for macOS.
|
|
4
|
-
# Review this script before running it: https://freecode.sh/install
|
|
5
|
-
|
|
6
|
-
set -eu
|
|
7
|
-
|
|
8
|
-
DEFAULT_CHANNEL="alpha"
|
|
9
|
-
DEFAULT_DOWNLOAD_BASE_URL="https://releases.freecode.sh/releases"
|
|
10
|
-
DEFAULT_CHANNEL_BASE_URL="https://releases.freecode.sh/channels"
|
|
11
|
-
EXPECTED_TEAM_IDENTIFIER="GD597P8KJ2"
|
|
12
|
-
# Apple code requirement language. "anchor apple generic" forces the signing
|
|
13
|
-
# chain to terminate at Apple's root certificate, so only a real Developer ID
|
|
14
|
-
# certificate issued to this team can satisfy it. Text printed by
|
|
15
|
-
# `codesign --display` cannot be trusted for this: Authority and TeamIdentifier
|
|
16
|
-
# lines are read straight out of the certificate, so a self-signed certificate
|
|
17
|
-
# can present any subject and OU it likes.
|
|
18
|
-
RELEASE_CODE_REQUIREMENT="=anchor apple generic and certificate leaf[subject.OU] = \"$EXPECTED_TEAM_IDENTIFIER\""
|
|
19
|
-
CODESIGN_BIN="/usr/bin/codesign"
|
|
20
|
-
LEGACY_ALPHA6_MANIFEST_SHA256="1bf63e0d6114b12d61c07094936bd2606ae82ce87d5b054fbfb01c5f3d3646d8"
|
|
21
|
-
LAUNCHER_RELATIVE_PATH="bin/freecode"
|
|
22
|
-
INTERFACE_RELATIVE_PATH="libexec/freecode/freecode-tui"
|
|
23
|
-
ENGINE_RELATIVE_PATH="libexec/freecode/freecode-engine"
|
|
24
|
-
|
|
25
|
-
VERSION="${FREECODE_INSTALL_VERSION:-}"
|
|
26
|
-
CHANNEL="${FREECODE_INSTALL_CHANNEL:-$DEFAULT_CHANNEL}"
|
|
27
|
-
DOWNLOAD_BASE_URL="${FREECODE_INSTALL_BASE_URL:-$DEFAULT_DOWNLOAD_BASE_URL}"
|
|
28
|
-
CHANNEL_BASE_URL="${FREECODE_CHANNEL_BASE_URL:-$DEFAULT_CHANNEL_BASE_URL}"
|
|
29
|
-
INSTALL_ROOT="${FREECODE_INSTALL_ROOT:-${HOME:?HOME is required}/.local/share/freecode}"
|
|
30
|
-
BIN_DIR="${FREECODE_BIN_DIR:-${HOME}/.local/bin}"
|
|
31
|
-
OS_NAME="${FREECODE_INSTALL_OS:-$(uname -s)}"
|
|
32
|
-
MACHINE="${FREECODE_INSTALL_ARCH:-$(uname -m)}"
|
|
33
|
-
TEMP_DIR=""
|
|
34
|
-
STAGE_DIR=""
|
|
35
|
-
INSTALL_LOCK_PATH=""
|
|
36
|
-
INSTALL_LOCK_HELD="false"
|
|
37
|
-
INSTALL_LOCK_OWNER_TOKEN=""
|
|
38
|
-
INSTALL_LOCK_GUARD_HELD="false"
|
|
39
|
-
ALLOW_UNSIGNED_INSTALL="${FREECODE_ALLOW_UNSIGNED_INSTALL:-false}"
|
|
40
|
-
EMBEDDED_INSTALL="${FREECODE_INSTALL_EMBEDDED:-false}"
|
|
41
|
-
LAUNCH_AFTER_INSTALL="${FREECODE_INSTALL_LAUNCH:-true}"
|
|
42
|
-
RELEASE_MODE=""
|
|
43
|
-
PATH_PROFILE=""
|
|
44
|
-
PATH_CONFIGURED="false"
|
|
45
|
-
|
|
46
|
-
usage() {
|
|
47
|
-
cat <<'EOF'
|
|
48
|
-
Install Free Code's native macOS release.
|
|
49
|
-
|
|
50
|
-
Usage:
|
|
51
|
-
curl -fsSL https://freecode.sh/install.sh | sh
|
|
52
|
-
curl -fsSL https://freecode.sh/install.sh | sh -s -- --version 0.1.0-alpha.1
|
|
53
|
-
|
|
54
|
-
Options:
|
|
55
|
-
--version VERSION Install an exact immutable version.
|
|
56
|
-
--channel CHANNEL Resolve a channel (default: alpha).
|
|
57
|
-
--base-url URL Override the immutable release download base URL.
|
|
58
|
-
--channel-base-url URL Override the channel manifest base URL.
|
|
59
|
-
--install-root PATH Version store (default: ~/.local/share/freecode).
|
|
60
|
-
--bin-dir PATH Executable link directory (default: ~/.local/bin).
|
|
61
|
-
--no-launch Install without opening Free Code.
|
|
62
|
-
-h, --help Show this help.
|
|
63
|
-
|
|
64
|
-
Environment equivalents:
|
|
65
|
-
FREECODE_INSTALL_VERSION, FREECODE_INSTALL_CHANNEL,
|
|
66
|
-
FREECODE_INSTALL_BASE_URL, FREECODE_CHANNEL_BASE_URL,
|
|
67
|
-
FREECODE_INSTALL_ROOT, FREECODE_BIN_DIR, FREECODE_INSTALL_LAUNCH
|
|
68
|
-
|
|
69
|
-
Internal testing only:
|
|
70
|
-
FREECODE_ALLOW_UNSIGNED_INSTALL=true permits an explicitly marked unsigned
|
|
71
|
-
release outside the alpha channel. Never use this for a normal install.
|
|
72
|
-
EOF
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
fail() {
|
|
76
|
-
printf 'freecode installer: %s\n' "$*" >&2
|
|
77
|
-
exit 1
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
cleanup() {
|
|
81
|
-
if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then
|
|
82
|
-
rm -rf "$TEMP_DIR"
|
|
83
|
-
fi
|
|
84
|
-
if [ -n "$STAGE_DIR" ] && [ -d "$STAGE_DIR" ]; then
|
|
85
|
-
rm -rf "$STAGE_DIR"
|
|
86
|
-
fi
|
|
87
|
-
release_install_lock
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
require_value() {
|
|
91
|
-
[ "$#" -ge 2 ] || fail "$1 requires a value"
|
|
92
|
-
[ -n "$2" ] || fail "$1 requires a non-empty value"
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
release_install_lock() {
|
|
96
|
-
if [ "$INSTALL_LOCK_HELD" = "true" ]; then
|
|
97
|
-
lock_owner=$(sed -n '1p' "$INSTALL_LOCK_PATH" 2>/dev/null || true)
|
|
98
|
-
lock_owner_token=$(sed -n '2p' "$INSTALL_LOCK_PATH" 2>/dev/null || true)
|
|
99
|
-
if [ "$lock_owner" = "$$" ] && [ "$lock_owner_token" = "$INSTALL_LOCK_OWNER_TOKEN" ]; then
|
|
100
|
-
rm -f "$INSTALL_LOCK_PATH"
|
|
101
|
-
fi
|
|
102
|
-
fi
|
|
103
|
-
INSTALL_LOCK_HELD="false"
|
|
104
|
-
if [ "$INSTALL_LOCK_GUARD_HELD" = "true" ]; then
|
|
105
|
-
exec 9>&-
|
|
106
|
-
INSTALL_LOCK_GUARD_HELD="false"
|
|
107
|
-
fi
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
create_install_lock() {
|
|
111
|
-
(
|
|
112
|
-
umask 077
|
|
113
|
-
set -C
|
|
114
|
-
printf '%s\n%s\n' "$$" "$INSTALL_LOCK_OWNER_TOKEN" > "$INSTALL_LOCK_PATH"
|
|
115
|
-
) 2>/dev/null
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
process_start_token() {
|
|
119
|
-
LC_ALL=C ps -o lstart= -p "$1" 2>/dev/null \
|
|
120
|
-
| sed -n '1{s/^[[:space:]]*//;s/[[:space:]]*$//;p;}'
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
acquire_install_lock() {
|
|
124
|
-
INSTALL_LOCK_PATH="$INSTALL_ROOT/.update-lock"
|
|
125
|
-
INSTALL_LOCK_OWNER_TOKEN=$(process_start_token "$$")
|
|
126
|
-
[ -n "$INSTALL_LOCK_OWNER_TOKEN" ] \
|
|
127
|
-
|| fail "could not identify the installer process for update locking"
|
|
128
|
-
mkdir -p "$INSTALL_ROOT"
|
|
129
|
-
exec 9>"$INSTALL_LOCK_PATH.guard"
|
|
130
|
-
# Descriptor-mode locks remain attached to fd 9 after the helper exits and
|
|
131
|
-
# are held until release_install_lock closes the descriptor.
|
|
132
|
-
if [ -x /usr/bin/lockf ]; then
|
|
133
|
-
guard_acquired=false
|
|
134
|
-
/usr/bin/lockf -s -t 120 9 && guard_acquired=true
|
|
135
|
-
elif [ "$(uname -s)" = "Linux" ] && command -v flock >/dev/null 2>&1; then
|
|
136
|
-
# The Linux CI host emulates Darwin to exercise this macOS-only installer.
|
|
137
|
-
guard_acquired=false
|
|
138
|
-
flock -w 120 9 && guard_acquired=true
|
|
139
|
-
else
|
|
140
|
-
exec 9>&-
|
|
141
|
-
fail "the system lock utility is unavailable"
|
|
142
|
-
fi
|
|
143
|
-
if [ "$guard_acquired" != "true" ]; then
|
|
144
|
-
exec 9>&-
|
|
145
|
-
fail "another Free Code install or update is still in progress"
|
|
146
|
-
fi
|
|
147
|
-
INSTALL_LOCK_GUARD_HELD="true"
|
|
148
|
-
attempts=0
|
|
149
|
-
while ! create_install_lock; do
|
|
150
|
-
# Read both the regular-file protocol and the short-lived directory format
|
|
151
|
-
# used by development builds before the lock contract was unified.
|
|
152
|
-
if [ -d "$INSTALL_LOCK_PATH" ]; then
|
|
153
|
-
lock_owner=$(sed -n '1p' "$INSTALL_LOCK_PATH/pid" 2>/dev/null || true)
|
|
154
|
-
lock_owner_token=""
|
|
155
|
-
else
|
|
156
|
-
lock_owner=$(sed -n '1p' "$INSTALL_LOCK_PATH" 2>/dev/null || true)
|
|
157
|
-
lock_owner_token=$(sed -n '2p' "$INSTALL_LOCK_PATH" 2>/dev/null || true)
|
|
158
|
-
fi
|
|
159
|
-
case "$lock_owner" in
|
|
160
|
-
''|*[!0-9]*) ;;
|
|
161
|
-
*)
|
|
162
|
-
if ! kill -0 "$lock_owner" 2>/dev/null; then
|
|
163
|
-
rm -rf "$INSTALL_LOCK_PATH"
|
|
164
|
-
continue
|
|
165
|
-
fi
|
|
166
|
-
if [ -n "$lock_owner_token" ]; then
|
|
167
|
-
current_owner_token=$(process_start_token "$lock_owner")
|
|
168
|
-
if [ -n "$current_owner_token" ] && [ "$current_owner_token" != "$lock_owner_token" ]; then
|
|
169
|
-
rm -rf "$INSTALL_LOCK_PATH"
|
|
170
|
-
continue
|
|
171
|
-
fi
|
|
172
|
-
fi
|
|
173
|
-
;;
|
|
174
|
-
esac
|
|
175
|
-
attempts=$((attempts + 1))
|
|
176
|
-
[ "$attempts" -lt 120 ] \
|
|
177
|
-
|| fail "another Free Code install or update is still in progress"
|
|
178
|
-
sleep 1
|
|
179
|
-
done
|
|
180
|
-
INSTALL_LOCK_HELD="true"
|
|
181
|
-
|
|
182
|
-
if [ -n "${FREECODE_TEST_HOLD_INSTALL_LOCK_SECONDS:-}" ]; then
|
|
183
|
-
case "$DOWNLOAD_BASE_URL" in
|
|
184
|
-
file://*) sleep "$FREECODE_TEST_HOLD_INSTALL_LOCK_SECONDS" ;;
|
|
185
|
-
*) fail "the test install delay is permitted only with local file URLs" ;;
|
|
186
|
-
esac
|
|
187
|
-
fi
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
configure_profile() {
|
|
191
|
-
profile=$1
|
|
192
|
-
path_line=$2
|
|
193
|
-
legacy_path_line=${3:-}
|
|
194
|
-
profile_dir=$(dirname "$profile")
|
|
195
|
-
|
|
196
|
-
[ -d "$profile_dir" ] || return 1
|
|
197
|
-
if [ -e "$profile" ] || [ -L "$profile" ]; then
|
|
198
|
-
[ -f "$profile" ] || return 1
|
|
199
|
-
fi
|
|
200
|
-
if grep -Fqx "$path_line" "$profile" 2>/dev/null \
|
|
201
|
-
|| { [ -n "$legacy_path_line" ] && grep -Fqx "$legacy_path_line" "$profile" 2>/dev/null; }
|
|
202
|
-
then
|
|
203
|
-
return 0
|
|
204
|
-
fi
|
|
205
|
-
if [ -s "$profile" ] && ! printf '\n' >> "$profile"; then
|
|
206
|
-
return 1
|
|
207
|
-
fi
|
|
208
|
-
printf '# Free Code\n%s\n' "$path_line" >> "$profile"
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
configure_path() {
|
|
212
|
-
[ "$BIN_DIR" = "$HOME/.local/bin" ] || return 0
|
|
213
|
-
|
|
214
|
-
guarded_path_line='case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) export PATH="$HOME/.local/bin:$PATH" ;; esac'
|
|
215
|
-
legacy_path_line='export PATH="$HOME/.local/bin:$PATH"'
|
|
216
|
-
detected_shell=${SHELL:-/bin/zsh}
|
|
217
|
-
|
|
218
|
-
case "$detected_shell" in
|
|
219
|
-
*/zsh)
|
|
220
|
-
zsh_config_dir=${ZDOTDIR:-$HOME}
|
|
221
|
-
if configure_profile "$zsh_config_dir/.zprofile" "$guarded_path_line" "$legacy_path_line" \
|
|
222
|
-
&& configure_profile "$zsh_config_dir/.zshrc" "$guarded_path_line" "$legacy_path_line"
|
|
223
|
-
then
|
|
224
|
-
PATH_PROFILE="$zsh_config_dir/.zprofile and $zsh_config_dir/.zshrc"
|
|
225
|
-
PATH_CONFIGURED="true"
|
|
226
|
-
fi
|
|
227
|
-
;;
|
|
228
|
-
*/bash)
|
|
229
|
-
if [ -e "$HOME/.bash_profile" ] || [ -L "$HOME/.bash_profile" ]; then
|
|
230
|
-
bash_login_profile="$HOME/.bash_profile"
|
|
231
|
-
elif [ -e "$HOME/.bash_login" ] || [ -L "$HOME/.bash_login" ]; then
|
|
232
|
-
bash_login_profile="$HOME/.bash_login"
|
|
233
|
-
elif [ -e "$HOME/.profile" ] || [ -L "$HOME/.profile" ]; then
|
|
234
|
-
bash_login_profile="$HOME/.profile"
|
|
235
|
-
else
|
|
236
|
-
bash_login_profile="$HOME/.bash_profile"
|
|
237
|
-
fi
|
|
238
|
-
if configure_profile "$bash_login_profile" "$guarded_path_line" "$legacy_path_line" \
|
|
239
|
-
&& configure_profile "$HOME/.bashrc" "$guarded_path_line" "$legacy_path_line"
|
|
240
|
-
then
|
|
241
|
-
PATH_PROFILE="$bash_login_profile and $HOME/.bashrc"
|
|
242
|
-
PATH_CONFIGURED="true"
|
|
243
|
-
fi
|
|
244
|
-
;;
|
|
245
|
-
*/fish)
|
|
246
|
-
fish_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d"
|
|
247
|
-
if mkdir -p "$fish_config_dir" 2>/dev/null \
|
|
248
|
-
&& configure_profile "$fish_config_dir/freecode.fish" \
|
|
249
|
-
'contains -- "$HOME/.local/bin" $PATH; or set -gx PATH "$HOME/.local/bin" $PATH'
|
|
250
|
-
then
|
|
251
|
-
PATH_PROFILE="$fish_config_dir/freecode.fish"
|
|
252
|
-
PATH_CONFIGURED="true"
|
|
253
|
-
fi
|
|
254
|
-
;;
|
|
255
|
-
esac
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
trap cleanup EXIT HUP INT TERM
|
|
259
|
-
|
|
260
|
-
while [ "$#" -gt 0 ]; do
|
|
261
|
-
case "$1" in
|
|
262
|
-
--version)
|
|
263
|
-
require_value "$@"
|
|
264
|
-
VERSION=$2
|
|
265
|
-
shift 2
|
|
266
|
-
;;
|
|
267
|
-
--channel)
|
|
268
|
-
require_value "$@"
|
|
269
|
-
CHANNEL=$2
|
|
270
|
-
shift 2
|
|
271
|
-
;;
|
|
272
|
-
--base-url)
|
|
273
|
-
require_value "$@"
|
|
274
|
-
DOWNLOAD_BASE_URL=$2
|
|
275
|
-
shift 2
|
|
276
|
-
;;
|
|
277
|
-
--channel-base-url)
|
|
278
|
-
require_value "$@"
|
|
279
|
-
CHANNEL_BASE_URL=$2
|
|
280
|
-
shift 2
|
|
281
|
-
;;
|
|
282
|
-
--install-root)
|
|
283
|
-
require_value "$@"
|
|
284
|
-
INSTALL_ROOT=$2
|
|
285
|
-
shift 2
|
|
286
|
-
;;
|
|
287
|
-
--bin-dir)
|
|
288
|
-
require_value "$@"
|
|
289
|
-
BIN_DIR=$2
|
|
290
|
-
shift 2
|
|
291
|
-
;;
|
|
292
|
-
--no-launch)
|
|
293
|
-
LAUNCH_AFTER_INSTALL="false"
|
|
294
|
-
shift
|
|
295
|
-
;;
|
|
296
|
-
-h|--help)
|
|
297
|
-
usage
|
|
298
|
-
exit 0
|
|
299
|
-
;;
|
|
300
|
-
--)
|
|
301
|
-
shift
|
|
302
|
-
break
|
|
303
|
-
;;
|
|
304
|
-
*)
|
|
305
|
-
fail "unknown option: $1"
|
|
306
|
-
;;
|
|
307
|
-
esac
|
|
308
|
-
done
|
|
309
|
-
|
|
310
|
-
[ "$#" -eq 0 ] || fail "unexpected positional arguments: $*"
|
|
311
|
-
command -v curl >/dev/null 2>&1 || fail "curl is required"
|
|
312
|
-
command -v tar >/dev/null 2>&1 || fail "tar is required"
|
|
313
|
-
command -v shasum >/dev/null 2>&1 || fail "shasum is required"
|
|
314
|
-
|
|
315
|
-
case "$ALLOW_UNSIGNED_INSTALL" in
|
|
316
|
-
true|false) ;;
|
|
317
|
-
*) fail "FREECODE_ALLOW_UNSIGNED_INSTALL must be exactly true or false" ;;
|
|
318
|
-
esac
|
|
319
|
-
|
|
320
|
-
case "$EMBEDDED_INSTALL" in
|
|
321
|
-
true|false) ;;
|
|
322
|
-
*) fail "FREECODE_INSTALL_EMBEDDED must be exactly true or false" ;;
|
|
323
|
-
esac
|
|
324
|
-
|
|
325
|
-
case "$LAUNCH_AFTER_INSTALL" in
|
|
326
|
-
true|false) ;;
|
|
327
|
-
*) fail "FREECODE_INSTALL_LAUNCH must be exactly true or false" ;;
|
|
328
|
-
esac
|
|
329
|
-
|
|
330
|
-
case "$OS_NAME" in
|
|
331
|
-
Darwin) ;;
|
|
332
|
-
*) fail "only macOS is supported right now (detected $OS_NAME)" ;;
|
|
333
|
-
esac
|
|
334
|
-
|
|
335
|
-
case "$MACHINE" in
|
|
336
|
-
arm64|aarch64)
|
|
337
|
-
ARCH="arm64"
|
|
338
|
-
;;
|
|
339
|
-
x86_64|amd64)
|
|
340
|
-
# Prefer the native Apple Silicon build when this shell is running under Rosetta.
|
|
341
|
-
if [ "${FREECODE_INSTALL_ARCH:-}" = "" ] && [ "$(sysctl -in sysctl.proc_translated 2>/dev/null || true)" = "1" ]; then
|
|
342
|
-
ARCH="arm64"
|
|
343
|
-
else
|
|
344
|
-
ARCH="x64"
|
|
345
|
-
fi
|
|
346
|
-
;;
|
|
347
|
-
*)
|
|
348
|
-
fail "unsupported Mac architecture: $MACHINE"
|
|
349
|
-
;;
|
|
350
|
-
esac
|
|
351
|
-
|
|
352
|
-
validate_version() {
|
|
353
|
-
printf '%s\n' "$1" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$' \
|
|
354
|
-
|| fail "invalid release version: $1"
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
validate_channel() {
|
|
358
|
-
printf '%s\n' "$1" | grep -Eq '^[0-9A-Za-z][0-9A-Za-z._-]*$' \
|
|
359
|
-
|| fail "invalid release channel: $1"
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
validate_release_security() {
|
|
363
|
-
manifest_path=$1
|
|
364
|
-
context=$2
|
|
365
|
-
release_mode=$(sed -n 's/.*"releaseMode"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest_path" | head -n 1)
|
|
366
|
-
manifest_channel=$(sed -n 's/.*"channel"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest_path" | head -n 1)
|
|
367
|
-
case "$release_mode" in
|
|
368
|
-
signed-notarized)
|
|
369
|
-
RELEASE_MODE="signed-notarized"
|
|
370
|
-
;;
|
|
371
|
-
unsigned-override)
|
|
372
|
-
if [ "$CHANNEL" = "alpha" ] && [ "$manifest_channel" = "alpha" ]; then
|
|
373
|
-
printf 'WARNING: %s is an unsigned Free Code alpha and is not Apple notarized. Its SHA-256 checksum will still be verified.\n' "$context" >&2
|
|
374
|
-
elif [ "$ALLOW_UNSIGNED_INSTALL" = "true" ]; then
|
|
375
|
-
printf 'WARNING: %s declares unsigned-override security metadata outside the alpha channel.\n' "$context" >&2
|
|
376
|
-
else
|
|
377
|
-
fail "$context is unsigned and is not an alpha release; refusing to install it"
|
|
378
|
-
fi
|
|
379
|
-
RELEASE_MODE="unsigned-override"
|
|
380
|
-
;;
|
|
381
|
-
*) fail "$context does not contain valid signed-notarized release security metadata" ;;
|
|
382
|
-
esac
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
# Test-only seam. Signature verification runs /usr/bin/codesign by absolute path
|
|
386
|
-
# so nothing on PATH can impersonate it; the installer test suite substitutes a
|
|
387
|
-
# stub, and like every other FREECODE_TEST_* override it is refused unless the
|
|
388
|
-
# release is being read from a local file:// URL.
|
|
389
|
-
resolve_codesign_bin() {
|
|
390
|
-
[ -n "${FREECODE_TEST_CODESIGN_BIN:-}" ] || return 0
|
|
391
|
-
case "$DOWNLOAD_BASE_URL" in
|
|
392
|
-
file://*) CODESIGN_BIN="$FREECODE_TEST_CODESIGN_BIN" ;;
|
|
393
|
-
*) fail "the test codesign override is permitted only with local file URLs" ;;
|
|
394
|
-
esac
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
verify_release_signatures() {
|
|
398
|
-
root=$1
|
|
399
|
-
if [ "$RELEASE_MODE" = "unsigned-override" ]; then
|
|
400
|
-
printf 'WARNING: skipping Apple signature and Team ID verification for this explicitly marked unsigned release.\n' >&2
|
|
401
|
-
return
|
|
402
|
-
fi
|
|
403
|
-
|
|
404
|
-
[ -x "$CODESIGN_BIN" ] || fail "$CODESIGN_BIN is required to verify this release"
|
|
405
|
-
for executable in \
|
|
406
|
-
"$root/$LAUNCHER_RELATIVE_PATH" \
|
|
407
|
-
"$root/$INTERFACE_RELATIVE_PATH" \
|
|
408
|
-
"$root/$ENGINE_RELATIVE_PATH"
|
|
409
|
-
do
|
|
410
|
-
# Without -R this only proves the seal is intact, not who signed it.
|
|
411
|
-
"$CODESIGN_BIN" --verify --strict --verbose=2 -R "$RELEASE_CODE_REQUIREMENT" "$executable" \
|
|
412
|
-
|| fail "Apple signature verification failed for $executable; it must be Developer ID signed by Apple team $EXPECTED_TEAM_IDENTIFIER"
|
|
413
|
-
done
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
write_release_public_key() {
|
|
417
|
-
destination=$1
|
|
418
|
-
if [ -n "${FREECODE_TEST_RELEASE_PUBLIC_KEY_FILE:-}" ]; then
|
|
419
|
-
case "$DOWNLOAD_BASE_URL" in
|
|
420
|
-
file://*) cp "$FREECODE_TEST_RELEASE_PUBLIC_KEY_FILE" "$destination" ;;
|
|
421
|
-
*) fail "the test release key override is permitted only with local file URLs" ;;
|
|
422
|
-
esac
|
|
423
|
-
return
|
|
424
|
-
fi
|
|
425
|
-
cat > "$destination" <<'EOF'
|
|
426
|
-
-----BEGIN PUBLIC KEY-----
|
|
427
|
-
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA8so4sWArOV0GXihYvoSo
|
|
428
|
-
xDLI1yMoILnVfCjYQwe4CKQ9EC10JV30cntMJZWXvXZS6STqQGIjyWisa6Op4nId
|
|
429
|
-
OuXKDjaEcQIeftgZBzzyGrMmAMC3W57VAi3d+w/F2XnkXBaH3NOphfftF6Fxt3Q0
|
|
430
|
-
2CxN3PjuuFaOGytKsYF5uDLCvEb+MpzJlD3HZoBzhhMCAoY6wmCdlgaDxD20UFdy
|
|
431
|
-
EigF5wwpcUWpYmbYMFp+6SLe/Fi8IrYirwiCv5eLur6PzFFcwFrJgUeZAdxmGuM8
|
|
432
|
-
eJzZlc7mK0+gzi8KpC2FZ9eOx1SafPE4An4+u8rZ7WA1Qk5eCAhNU9lm+JAeeKcC
|
|
433
|
-
XzhQpPUafybJg75dIbIayOC3tf5mn+X+Vl5+uY19nT2qcJsjgUeIcqbMs9FGXiIA
|
|
434
|
-
2G292N5WppyiDcx8yAIG6rOrAUGzHMYCrhvxQLAx2KP5QfC53VZ3La7yZOATY5ic
|
|
435
|
-
F78qSSBrKDrT42vinbosbX++Wr34YB6pXhiZF6jHPHrxAgMBAAE=
|
|
436
|
-
-----END PUBLIC KEY-----
|
|
437
|
-
EOF
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
authenticate_release_manifest() {
|
|
441
|
-
manifest_path=$1
|
|
442
|
-
signature_path=$2
|
|
443
|
-
context=$3
|
|
444
|
-
if [ -s "$signature_path" ] && \
|
|
445
|
-
/usr/bin/openssl dgst -sha256 \
|
|
446
|
-
-verify "$RELEASE_PUBLIC_KEY" \
|
|
447
|
-
-signature "$signature_path" \
|
|
448
|
-
"$manifest_path" >/dev/null 2>&1
|
|
449
|
-
then
|
|
450
|
-
return
|
|
451
|
-
fi
|
|
452
|
-
|
|
453
|
-
manifest_sha=$(shasum -a 256 "$manifest_path" | awk '{ print $1 }')
|
|
454
|
-
if [ "$manifest_sha" = "$LEGACY_ALPHA6_MANIFEST_SHA256" ]; then
|
|
455
|
-
printf 'WARNING: %s predates release-manifest signatures; accepting its exact pinned alpha.6 manifest.\n' "$context" >&2
|
|
456
|
-
return
|
|
457
|
-
fi
|
|
458
|
-
fail "$context does not have a valid Free Code release signature"
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
manifest_asset_value() {
|
|
462
|
-
manifest_path=$1
|
|
463
|
-
target=$2
|
|
464
|
-
field=$3
|
|
465
|
-
awk -v target="$target" -v field="$field" '
|
|
466
|
-
/^[[:space:]]*"assets"[[:space:]]*:[[:space:]]*{/ { in_assets = 1; next }
|
|
467
|
-
in_assets && index($0, "\"" target "\"") && index($0, ":") { in_target = 1; next }
|
|
468
|
-
in_target && index($0, "\"" field "\"") {
|
|
469
|
-
value = $0
|
|
470
|
-
sub(/^[^:]*:[[:space:]]*/, "", value)
|
|
471
|
-
sub(/,[[:space:]]*$/, "", value)
|
|
472
|
-
sub(/^"/, "", value)
|
|
473
|
-
sub(/"$/, "", value)
|
|
474
|
-
print value
|
|
475
|
-
exit
|
|
476
|
-
}
|
|
477
|
-
in_target && /^[[:space:]]*}[,]?[[:space:]]*$/ { exit }
|
|
478
|
-
' "$manifest_path"
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
DOWNLOAD_BASE_URL=${DOWNLOAD_BASE_URL%/}
|
|
482
|
-
CHANNEL_BASE_URL=${CHANNEL_BASE_URL%/}
|
|
483
|
-
validate_channel "$CHANNEL"
|
|
484
|
-
|
|
485
|
-
resolve_codesign_bin
|
|
486
|
-
|
|
487
|
-
TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/freecode-install.XXXXXX")
|
|
488
|
-
RELEASE_PUBLIC_KEY="$TEMP_DIR/release-signing-public.pem"
|
|
489
|
-
write_release_public_key "$RELEASE_PUBLIC_KEY"
|
|
490
|
-
|
|
491
|
-
if [ -z "$VERSION" ]; then
|
|
492
|
-
CHANNEL_MANIFEST="$TEMP_DIR/channel.json"
|
|
493
|
-
CHANNEL_SIGNATURE="$TEMP_DIR/channel.json.sig"
|
|
494
|
-
CHANNEL_URL="$CHANNEL_BASE_URL/$CHANNEL.json"
|
|
495
|
-
printf 'Resolving Free Code channel %s...\n' "$CHANNEL"
|
|
496
|
-
curl -fsSL --retry 3 --connect-timeout 15 "$CHANNEL_URL" -o "$CHANNEL_MANIFEST" \
|
|
497
|
-
|| fail "could not download channel manifest: $CHANNEL_URL"
|
|
498
|
-
curl -fsSL --retry 3 --connect-timeout 15 "$CHANNEL_URL.sig" -o "$CHANNEL_SIGNATURE" 2>/dev/null \
|
|
499
|
-
|| rm -f "$CHANNEL_SIGNATURE"
|
|
500
|
-
authenticate_release_manifest "$CHANNEL_MANIFEST" "$CHANNEL_SIGNATURE" "channel $CHANNEL"
|
|
501
|
-
VERSION=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$CHANNEL_MANIFEST" | head -n 1)
|
|
502
|
-
[ -n "$VERSION" ] || fail "channel manifest does not contain a version"
|
|
503
|
-
validate_release_security "$CHANNEL_MANIFEST" "channel $CHANNEL"
|
|
504
|
-
fi
|
|
505
|
-
validate_version "$VERSION"
|
|
506
|
-
|
|
507
|
-
mkdir -p "$INSTALL_ROOT"
|
|
508
|
-
acquire_install_lock
|
|
509
|
-
|
|
510
|
-
ASSET="freecode-darwin-$ARCH.tar.gz"
|
|
511
|
-
TAG="v$VERSION"
|
|
512
|
-
RELEASE_URL="$DOWNLOAD_BASE_URL/$TAG"
|
|
513
|
-
ARCHIVE="$TEMP_DIR/$ASSET"
|
|
514
|
-
RELEASE_MANIFEST="$TEMP_DIR/release-manifest.json"
|
|
515
|
-
RELEASE_SIGNATURE="$TEMP_DIR/release-manifest.json.sig"
|
|
516
|
-
|
|
517
|
-
curl -fsSL --retry 3 --connect-timeout 15 "$RELEASE_URL/release-manifest.json" -o "$RELEASE_MANIFEST" \
|
|
518
|
-
|| fail "could not download release manifest: $RELEASE_URL/release-manifest.json"
|
|
519
|
-
curl -fsSL --retry 3 --connect-timeout 15 "$RELEASE_URL/release-manifest.json.sig" -o "$RELEASE_SIGNATURE" 2>/dev/null \
|
|
520
|
-
|| rm -f "$RELEASE_SIGNATURE"
|
|
521
|
-
authenticate_release_manifest "$RELEASE_MANIFEST" "$RELEASE_SIGNATURE" "release $VERSION"
|
|
522
|
-
|
|
523
|
-
MANIFEST_VERSION=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$RELEASE_MANIFEST" | head -n 1)
|
|
524
|
-
[ "$MANIFEST_VERSION" = "$VERSION" ] \
|
|
525
|
-
|| fail "release manifest version ${MANIFEST_VERSION:-missing} does not match requested version $VERSION"
|
|
526
|
-
validate_release_security "$RELEASE_MANIFEST" "release $VERSION"
|
|
527
|
-
|
|
528
|
-
MANIFEST_TARGET="darwin-$ARCH"
|
|
529
|
-
MANIFEST_ARCHIVE=$(manifest_asset_value "$RELEASE_MANIFEST" "$MANIFEST_TARGET" archive)
|
|
530
|
-
[ "$MANIFEST_ARCHIVE" = "$ASSET" ] \
|
|
531
|
-
|| fail "release manifest does not declare the expected archive $ASSET"
|
|
532
|
-
EXPECTED_SHA=$(manifest_asset_value "$RELEASE_MANIFEST" "$MANIFEST_TARGET" sha256)
|
|
533
|
-
printf '%s\n' "$EXPECTED_SHA" | grep -Eq '^[0-9a-fA-F]{64}$' \
|
|
534
|
-
|| fail "release checksum is not a SHA-256 digest"
|
|
535
|
-
EXPECTED_SIZE=$(manifest_asset_value "$RELEASE_MANIFEST" "$MANIFEST_TARGET" size)
|
|
536
|
-
printf '%s\n' "$EXPECTED_SIZE" | grep -Eq '^[1-9][0-9]*$' \
|
|
537
|
-
|| fail "release manifest does not contain a valid archive size"
|
|
538
|
-
|
|
539
|
-
printf 'Downloading Free Code %s for darwin-%s...\n' "$VERSION" "$ARCH"
|
|
540
|
-
curl -fsSL --retry 3 --connect-timeout 15 "$RELEASE_URL/$ASSET" -o "$ARCHIVE" \
|
|
541
|
-
|| fail "could not download release asset: $RELEASE_URL/$ASSET"
|
|
542
|
-
ACTUAL_SIZE=$(wc -c < "$ARCHIVE" | tr -d '[:space:]')
|
|
543
|
-
[ "$ACTUAL_SIZE" = "$EXPECTED_SIZE" ] \
|
|
544
|
-
|| fail "download size verification failed for $ASSET"
|
|
545
|
-
ACTUAL_SHA=$(shasum -a 256 "$ARCHIVE" | awk '{ print $1 }')
|
|
546
|
-
[ "$ACTUAL_SHA" = "$EXPECTED_SHA" ] \
|
|
547
|
-
|| fail "checksum verification failed for $ASSET"
|
|
548
|
-
|
|
549
|
-
if tar -tzf "$ARCHIVE" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then
|
|
550
|
-
fail "release archive contains an unsafe path"
|
|
551
|
-
fi
|
|
552
|
-
|
|
553
|
-
mkdir -p "$INSTALL_ROOT/versions" "$BIN_DIR"
|
|
554
|
-
VERSION_DIR="$INSTALL_ROOT/versions/$VERSION"
|
|
555
|
-
NEW_INSTALL="false"
|
|
556
|
-
|
|
557
|
-
if [ ! -d "$VERSION_DIR" ]; then
|
|
558
|
-
STAGE_DIR=$(mktemp -d "$INSTALL_ROOT/.install-$VERSION.XXXXXX")
|
|
559
|
-
tar -xzf "$ARCHIVE" -C "$STAGE_DIR"
|
|
560
|
-
[ -x "$STAGE_DIR/$LAUNCHER_RELATIVE_PATH" ] \
|
|
561
|
-
|| fail "release archive is missing executable $LAUNCHER_RELATIVE_PATH"
|
|
562
|
-
[ -x "$STAGE_DIR/$INTERFACE_RELATIVE_PATH" ] \
|
|
563
|
-
|| fail "release archive is missing $INTERFACE_RELATIVE_PATH"
|
|
564
|
-
[ -x "$STAGE_DIR/$ENGINE_RELATIVE_PATH" ] \
|
|
565
|
-
|| fail "release archive is missing $ENGINE_RELATIVE_PATH"
|
|
566
|
-
verify_release_signatures "$STAGE_DIR"
|
|
567
|
-
mv "$STAGE_DIR" "$VERSION_DIR"
|
|
568
|
-
STAGE_DIR=""
|
|
569
|
-
NEW_INSTALL="true"
|
|
570
|
-
else
|
|
571
|
-
[ -x "$VERSION_DIR/$LAUNCHER_RELATIVE_PATH" ] \
|
|
572
|
-
|| fail "existing install is incomplete: $VERSION_DIR"
|
|
573
|
-
[ -x "$VERSION_DIR/$INTERFACE_RELATIVE_PATH" ] \
|
|
574
|
-
|| fail "existing install is incomplete: $VERSION_DIR/$INTERFACE_RELATIVE_PATH"
|
|
575
|
-
[ -x "$VERSION_DIR/$ENGINE_RELATIVE_PATH" ] \
|
|
576
|
-
|| fail "existing install is incomplete: $VERSION_DIR/$ENGINE_RELATIVE_PATH"
|
|
577
|
-
verify_release_signatures "$VERSION_DIR"
|
|
578
|
-
fi
|
|
579
|
-
|
|
580
|
-
PREVIOUS_LINK=""
|
|
581
|
-
if [ -L "$BIN_DIR/freecode" ]; then
|
|
582
|
-
PREVIOUS_LINK=$(readlink "$BIN_DIR/freecode")
|
|
583
|
-
elif [ -e "$BIN_DIR/freecode" ]; then
|
|
584
|
-
fail "refusing to replace non-symlink executable: $BIN_DIR/freecode"
|
|
585
|
-
fi
|
|
586
|
-
LINK_TEMP="$BIN_DIR/.freecode-link.$$"
|
|
587
|
-
rm -f "$LINK_TEMP"
|
|
588
|
-
ln -s "$VERSION_DIR/$LAUNCHER_RELATIVE_PATH" "$LINK_TEMP"
|
|
589
|
-
mv -f "$LINK_TEMP" "$BIN_DIR/freecode"
|
|
590
|
-
|
|
591
|
-
rollback_activation() {
|
|
592
|
-
rm -f "$BIN_DIR/freecode"
|
|
593
|
-
if [ -n "$PREVIOUS_LINK" ]; then
|
|
594
|
-
ln -s "$PREVIOUS_LINK" "$BIN_DIR/freecode"
|
|
595
|
-
fi
|
|
596
|
-
if [ "$NEW_INSTALL" = "true" ]; then
|
|
597
|
-
rm -rf "$VERSION_DIR"
|
|
598
|
-
fi
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
printf 'Verifying Free Code %s...\n' "$VERSION"
|
|
602
|
-
# Runtime diagnostics (such as Bun's AVX warning under Rosetta) go to stderr;
|
|
603
|
-
# only stdout carries the version, and it must match exactly.
|
|
604
|
-
VERSION_STDERR="$TEMP_DIR/version.stderr"
|
|
605
|
-
VERSION_OUTPUT=$("$BIN_DIR/freecode" --version 2>"$VERSION_STDERR") || {
|
|
606
|
-
cat "$VERSION_STDERR" >&2
|
|
607
|
-
rollback_activation
|
|
608
|
-
fail "freecode --version failed; restored the previous install"
|
|
609
|
-
}
|
|
610
|
-
cat "$VERSION_STDERR" >&2
|
|
611
|
-
printf '%s\n' "$VERSION_OUTPUT"
|
|
612
|
-
[ "$VERSION_OUTPUT" = "freecode-sh $VERSION" ] || {
|
|
613
|
-
rollback_activation
|
|
614
|
-
fail "freecode --version reported the wrong release; restored the previous install"
|
|
615
|
-
}
|
|
616
|
-
if ! "$BIN_DIR/freecode" doctor; then
|
|
617
|
-
rollback_activation
|
|
618
|
-
fail "freecode doctor failed; restored the previous install"
|
|
619
|
-
fi
|
|
620
|
-
|
|
621
|
-
rm -f "$INSTALL_ROOT/current-version"
|
|
622
|
-
printf '%s\n' "$BIN_DIR" > "$INSTALL_ROOT/bin-dir"
|
|
623
|
-
printf '%s\n' "$CHANNEL" > "$INSTALL_ROOT/channel"
|
|
624
|
-
printf '%s\n' "$DOWNLOAD_BASE_URL" > "$INSTALL_ROOT/release-base-url"
|
|
625
|
-
printf '%s\n' "$CHANNEL_BASE_URL" > "$INSTALL_ROOT/channel-base-url"
|
|
626
|
-
|
|
627
|
-
release_install_lock
|
|
628
|
-
|
|
629
|
-
printf '\nFree Code %s installed successfully.\n' "$VERSION"
|
|
630
|
-
printf 'Executable: %s/freecode\n' "$BIN_DIR"
|
|
631
|
-
|
|
632
|
-
if [ "$EMBEDDED_INSTALL" = "true" ]; then
|
|
633
|
-
exit 0
|
|
634
|
-
fi
|
|
635
|
-
|
|
636
|
-
configure_path
|
|
637
|
-
|
|
638
|
-
if [ "$PATH_CONFIGURED" = "true" ]; then
|
|
639
|
-
printf 'Added Free Code to your PATH in %s.\n' "$PATH_PROFILE"
|
|
640
|
-
else
|
|
641
|
-
printf '\nFree Code could not update your shell profile automatically.\n'
|
|
642
|
-
printf 'Add this line to your shell profile for future Terminal windows:\n'
|
|
643
|
-
printf ' export PATH="%s:$PATH"\n' "$BIN_DIR"
|
|
644
|
-
fi
|
|
645
|
-
|
|
646
|
-
printf '\nVerify the install:\n'
|
|
647
|
-
printf ' %s/freecode --version\n' "$BIN_DIR"
|
|
648
|
-
printf ' %s/freecode doctor\n' "$BIN_DIR"
|
|
649
|
-
printf '\nUninstall:\n'
|
|
650
|
-
printf ' rm -f "%s/freecode" && rm -rf "%s"\n' "$BIN_DIR" "$INSTALL_ROOT"
|
|
651
|
-
|
|
652
|
-
if [ "$LAUNCH_AFTER_INSTALL" = "true" ]; then
|
|
653
|
-
if [ -n "${FREECODE_TEST_TTY_FILE:-}" ]; then
|
|
654
|
-
if [ -r "$FREECODE_TEST_TTY_FILE" ]; then
|
|
655
|
-
CAN_LAUNCH="true"
|
|
656
|
-
else
|
|
657
|
-
CAN_LAUNCH="false"
|
|
658
|
-
fi
|
|
659
|
-
elif [ -t 1 ]; then
|
|
660
|
-
CAN_LAUNCH="true"
|
|
661
|
-
else
|
|
662
|
-
CAN_LAUNCH="false"
|
|
663
|
-
fi
|
|
664
|
-
|
|
665
|
-
if [ "$CAN_LAUNCH" = "true" ]; then
|
|
666
|
-
printf '\nStarting Free Code...\n'
|
|
667
|
-
if [ -n "${FREECODE_TEST_TTY_FILE:-}" ]; then
|
|
668
|
-
if "$BIN_DIR/freecode" < "$FREECODE_TEST_TTY_FILE"; then
|
|
669
|
-
LAUNCH_SUCCEEDED="true"
|
|
670
|
-
else
|
|
671
|
-
LAUNCH_SUCCEEDED="false"
|
|
672
|
-
fi
|
|
673
|
-
elif "$BIN_DIR/freecode" 0<&1; then
|
|
674
|
-
LAUNCH_SUCCEEDED="true"
|
|
675
|
-
else
|
|
676
|
-
LAUNCH_SUCCEEDED="false"
|
|
677
|
-
fi
|
|
678
|
-
|
|
679
|
-
if [ "$LAUNCH_SUCCEEDED" != "true" ]; then
|
|
680
|
-
printf '\nFree Code could not start automatically, but the installation succeeded.\n' >&2
|
|
681
|
-
printf 'Open a Terminal and run:\n "%s/freecode"\n' "$BIN_DIR" >&2
|
|
682
|
-
fi
|
|
683
|
-
else
|
|
684
|
-
printf '\nFree Code is installed. Open a Terminal and run:\n'
|
|
685
|
-
printf ' "%s/freecode"\n' "$BIN_DIR"
|
|
686
|
-
fi
|
|
687
|
-
fi
|