-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbooth
More file actions
executable file
·716 lines (622 loc) · 24.7 KB
/
booth
File metadata and controls
executable file
·716 lines (622 loc) · 24.7 KB
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
#!/bin/bash
# Copyright 2025-2026 : Nawa Manusitthipol
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# CodingBooth Wrapper (booth)
# Downloads, verifies, and runs the platform-specific CodingBooth binary.
# Install: curl -fsSL https://codingbooth.io/install.sh | bash
set -euo pipefail
trap 'status=$?; echo "❌ Error on line $LINENO (exit $status)" >&2; exit "$status"' ERR
# --- PIPE INSTALL DETECTION ---
# Detect if running via pipe (curl ... | bash)
# When piped, $0 is the shell name, not a script path
if [[ "$0" == "bash" || "$0" == "-bash" || "$0" == "/bin/bash" || \
"$0" == "sh" || "$0" == "-sh" || "$0" == "/bin/sh" || \
"$0" == "zsh" || "$0" == "-zsh" || "$0" == "/bin/zsh" ]]; then
echo "Installing CodingBooth wrapper..."
curl -fsSL https://codingbooth.io/install.sh | bash
exit 0
fi
VERSION=0.13.0
VERBOSE="${VERBOSE:-true}"
# Resolve script directory (follow symlinks) so all project paths are
# location-based. realpath canonicalises the full path in one call.
get_script_dir() {
dirname "$(realpath "${BASH_SOURCE[0]}")"
}
SCRIPT_DIR="$(get_script_dir)"
BOOTH_DIR="${SCRIPT_DIR}/.booth"
TOOLS_DIR="${BOOTH_DIR}/tools"
LOCK_FILE="${TOOLS_DIR}/codingbooth.lock"
# --- NESTED BOOTH DETECTION ---
# Detect if running inside a CodingBooth container and prevent accidental nested execution.
# Users can explicitly allow nested execution by setting BOOTH_IN_BOOTH=true and using a different port.
detect_nested_booth() {
# Markers for being inside a booth: /opt/codingbooth/ dir or BOOTH_CONTAINER_NAME.
[[ ! -d "/opt/codingbooth" && -z "${BOOTH_CONTAINER_NAME:-}" ]] && return 0
if [[ "${BOOTH_IN_BOOTH:-}" != "true" ]]; then
cat >&2 <<EOF
Error: Running booth inside a booth container.
To configure this booth, run: $0 config
To launch a nested booth, set BOOTH_IN_BOOTH=true and pass --port <different>.
EOF
exit 1
fi
# Find --port in args.
local requested_port=""
while [[ $# -gt 0 ]]; do
case "$1" in
--port=*) requested_port="${1#--port=}"; break ;;
--port) requested_port="${2:-}"; break ;;
*) shift ;;
esac
done
local host_port="${BOOTH_HOST_PORT:-}"
local code_port="${BOOTH_CODE_PORT:-10000}"
if [[ -z "$requested_port" ]]; then
cat >&2 <<EOF
Error: nested booth requires an explicit --port.
Current container: host=${host_port:-(not set)}, code=${code_port}
Use: --port NEXT | --port RANDOM | --port <number>
EOF
exit 1
fi
if [[ "$requested_port" != "NEXT" && "$requested_port" != "RANDOM" ]]; then
if [[ "$requested_port" == "$host_port" || "$requested_port" == "$code_port" ]]; then
local kind="host port"
[[ "$requested_port" == "$code_port" ]] && kind="code port"
echo "Error: --port $requested_port conflicts with this container's $kind." >&2
echo " Use: --port NEXT | --port RANDOM | --port <other-number>" >&2
exit 1
fi
fi
}
# --- CENTRAL CACHE SETUP ---
# Platform-specific cache directory for shared binary storage
get_cache_dir() {
case "$(uname -s)" in
Linux*) echo "${XDG_CACHE_HOME:-$HOME/.cache}/codingbooth" ;;
Darwin*) echo "$HOME/Library/Caches/codingbooth" ;;
MINGW*|MSYS*|CYGWIN*)
if [[ -n "${LOCALAPPDATA:-}" ]]; then
echo "$LOCALAPPDATA/codingbooth"
else
echo "$HOME/AppData/Local/codingbooth"
fi
;;
*) echo "${XDG_CACHE_HOME:-$HOME/.cache}/codingbooth" ;;
esac
}
BOOTH_CACHE_DIR="${BOOTH_CACHE_DIR:-$(get_cache_dir)}"
# Get version-specific cache directory
get_cache_version_dir() {
local version="$1"
echo "${BOOTH_CACHE_DIR}/versions/${version}"
}
# Find binary directory: local first, then shared cache
# Returns directory path on stdout, returns 0 if found, 1 if not found
find_binary_dir() {
local version="$1"
local platform="$2"
local binary_name
binary_name=$(get_binary_name "$platform")
# Check local first (for --cache=local mode)
if [[ -f "${TOOLS_DIR}/${binary_name}" ]]; then
echo "$TOOLS_DIR"
return 0
fi
# Then check shared cache
local shared_dir
shared_dir="$(get_cache_version_dir "$version")"
if [[ -f "${shared_dir}/${binary_name}" ]]; then
echo "$shared_dir"
return 0
fi
# Not found
return 1
}
# Decide what the "command" is:
if [[ $# -eq 0 ]]; then COMMAND="run" ; else COMMAND="$1" ; fi
function Main() {
### --- COMMAND DISPATCH --- ###
case "${COMMAND}" in
uninstall)
shift # Remove 'uninstall'
UninstallBooth "$@"
exit 0
;;
install|update)
local cmd="$COMMAND"
shift
local cache_mode="shared"
local version_arg=""
while [[ $# -gt 0 ]]; do
case "$1" in
--cache=*) cache_mode="${1#--cache=}"; shift ;;
--cache) cache_mode="$2"; shift 2 ;;
-*) echo "Unknown option: $1" >&2; exit 1 ;;
*) version_arg="$1"; shift ;;
esac
done
if [[ "$cache_mode" != "local" && "$cache_mode" != "shared" ]]; then
echo "Error: Invalid cache mode '$cache_mode'. Use 'local' or 'shared'." >&2
exit 1
fi
if [[ "$cmd" == "install" && -z "$version_arg" && -f "$LOCK_FILE" ]]; then
local lock_version
lock_version=$(grep '^version=' "$LOCK_FILE" 2>/dev/null | cut -d= -f2-)
echo "CodingBooth is already installed (version: ${lock_version:-unknown})."
echo "Use '$0 update' to update to the latest version."
exit 0
fi
DownloadBooth "${version_arg:-latest}" "$cache_mode"
exit 0
;;
run) [[ "${1-}" == "run" ]] && shift ; ;;
*) ;;
esac
### --- RUN MODE --- ###
# Read version and cache mode from lock file
if [[ ! -f "$LOCK_FILE" ]]; then
echo "CodingBooth is not installed."
echo "Please run: $0 install"
exit 1
fi
local lock_version lock_cache
lock_version=$(grep '^version=' "$LOCK_FILE" 2>/dev/null | cut -d= -f2-)
lock_cache=$(grep '^cache=' "$LOCK_FILE" 2>/dev/null | cut -d= -f2- || echo "shared")
if [[ -z "$lock_version" ]]; then
echo "Invalid lock file: missing version"
echo "Please run: $0 install"
exit 1
fi
# Detect platform
local platform binary_name
if ! platform=$(detect_platform); then
echo "Error: Failed to detect platform" >&2
exit 1
fi
binary_name=$(get_binary_name "$platform")
# Find binary directory (local first, then shared cache)
local binary_dir sha_file dest
if ! binary_dir=$(find_binary_dir "$lock_version" "$platform"); then
# Binary not found, auto-download
echo "Binary missing, downloading version $lock_version..."
DownloadBooth "$lock_version" "$lock_cache"
if ! binary_dir=$(find_binary_dir "$lock_version" "$platform"); then
echo "Failed to download binary"
exit 1
fi
fi
sha_file="$binary_dir/codingbooth.sha256"
dest="$binary_dir/$binary_name"
# Verify binary exists
if [[ ! -f "$dest" || ! -f "$sha_file" ]]; then
echo "CodingBooth binary or checksum missing for platform: $platform"
echo "Please run: $0 install"
exit 1
fi
# Verify SHA256 for this platform's binary
local expected_sha256 actual_sha256
expected_sha256=$(grep " $binary_name\$" "$sha_file" 2>/dev/null | awk '{print $1}')
if [[ -z "$expected_sha256" ]]; then
echo "No SHA256 entry found for $binary_name"
echo "Run: $0 update to restore the official release."
exit 1
fi
actual_sha256=$(hash_sha256 "$dest" | awk '{gsub(/^\\/, ""); print $1}')
if [[ "$expected_sha256" != "$actual_sha256" ]]; then
echo "Binary ($binary_name) failed SHA256 verification."
echo "Run: $0 update to restore the official release."
exit 1
fi
# Create/update symlink named 'booth' so the binary displays correct name
local booth_link="$binary_dir/booth"
if [[ ! -L "$booth_link" ]] || [[ "$(readlink "$booth_link")" != "$binary_name" ]]; then
ln -sf "$binary_name" "$booth_link" 2>/dev/null || true
fi
# Execute via symlink if available, otherwise direct
if [[ -L "$booth_link" ]]; then
exec "$booth_link" "$@"
else
exec "$dest" "$@"
fi
}
function PrintHelp() {
cat <<EOF
Usage: ./$(basename "$0") <command> [args...]
Purpose:
This script is the *CodingBooth Wrapper*.
- It downloads, verifies, and runs the CodingBooth binary.
- Binaries are cached in a shared location (default) or per-project.
Wrapper commands:
install [VERSION] Install binary (skips if already installed)
install --cache=shared [VER] Install to shared cache (explicit)
install --cache=local [VER] Install to .booth/tools/ (project-local)
update [VERSION] Update binary to latest (or specified) version
uninstall [SCOPES...] [-y] Remove this project's booth (interactive by default)
Scopes: --shared-binary (also this version's shared cache)
--all-shared-binary (also every shared cache version)
--wrapper (also delete ./booth wrapper)
--all (everything above)
run [ARGS...] Run booth with ARGS (after integrity checks)
version Show version information
help Show this help message
Cache modes:
--cache=shared Store in user cache, shared across projects (default)
--cache=local Store in .booth/tools/, project-specific
Cache locations (for --cache=shared):
Linux: ~/.cache/codingbooth/
macOS: ~/Library/Caches/codingbooth/
Windows: %LOCALAPPDATA%\\codingbooth\\
Notes:
- Lock file (.booth/tools/codingbooth.lock) is version-controlled
- Binary is auto-downloaded when lock file exists but binary missing
- Only the current platform's binary is downloaded
- Use --cache=local for CI/CD or portable/air-gapped environments
- Set VERBOSE=true for extra logs during install
- To update the wrapper itself, re-run:
curl -fsSL https://codingbooth.io/install.sh | bash
Help/Version disambiguation:
./booth help Show this wrapper help (install, update, cache commands)
./booth --help Show codingbooth binary help (run flags, variants, etc.)
./booth version Show wrapper version + binary version info
./booth --version Show codingbooth binary version only
EOF
}
function PrintVersion() {
cat <<'EOF'
____ _ _ ____ _ _
/ ___|___ __| (_)_ __ __ _| __ ) ___ ___ | |_| |__
| | / _ \ / _` | | '_ \ / _` | _ \ / _ \ / _ \| __| '_ \
| |__| (_) | (_| | | | | | (_| | |_) | (_) | (_) | |_| | | |
\____\___/ \__,_|_|_| |_|\__, |____/ \___/ \___/ \__|_| |_|
|___/
EOF
echo "CodingBooth Wrapper: $VERSION"
# Detect current platform
local platform binary_name
platform=$(detect_platform 2>/dev/null || echo "unknown")
binary_name=$(get_binary_name "$platform")
# Check if lock file exists
if [[ ! -f "$LOCK_FILE" ]]; then
echo "CodingBooth: not installed"
echo "Platform: $platform"
echo "Shared cache: $BOOTH_CACHE_DIR"
exit 0
fi
# Read lock file
local lock_version lock_cache
lock_version=$(grep '^version=' "$LOCK_FILE" 2>/dev/null | cut -d= -f2- || echo "unknown")
lock_cache=$(grep '^cache=' "$LOCK_FILE" 2>/dev/null | cut -d= -f2- || echo "shared")
# Find binary
local binary_dir TOOL
if binary_dir=$(find_binary_dir "$lock_version" "$platform" 2>/dev/null); then
TOOL="$binary_dir/$binary_name"
else
echo "CodingBooth: $lock_version (binary missing)"
echo "Platform: $platform"
echo "Cache mode: $lock_cache"
exit 0
fi
local TOOL_VERSION
TOOL_VERSION=$("$TOOL" version 2>/dev/null || echo "unknown")
echo ""
echo "$TOOL_VERSION"
echo "Platform: $platform"
echo "Cache mode: $lock_cache"
if [[ "$lock_cache" == "shared" ]]; then
echo "Binary location: $binary_dir"
fi
}
# Portable SHA256 helper
function hash_sha256() {
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$@"
elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$@"
else echo "Error: No SHA256 tool found (sha256sum or shasum)." >&2 ; return 1
fi
}
# Detect platform (OS-ARCH format)
function detect_platform() {
local os arch
# Detect OS
case "$(uname -s)" in
Linux*) os="linux" ;;
Darwin*) os="darwin" ;;
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
*) echo "Error: Unsupported OS: $(uname -s)" >&2; return 1 ;;
esac
# Detect architecture
case "$(uname -m)" in
x86_64|amd64) arch="amd64" ;;
aarch64|arm64) arch="arm64" ;;
*) echo "Error: Unsupported architecture: $(uname -m)" >&2; return 1 ;;
esac
echo "${os}-${arch}"
}
# Get binary name for platform (adds .exe for Windows)
function get_binary_name() {
local platform="$1"
if [[ "$platform" == windows-* ]]; then
echo "codingbooth-${platform}.exe"
else
echo "codingbooth-${platform}"
fi
}
function UninstallBooth() {
local remove_shared_binary=false
local remove_all_shared_binary=false
local remove_wrapper=false
local assume_yes=false
while [[ $# -gt 0 ]]; do
case "$1" in
--shared-binary) remove_shared_binary=true; shift ;;
--all-shared-binary) remove_all_shared_binary=true; shift ;;
--wrapper) remove_wrapper=true; shift ;;
--all) remove_all_shared_binary=true; remove_wrapper=true; shift ;;
-y|--yes) assume_yes=true; shift ;;
*) echo "Unknown option: $1" >&2
echo "Usage: $0 uninstall [--shared-binary] [--all-shared-binary] [--wrapper] [--all] [-y]" >&2
exit 1 ;;
esac
done
local tools_dir="$TOOLS_DIR"
local sha_file="$tools_dir/codingbooth.sha256"
local lock_file="$LOCK_FILE"
# Read lock version BEFORE removing it (needed for --shared-binary scope).
local lock_version=""
if [[ -f "$lock_file" ]]; then
lock_version=$(grep '^version=' "$lock_file" 2>/dev/null | cut -d= -f2-)
fi
# Build summary of what will be removed (shown in the confirmation prompt).
local -a summary=()
summary+=("- Project binary association ($tools_dir lock/sha + project-local binaries)")
if [[ "$remove_all_shared_binary" == "true" ]]; then
summary+=("- ALL shared-cache binaries ($BOOTH_CACHE_DIR/versions/)")
elif [[ "$remove_shared_binary" == "true" ]]; then
if [[ -n "$lock_version" ]]; then
summary+=("- Shared-cache binary for v$lock_version ($BOOTH_CACHE_DIR/versions/$lock_version/)")
else
summary+=("- Shared-cache binary (no lock file — nothing to remove)")
fi
fi
if [[ "$remove_wrapper" == "true" ]]; then
summary+=("- Wrapper script ($SCRIPT_DIR/booth)")
fi
# Confirmation prompt (single prompt, lists everything).
if [[ "$assume_yes" != "true" ]]; then
if [[ ! -t 0 ]]; then
echo "Error: not connected to a terminal — re-run with -y to skip prompts." >&2
exit 1
fi
echo "Uninstall will remove:"
for line in "${summary[@]}"; do echo " $line"; done
echo ""
printf "Proceed? [y/N] "
local reply
read -r reply
case "$reply" in
y|Y|yes|YES|Yes) ;;
*) echo "Cancelled." ; exit 0 ;;
esac
echo ""
fi
# 1. Project binary association (always).
rm -f "$tools_dir"/codingbooth-* "$tools_dir/booth" "$sha_file" "$lock_file"
rmdir "$tools_dir" 2>/dev/null || true
rmdir "$BOOTH_DIR" 2>/dev/null || true
echo "✓ Removed project binary association."
# 2. Shared-cache binary.
if [[ "$remove_all_shared_binary" == "true" ]]; then
rm -rf "$BOOTH_CACHE_DIR/versions"
echo "✓ Removed all cached versions ($BOOTH_CACHE_DIR/versions)."
elif [[ "$remove_shared_binary" == "true" ]]; then
if [[ -n "$lock_version" ]]; then
rm -rf "$BOOTH_CACHE_DIR/versions/$lock_version"
echo "✓ Removed cached version $lock_version."
else
echo "(no lock file — nothing to remove from shared cache)"
fi
fi
# 3. Wrapper script (self-delete — safe because the script is already mmap'd).
if [[ "$remove_wrapper" == "true" ]]; then
local wrapper_path="$SCRIPT_DIR/booth"
if [[ -f "$wrapper_path" ]]; then
rm -f "$wrapper_path"
echo "✓ Removed wrapper: $wrapper_path"
fi
fi
echo ""
echo "CodingBooth has been uninstalled from this project."
if [[ "$remove_shared_binary" != "true" && "$remove_all_shared_binary" != "true" ]]; then
echo "To clean shared cache, run: $0 tools-cache clean"
fi
}
function DownloadBooth() {
local CB_VERSION=${1:-latest}
local CACHE_MODE=${2:-shared}
local tools_dir="$TOOLS_DIR"
local lock_file="$LOCK_FILE"
REPO_URL="https://github.com/NawaMan/CodingBooth"
DWLD_URL="${REPO_URL}/releases/download"
# Download version.txt to get the actual version first
local actual_version=""
local VERSION_URL="${DWLD_URL}/${CB_VERSION}/version.txt"
if actual_version=$(curl -fsSL --retry 3 --retry-delay 2 --retry-connrefused "$VERSION_URL" 2>/dev/null | tr -d ' \t\r\n'); then
[[ "$VERBOSE" == "true" ]] && echo " Version: $actual_version"
else
actual_version="$CB_VERSION"
fi
# Determine target directory based on cache mode
local target_dir sha_file
if [[ "$CACHE_MODE" == "local" ]]; then
target_dir="$tools_dir"
sha_file="$tools_dir/codingbooth.sha256"
else
target_dir="$(get_cache_version_dir "$actual_version")"
sha_file="$target_dir/codingbooth.sha256"
fi
mkdir -p "$target_dir"
mkdir -p "$tools_dir" # Always need tools dir for lock file
# For specific versions (not 'latest'), check if already cached and valid
if [[ "$CB_VERSION" != "latest" ]]; then
local current_platform_check
if current_platform_check=$(detect_platform 2>/dev/null); then
local check_binary_name check_dest
check_binary_name=$(get_binary_name "$current_platform_check")
check_dest="$target_dir/$check_binary_name"
if [[ -f "$check_dest" && -f "$sha_file" ]]; then
local check_expected check_actual
check_expected=$(grep " ${check_binary_name}\$" "$sha_file" 2>/dev/null | awk '{print $1}')
check_actual=$(hash_sha256 "$check_dest" | awk '{gsub(/^\\/, ""); print $1}')
if [[ -n "$check_expected" && "$check_expected" == "$check_actual" ]]; then
# Check if this is a version switch
local prev_version=""
if [[ -f "$lock_file" ]]; then
prev_version=$(grep '^version=' "$lock_file" 2>/dev/null | cut -d= -f2-)
fi
if [[ -n "$prev_version" && "$prev_version" != "$actual_version" ]]; then
echo "Switched from $prev_version to $actual_version."
fi
echo "CodingBooth $actual_version is already up-to-date."
# Update lock file to point to this version
{
echo "version=${actual_version}"
echo "downloaded_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "cache=${CACHE_MODE}"
} > "$lock_file"
return 0
fi
fi
fi
fi
# Create .gitignore based on cache mode.
# Must stay in sync with cli/src/pkg/boothinit/output/writer.go (gitignoreContent)
# so secrets / cache/ / .tmp/ stay gitignored regardless of which path wrote last.
if [[ "$CACHE_MODE" == "local" ]]; then
cat > "${BOOTH_DIR}/.gitignore" <<'GITIGNORE'
# Secrets - never commit
.booth.password
.env
# Local persistent state (not committed)
cache/
# Runtime temp files
.tmp/
# Binaries excluded - re-download from lock version
tools/codingbooth-*
tools/*.sha256
GITIGNORE
else
cat > "${BOOTH_DIR}/.gitignore" <<'GITIGNORE'
# Secrets - never commit
.booth.password
.env
# Local persistent state (not committed)
cache/
# Runtime temp files
.tmp/
# Lock file is version-controlled
# Binaries are in ~/.cache/codingbooth/ (not here)
GITIGNORE
fi
local current_platform
if ! current_platform=$(detect_platform); then
echo "Error: Failed to detect platform" >&2
return 1
fi
if [[ "$CACHE_MODE" == "local" ]]; then
echo "Downloading CodingBooth binary to project (--cache=local)..."
else
echo "Downloading CodingBooth binary to shared cache..."
echo " Cache: $target_dir"
fi
local binary_name dest TOOL_URL SHA256_URL
binary_name=$(get_binary_name "$current_platform")
dest="$target_dir/$binary_name"
TOOL_URL="${DWLD_URL}/${CB_VERSION}/${binary_name}"
SHA256_URL="${DWLD_URL}/${CB_VERSION}/${binary_name}.sha256"
if [[ "$VERBOSE" == "true" ]]; then
echo " Checking: $binary_name"
else
echo -n " $current_platform ... "
fi
local tmpsha256 expected_sha256
tmpsha256=$(mktemp "/tmp/booth.sha256.XXXXXX")
if ! curl -fsSLo "$tmpsha256" --retry 3 --retry-delay 2 --retry-connrefused "$SHA256_URL"; then
echo "FAILED (sha256 fetch)"
rm -f "$tmpsha256"
echo "Error: Failed to download binary for $current_platform" >&2
return 1
fi
expected_sha256=$(awk '{print $1}' "$tmpsha256")
rm -f "$tmpsha256"
if ! [[ "$expected_sha256" =~ ^[0-9a-fA-F]{64}$ ]]; then
echo "FAILED (malformed sha256)"
return 1
fi
# If existing binary already matches expected SHA, no download needed.
local need_download=true
if [[ -f "$dest" ]]; then
local existing_sha256
existing_sha256=$(hash_sha256 "$dest" | awk '{print $1}')
if [[ "$expected_sha256" == "$existing_sha256" ]]; then
need_download=false
if [[ "$VERBOSE" != "true" ]]; then
echo "✓ up-to-date"
else
echo " Already up-to-date: $binary_name"
fi
fi
fi
if $need_download; then
if [[ "$VERBOSE" == "true" ]]; then
echo " Downloading: $binary_name"
fi
local tmpfile actual_sha256
tmpfile=$(mktemp "/tmp/booth.XXXXXX")
if ! curl -fsSLo "$tmpfile" --retry 3 --retry-delay 2 --retry-connrefused "$TOOL_URL"; then
echo "FAILED (download)"
rm -f "$tmpfile"
return 1
fi
actual_sha256=$(hash_sha256 "$tmpfile" | awk '{print $1}')
if [[ "$expected_sha256" != "$actual_sha256" ]]; then
echo "FAILED (sha256 mismatch)"
rm -f "$tmpfile"
return 1
fi
mv -f "$tmpfile" "$dest"
chmod 744 "$dest"
if [[ "$VERBOSE" != "true" ]]; then
echo "OK"
fi
fi
# Write SHA file (single entry, single platform).
printf '%s %s\n' "$expected_sha256" "$binary_name" > "$sha_file"
# Write lock file (always in .booth/tools/)
{
echo "version=${actual_version}"
echo "downloaded_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "cache=${CACHE_MODE}"
} > "$lock_file"
echo "CodingBooth installed: downloaded, verified, and installed."
if [[ "$VERBOSE" == "true" ]]; then
echo "Lock file: $lock_file"
fi
}
# Early handling of version/help so they don't require curl
case "${COMMAND}" in
version) PrintVersion ; exit 0 ; ;;
help) PrintHelp ; exit 0 ; ;;
esac
# Check for nested booth execution (running booth inside a booth container)
# Only warn for 'run' command — other commands (list, init, etc.) are safe inside a container
if [[ "${COMMAND}" == "run" ]]; then
detect_nested_booth "$@"
fi
# Need curl for install/run/update/uninstall
if ! command -v curl >/dev/null 2>&1; then
echo "Error: curl is required but was not found." >&2
exit 1
fi
Main "$@"